The OpenMC Monte Carlo Code¶
OpenMC is a community-developed Monte Carlo neutron and photon transport simulation code. It is capable of performing fixed source, k-eigenvalue, and subcritical multiplication calculations on models built using either a constructive solid geometry or CAD representation. OpenMC supports both continuous-energy and multigroup transport. The continuous-energy particle interaction data is based on a native HDF5 format that can be generated from ACE files produced by NJOY. Parallelism is enabled via a hybrid MPI and OpenMP programming model.
OpenMC was originally developed by members of the Computational Reactor Physics Group at the Massachusetts Institute of Technology starting in 2011. Various universities, laboratories, and other organizations now contribute to the development of OpenMC. For more information on OpenMC, feel free to post a message on the OpenMC Discourse Forum.
Recommended publication for citing
Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit Forget, and Kord Smith, “OpenMC: A State-of-the-Art Monte Carlo Code for Research and Development,” Ann. Nucl. Energy, 82, 90–97 (2015).
Contents¶
Quick Install Guide¶
This quick install guide outlines the basic steps needed to install OpenMC on your computer. For more detailed instructions on configuring and installing OpenMC, see Installation and Configuration in the User’s Manual.
Installing on Linux/Mac with conda-forge¶
Conda is an open source package management system and environment management system for installing multiple versions of software packages and their dependencies and switching easily between them. If you have conda installed on your system, OpenMC can be installed via the conda-forge channel. First, add the conda-forge channel with:
conda config --add channels conda-forge
To list the versions of OpenMC that are available on the conda-forge channel, in your terminal window or an Anaconda Prompt run:
conda search openmc
OpenMC can then be installed with:
conda create -n openmc-env openmc
This will install OpenMC in a conda environment called openmc-env. To activate the environment, run:
conda activate openmc-env
Installing on Linux/Mac/Windows with Docker¶
OpenMC can be easily deployed using Docker on any Windows, Mac, or Linux system. With Docker running, execute the following command in the shell to download and run a Docker image with the most recent release of OpenMC from DockerHub:
docker run openmc/openmc:latest
This will take several minutes to run depending on your internet download speed. The command will place you in an interactive shell running in a Docker container with OpenMC installed.
Note
The docker run
command supports many options for spawning
containers including mounting volumes from the host filesystem,
which many users will find useful.
Installing from Source using Spack¶
Spack is a package management tool designed to support multiple versions and configurations of software on a wide variety of platforms and environments. Please follow Spack’s setup guide to configure the Spack system.
To install the latest OpenMC with the Python API, use the following command:
spack install py-openmc
For more information about customizations including MPI, see the detailed installation instructions using Spack. Once installed, environment/lmod modules can be generated or Spack’s load feature can be used to access the installed packages.
Installing from Source on Ubuntu¶
To build OpenMC from source, several prerequisites are needed. If you are using Ubuntu or higher, all prerequisites can be installed directly from the package manager:
sudo apt install g++ cmake libhdf5-dev
After the packages have been installed, follow the instructions below for building and installing OpenMC from source.
Installing from Source on Linux or Mac OS X¶
All OpenMC source code is hosted on GitHub. If you have git, the gcc compiler suite, CMake, and HDF5 installed, you can download and install OpenMC be entering the following commands in a terminal:
git clone --recurse-submodules https://github.com/openmc-dev/openmc.git
cd openmc
mkdir build && cd build
cmake ..
make
sudo make install
This will build an executable named openmc
and install it (by default in
/usr/local/bin). If you do not have administrator privileges, the cmake command
should specify an installation directory where you have write access, e.g.
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local ..
The openmc
Python package must be installed separately. The easiest way
to install it is using pip, which is
included by default in Python 3.4+. From the root directory of the OpenMC
distribution/repository, run:
pip install .
If you want to build a parallel version of OpenMC (using OpenMP or MPI), directions can be found in the detailed installation instructions.
Examples¶
The following series of Jupyter Notebooks provide examples for how to use various features of OpenMC by leveraging the Python API.
General Usage¶
Modeling a Pin-Cell¶
This notebook is intended to demonstrate the basic features of the Python API for constructing input files and running OpenMC. In it, we will show how to create a basic reflective pin-cell model that is equivalent to modeling an infinite array of fuel pins. If you have never used OpenMC, this can serve as a good starting point to learn the Python API. We highly recommend having a copy of the Python API reference documentation open in another browser tab that you can refer to.
[1]:
%matplotlib inline
import openmc
Defining Materials¶
Materials in OpenMC are defined as a set of nuclides with specified atom/weight fractions. To begin, we will create a material by making an instance of the Material
class. In OpenMC, many objects, including materials, are identified by a “unique ID” that is simply just a positive integer. These IDs are used when exporting XML files that the solver reads in. They also appear in the output and can be used for identification. Since an integer ID is not very useful by itself, you can also give a
material a name
as well.
[2]:
uo2 = openmc.Material(1, "uo2")
print(uo2)
Material
ID = 1
Name = uo2
Temperature = None
Density = None [sum]
S(a,b) Tables
Nuclides
On the XML side, you have no choice but to supply an ID. However, in the Python API, if you don’t give an ID, one will be automatically generated for you:
[3]:
mat = openmc.Material()
print(mat)
Material
ID = 2
Name =
Temperature = None
Density = None [sum]
S(a,b) Tables
Nuclides
We see that an ID of 2 was automatically assigned. Let’s now move on to adding nuclides to our uo2
material. The Material
object has a method add_nuclide()
whose first argument is the name of the nuclide and second argument is the atom or weight fraction.
[4]:
help(uo2.add_nuclide)
Help on method add_nuclide in module openmc.material:
add_nuclide(nuclide, percent, percent_type='ao') method of openmc.material.Material instance
Add a nuclide to the material
Parameters
----------
nuclide : str
Nuclide to add, e.g., 'Mo95'
percent : float
Atom or weight percent
percent_type : {'ao', 'wo'}
'ao' for atom percent and 'wo' for weight percent
We see that by default it assumes we want an atom fraction.
[5]:
# Add nuclides to uo2
uo2.add_nuclide('U235', 0.03)
uo2.add_nuclide('U238', 0.97)
uo2.add_nuclide('O16', 2.0)
Now we need to assign a total density to the material. We’ll use the set_density
for this.
[6]:
uo2.set_density('g/cm3', 10.0)
You may sometimes be given a material specification where all the nuclide densities are in units of atom/b-cm. In this case, you just want the density to be the sum of the constituents. In that case, you can simply run mat.set_density('sum')
.
With UO2 finished, let’s now create materials for the clad and coolant. Note the use of add_element()
for zirconium.
[7]:
zirconium = openmc.Material(name="zirconium")
zirconium.add_element('Zr', 1.0)
zirconium.set_density('g/cm3', 6.6)
water = openmc.Material(name="h2o")
water.add_nuclide('H1', 2.0)
water.add_nuclide('O16', 1.0)
water.set_density('g/cm3', 1.0)
An astute observer might now point out that this water material we just created will only use free-atom cross sections. We need to tell it to use an \(S(\alpha,\beta)\) table so that the bound atom cross section is used at thermal energies. To do this, there’s an add_s_alpha_beta()
method. Note the use of the GND-style name “c_H_in_H2O”.
[8]:
water.add_s_alpha_beta('c_H_in_H2O')
When we go to run the transport solver in OpenMC, it is going to look for a materials.xml
file. Thus far, we have only created objects in memory. To actually create a materials.xml
file, we need to instantiate a Materials
collection and export it to XML.
[9]:
materials = openmc.Materials([uo2, zirconium, water])
Note that Materials
is actually a subclass of Python’s built-in list
, so we can use methods like append()
, insert()
, pop()
, etc.
[10]:
materials = openmc.Materials()
materials.append(uo2)
materials += [zirconium, water]
isinstance(materials, list)
[10]:
True
Finally, we can create the XML file with the export_to_xml()
method. In a Jupyter notebook, we can run a shell command by putting !
before it, so in this case we are going to display the materials.xml
file that we created.
[11]:
materials.export_to_xml()
!cat materials.xml
<?xml version='1.0' encoding='utf-8'?>
<materials>
<material depletable="true" id="1" name="uo2">
<density units="g/cm3" value="10.0" />
<nuclide ao="0.03" name="U235" />
<nuclide ao="0.97" name="U238" />
<nuclide ao="2.0" name="O16" />
</material>
<material id="3" name="zirconium">
<density units="g/cm3" value="6.6" />
<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="4" name="h2o">
<density units="g/cm3" value="1.0" />
<nuclide ao="2.0" name="H1" />
<nuclide ao="1.0" name="O16" />
<sab name="c_H_in_H2O" />
</material>
</materials>
Element Expansion¶
Did you notice something really cool that happened to our Zr element? OpenMC automatically turned it into a list of nuclides when it exported it! The way this feature works is as follows:
- First, it checks whether
Materials.cross_sections
has been set, indicating the path to across_sections.xml
file. - If
Materials.cross_sections
isn’t set, it looks for theOPENMC_CROSS_SECTIONS
environment variable. - If either of these are found, it scans the file to see what nuclides are actually available and will expand elements accordingly.
Let’s see what happens if we change O16 in water to elemental O.
[12]:
water.remove_nuclide('O16')
water.add_element('O', 1.0)
materials.export_to_xml()
!cat materials.xml
<?xml version='1.0' encoding='utf-8'?>
<materials>
<material depletable="true" id="1" name="uo2">
<density units="g/cm3" value="10.0" />
<nuclide ao="0.03" name="U235" />
<nuclide ao="0.97" name="U238" />
<nuclide ao="2.0" name="O16" />
</material>
<material id="3" name="zirconium">
<density units="g/cm3" value="6.6" />
<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="4" name="h2o">
<density units="g/cm3" value="1.0" />
<nuclide ao="2.0" name="H1" />
<nuclide ao="0.999621" name="O16" />
<nuclide ao="0.000379" name="O17" />
<sab name="c_H_in_H2O" />
</material>
</materials>
We see that now O16 and O17 were automatically added. O18 is missing because our cross sections file (which is based on ENDF/B-VII.1) doesn’t have O18. If OpenMC didn’t know about the cross sections file, it would have assumed that all isotopes exist.
The cross_sections.xml
file¶
The cross_sections.xml
tells OpenMC where it can find nuclide cross sections and \(S(\alpha,\beta)\) tables. It serves the same purpose as MCNP’s xsdir
file and Serpent’s xsdata
file. As we mentioned, this can be set either by the OPENMC_CROSS_SECTIONS
environment variable or the Materials.cross_sections
attribute.
Let’s have a look at what’s inside this file:
[13]:
!cat $OPENMC_CROSS_SECTIONS | head -n 10
print(' ...')
!cat $OPENMC_CROSS_SECTIONS | tail -n 10
<?xml version='1.0' encoding='utf-8'?>
<cross_sections>
<library materials="H1" path="H1.h5" type="neutron" />
<library materials="H2" path="H2.h5" type="neutron" />
<library materials="H3" path="H3.h5" type="neutron" />
<library materials="He3" path="He3.h5" type="neutron" />
<library materials="He4" path="He4.h5" type="neutron" />
<library materials="Li6" path="Li6.h5" type="neutron" />
<library materials="Li7" path="Li7.h5" type="neutron" />
<library materials="Be7" path="Be7.h5" type="neutron" />
...
<library materials="Cf253" path="wmp/098253.h5" type="wmp" />
<library materials="Cf254" path="wmp/098254.h5" type="wmp" />
<library materials="Es251" path="wmp/099251.h5" type="wmp" />
<library materials="Es252" path="wmp/099252.h5" type="wmp" />
<library materials="Es253" path="wmp/099253.h5" type="wmp" />
<library materials="Es254" path="wmp/099254.h5" type="wmp" />
<library materials="Es254_m1" path="wmp/099254m1.h5" type="wmp" />
<library materials="Es255" path="wmp/099255.h5" type="wmp" />
<library materials="Fm255" path="wmp/100255.h5" type="wmp" />
</cross_sections>
Enrichment¶
Note that the add_element()
method has a special argument enrichment
that can be used for Uranium. For example, if we know that we want to create 3% enriched UO2, the following would work:
[14]:
uo2_three = openmc.Material()
uo2_three.add_element('U', 1.0, enrichment=3.0)
uo2_three.add_element('O', 2.0)
uo2_three.set_density('g/cc', 10.0)
Mixtures¶
In OpenMC it is also possible to define materials by mixing existing materials. For example, if we wanted to create MOX fuel out of a mixture of UO2 (97 wt%) and PuO2 (3 wt%) we could do the following:
[15]:
# Create PuO2 material
puo2 = openmc.Material()
puo2.add_nuclide('Pu239', 0.94)
puo2.add_nuclide('Pu240', 0.06)
puo2.add_nuclide('O16', 2.0)
puo2.set_density('g/cm3', 11.5)
# Create the mixture
mox = openmc.Material.mix_materials([uo2, puo2], [0.97, 0.03], 'wo')
The ‘wo’ argument in the mix_materials()
method specifies that the fractions are weight fractions. Materials can also be mixed by atomic and volume fractions with ‘ao’ and ‘vo’, respectively. For ‘ao’ and ‘wo’ the fractions must sum to one. For ‘vo’, if fractions do not sum to one, the remaining fraction is set as void.
Defining Geometry¶
At this point, we have three materials defined, exported to XML, and ready to be used in our model. To finish our model, we need to define the geometric arrangement of materials. OpenMC represents physical volumes using constructive solid geometry (CSG), also known as combinatorial geometry. The object that allows us to assign a material to a region of space is called a Cell
(same concept in MCNP, for those familiar). In order to define a region that we can assign to a cell, we must first
define surfaces which bound the region. A surface is a locus of zeros of a function of Cartesian coordinates \(x\), \(y\), and \(z\), e.g.
- A plane perpendicular to the x axis: \(x - x_0 = 0\)
- A cylinder parallel to the z axis: \((x - x_0)^2 + (y - y_0)^2 - R^2 = 0\)
- A sphere: \((x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\)
Between those three classes of surfaces (planes, cylinders, spheres), one can construct a wide variety of models. It is also possible to define cones and general second-order surfaces (tori are not currently supported).
Note that defining a surface is not sufficient to specify a volume – in order to define an actual volume, one must reference the half-space of a surface. A surface half-space is the region whose points satisfy a positive or negative inequality of the surface equation. For example, for a sphere of radius one centered at the origin, the surface equation is \(f(x,y,z) = x^2 + y^2 + z^2 - 1 = 0\). Thus, we say that the negative half-space of the sphere, is defined as the collection of points satisfying \(f(x,y,z) < 0\), which one can reason is the inside of the sphere. Conversely, the positive half-space of the sphere would correspond to all points outside of the sphere.
Let’s go ahead and create a sphere and confirm that what we’ve told you is true.
[16]:
sphere = openmc.Sphere(r=1.0)
Note that by default the sphere is centered at the origin so we didn’t have to supply x0
, y0
, or z0
arguments. Strictly speaking, we could have omitted R
as well since it defaults to one. To get the negative or positive half-space, we simply need to apply the -
or +
unary operators, respectively.
(NOTE: Those unary operators are defined by special methods: __pos__
and __neg__
in this case).
[17]:
inside_sphere = -sphere
outside_sphere = +sphere
Now let’s see if inside_sphere
actually contains points inside the sphere:
[18]:
print((0,0,0) in inside_sphere, (0,0,2) in inside_sphere)
print((0,0,0) in outside_sphere, (0,0,2) in outside_sphere)
True False
False True
Everything works as expected! Now that we understand how to create half-spaces, we can create more complex volumes by combining half-spaces using Boolean operators: &
(intersection), |
(union), and ~
(complement). For example, let’s say we want to define a region that is the top part of the sphere (all points inside the sphere that have \(z > 0\).
[19]:
z_plane = openmc.ZPlane(z0=0)
northern_hemisphere = -sphere & +z_plane
For many regions, OpenMC can automatically determine a bounding box. To get the bounding box, we use the bounding_box
property of a region, which returns a tuple of the lower-left and upper-right Cartesian coordinates for the bounding box:
[20]:
northern_hemisphere.bounding_box
[20]:
(array([-1., -1., 0.]), array([1., 1., 1.]))
Now that we see how to create volumes, we can use them to create a cell.
[21]:
cell = openmc.Cell()
cell.region = northern_hemisphere
# or...
cell = openmc.Cell(region=northern_hemisphere)
By default, the cell is not filled by any material (void). In order to assign a material, we set the fill
property of a Cell
.
[22]:
cell.fill = water
Universes and in-line plotting¶
A collection of cells is known as a universe (again, this will be familiar to MCNP/Serpent users) and can be used as a repeatable unit when creating a model. Although we don’t need it yet, the benefit of creating a universe is that we can visualize our geometry while we’re creating it.
[23]:
universe = openmc.Universe()
universe.add_cell(cell)
# this also works
universe = openmc.Universe(cells=[cell])
The Universe
object has a plot
method that will display our the universe as current constructed:
[24]:
universe.plot(width=(2.0, 2.0))
[24]:
<matplotlib.image.AxesImage at 0x7f7df827b250>

By default, the plot will appear in the \(x\)-\(y\) plane. We can change that with the basis
argument.
[25]:
universe.plot(width=(2.0, 2.0), basis='xz')
[25]:
<matplotlib.image.AxesImage at 0x7f7df8112100>

If we have particular fondness for, say, fuchsia, we can tell the plot()
method to make our cell that color.
[26]:
universe.plot(width=(2.0, 2.0), basis='xz',
colors={cell: 'fuchsia'})
[26]:
<matplotlib.image.AxesImage at 0x7f7df80edd30>

Pin cell geometry¶
We now have enough knowledge to create our pin-cell. We need three surfaces to define the fuel and clad:
- The outer surface of the fuel – a cylinder parallel to the z axis
- The inner surface of the clad – same as above
- The outer surface of the clad – same as above
These three surfaces will all be instances of openmc.ZCylinder
, each with a different radius according to the specification.
[27]:
fuel_outer_radius = openmc.ZCylinder(r=0.39)
clad_inner_radius = openmc.ZCylinder(r=0.40)
clad_outer_radius = openmc.ZCylinder(r=0.46)
With the surfaces created, we can now take advantage of the built-in operators on surfaces to create regions for the fuel, the gap, and the clad:
[28]:
fuel_region = -fuel_outer_radius
gap_region = +fuel_outer_radius & -clad_inner_radius
clad_region = +clad_inner_radius & -clad_outer_radius
Now we can create corresponding cells that assign materials to these regions. As with materials, cells have unique IDs that are assigned either manually or automatically. Note that the gap cell doesn’t have any material assigned (it is void by default).
[29]:
fuel = openmc.Cell(name='fuel')
fuel.fill = uo2
fuel.region = fuel_region
gap = openmc.Cell(name='air gap')
gap.region = gap_region
clad = openmc.Cell(name='clad')
clad.fill = zirconium
clad.region = clad_region
Finally, we need to handle the coolant outside of our fuel pin. To do this, we create x- and y-planes that bound the geometry.
[30]:
pitch = 1.26
left = openmc.XPlane(x0=-pitch/2, boundary_type='reflective')
right = openmc.XPlane(x0=pitch/2, boundary_type='reflective')
bottom = openmc.YPlane(y0=-pitch/2, boundary_type='reflective')
top = openmc.YPlane(y0=pitch/2, boundary_type='reflective')
The water region is going to be everything outside of the clad outer radius and within the box formed as the intersection of four half-spaces.
[31]:
water_region = +left & -right & +bottom & -top & +clad_outer_radius
moderator = openmc.Cell(name='moderator')
moderator.fill = water
moderator.region = water_region
OpenMC also includes a factory function that generates a rectangular prism that could have made our lives easier.
[32]:
box = openmc.rectangular_prism(width=pitch, height=pitch,
boundary_type='reflective')
type(box)
[32]:
openmc.region.Intersection
Pay attention here – the object that was returned is NOT a surface. It is actually the intersection of four surface half-spaces, just like we created manually before. Thus, we don’t need to apply the unary operator (-box
). Instead, we can directly combine it with +clad_or
.
[33]:
water_region = box & +clad_outer_radius
The final step is to assign the cells we created to a universe and tell OpenMC that this universe is the “root” universe in our geometry. The Geometry
is the final object that is actually exported to XML.
[34]:
root_universe = openmc.Universe(cells=(fuel, gap, clad, moderator))
geometry = openmc.Geometry()
geometry.root_universe = root_universe
# or...
geometry = openmc.Geometry(root_universe)
geometry.export_to_xml()
!cat geometry.xml
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<cell id="3" material="1" name="fuel" region="-3" universe="3" />
<cell id="4" material="void" name="air gap" region="3 -4" universe="3" />
<cell id="5" material="3" name="clad" region="4 -5" universe="3" />
<cell id="6" material="4" name="moderator" region="6 -7 8 -9 5" universe="3" />
<surface coeffs="0.0 0.0 0.39" id="3" type="z-cylinder" />
<surface coeffs="0.0 0.0 0.4" id="4" type="z-cylinder" />
<surface coeffs="0.0 0.0 0.46" id="5" type="z-cylinder" />
<surface boundary="reflective" coeffs="-0.63" id="6" type="x-plane" />
<surface boundary="reflective" coeffs="0.63" id="7" type="x-plane" />
<surface boundary="reflective" coeffs="-0.63" id="8" type="y-plane" />
<surface boundary="reflective" coeffs="0.63" id="9" type="y-plane" />
</geometry>
Starting source and settings¶
The Python API has a module openmc.stats
with various univariate and multivariate probability distributions. We can use these distributions to create a starting source using the openmc.Source
object.
[35]:
# Create a point source
point = openmc.stats.Point((0, 0, 0))
source = openmc.Source(space=point)
Now let’s create a Settings
object and give it the source we created along with specifying how many batches and particles we want to run.
[36]:
settings = openmc.Settings()
settings.source = source
settings.batches = 100
settings.inactive = 10
settings.particles = 1000
[37]:
settings.export_to_xml()
!cat settings.xml
<?xml version='1.0' encoding='utf-8'?>
<settings>
<run_mode>eigenvalue</run_mode>
<particles>1000</particles>
<batches>100</batches>
<inactive>10</inactive>
<source strength="1.0">
<space type="point">
<parameters>0 0 0</parameters>
</space>
</source>
</settings>
User-defined tallies¶
We actually have all the required files needed to run a simulation. Before we do that though, let’s give a quick example of how to create tallies. We will show how one would tally the total, fission, absorption, and (n,:math:gamma) reaction rates for \(^{235}\)U in the cell containing fuel. Recall that filters allow us to specify where in phase-space we want events to be tallied and scores tell us what we want to tally:
In this case, the where is “the fuel cell”. So, we will create a cell filter specifying the fuel cell.
[38]:
cell_filter = openmc.CellFilter(fuel)
tally = openmc.Tally(1)
tally.filters = [cell_filter]
The what is the total, fission, absorption, and (n,:math:gamma) reaction rates in \(^{235}\)U. By default, if we only specify what reactions, it will gives us tallies over all nuclides. We can use the nuclides
attribute to name specific nuclides we’re interested in.
[39]:
tally.nuclides = ['U235']
tally.scores = ['total', 'fission', 'absorption', '(n,gamma)']
Similar to the other files, we need to create a Tallies
collection and export it to XML.
[40]:
tallies = openmc.Tallies([tally])
tallies.export_to_xml()
!cat tallies.xml
<?xml version='1.0' encoding='utf-8'?>
<tallies>
<filter id="1" type="cell">
<bins>3</bins>
</filter>
<tally id="1">
<filters>1</filters>
<nuclides>U235</nuclides>
<scores>total fission absorption (n,gamma)</scores>
</tally>
</tallies>
Running OpenMC¶
Running OpenMC from Python can be done using the openmc.run()
function. This function allows you to set the number of MPI processes and OpenMP threads, if need be.
[41]:
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2020 MIT and OpenMC contributors
License | https://docs.openmc.org/en/latest/license.html
Version | 0.12.0
Git SHA1 | 3d90a9f857ec72eae897e054d4225180f1fa4d93
Date/Time | 2020-08-25 14:58:51
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /home/master/data/nuclear/endfb71_hdf5/U235.h5
Reading U238 from /home/master/data/nuclear/endfb71_hdf5/U238.h5
Reading O16 from /home/master/data/nuclear/endfb71_hdf5/O16.h5
Reading Zr90 from /home/master/data/nuclear/endfb71_hdf5/Zr90.h5
Reading Zr91 from /home/master/data/nuclear/endfb71_hdf5/Zr91.h5
Reading Zr92 from /home/master/data/nuclear/endfb71_hdf5/Zr92.h5
Reading Zr94 from /home/master/data/nuclear/endfb71_hdf5/Zr94.h5
Reading Zr96 from /home/master/data/nuclear/endfb71_hdf5/Zr96.h5
Reading H1 from /home/master/data/nuclear/endfb71_hdf5/H1.h5
Reading O17 from /home/master/data/nuclear/endfb71_hdf5/O17.h5
Reading c_H_in_H2O from /home/master/data/nuclear/endfb71_hdf5/c_H_in_H2O.h5
Minimum neutron data temperature: 294.000000 K
Maximum neutron data temperature: 294.000000 K
Reading tallies XML file...
Preparing distributed cell instances...
Writing summary.h5 file...
Maximum neutron transport energy: 20000000.000000 eV for U235
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.42066
2/1 1.39831
3/1 1.46207
4/1 1.44888
5/1 1.42595
6/1 1.35549
7/1 1.36717
8/1 1.45095
9/1 1.36061
10/1 1.36554
11/1 1.36973
12/1 1.44276 1.40625 +/- 0.03652
13/1 1.35512 1.38920 +/- 0.02711
14/1 1.54216 1.42744 +/- 0.04277
15/1 1.39353 1.42066 +/- 0.03382
16/1 1.38650 1.41497 +/- 0.02820
17/1 1.38760 1.41106 +/- 0.02415
18/1 1.38413 1.40769 +/- 0.02118
19/1 1.39088 1.40582 +/- 0.01877
20/1 1.47468 1.41271 +/- 0.01815
21/1 1.45695 1.41673 +/- 0.01690
22/1 1.40308 1.41559 +/- 0.01547
23/1 1.40821 1.41503 +/- 0.01424
24/1 1.32301 1.40845 +/- 0.01473
25/1 1.36702 1.40569 +/- 0.01399
26/1 1.30968 1.39969 +/- 0.01440
27/1 1.38099 1.39859 +/- 0.01357
28/1 1.42103 1.39984 +/- 0.01285
29/1 1.39741 1.39971 +/- 0.01216
30/1 1.36548 1.39800 +/- 0.01166
31/1 1.41573 1.39884 +/- 0.01112
32/1 1.39788 1.39880 +/- 0.01061
33/1 1.35942 1.39709 +/- 0.01028
34/1 1.40483 1.39741 +/- 0.00985
35/1 1.39418 1.39728 +/- 0.00944
36/1 1.41492 1.39796 +/- 0.00910
37/1 1.49392 1.40151 +/- 0.00945
38/1 1.45114 1.40329 +/- 0.00928
39/1 1.42619 1.40408 +/- 0.00899
40/1 1.35249 1.40236 +/- 0.00885
41/1 1.35401 1.40080 +/- 0.00870
42/1 1.40220 1.40084 +/- 0.00842
43/1 1.36437 1.39974 +/- 0.00824
44/1 1.33642 1.39787 +/- 0.00821
45/1 1.36953 1.39706 +/- 0.00801
46/1 1.30034 1.39438 +/- 0.00824
47/1 1.44097 1.39564 +/- 0.00811
48/1 1.37981 1.39522 +/- 0.00790
49/1 1.34870 1.39403 +/- 0.00779
50/1 1.41247 1.39449 +/- 0.00761
51/1 1.33382 1.39301 +/- 0.00756
52/1 1.37043 1.39247 +/- 0.00740
53/1 1.38754 1.39236 +/- 0.00723
54/1 1.40160 1.39257 +/- 0.00707
55/1 1.37511 1.39218 +/- 0.00692
56/1 1.38589 1.39204 +/- 0.00677
57/1 1.40630 1.39234 +/- 0.00663
58/1 1.29944 1.39041 +/- 0.00677
59/1 1.40019 1.39061 +/- 0.00663
60/1 1.42384 1.39127 +/- 0.00653
61/1 1.36502 1.39076 +/- 0.00643
62/1 1.37042 1.39037 +/- 0.00631
63/1 1.42295 1.39098 +/- 0.00622
64/1 1.40042 1.39116 +/- 0.00611
65/1 1.36382 1.39066 +/- 0.00602
66/1 1.31659 1.38934 +/- 0.00606
67/1 1.36101 1.38884 +/- 0.00597
68/1 1.46359 1.39013 +/- 0.00601
69/1 1.41012 1.39047 +/- 0.00591
70/1 1.27411 1.38853 +/- 0.00613
71/1 1.45399 1.38960 +/- 0.00612
72/1 1.40455 1.38984 +/- 0.00603
73/1 1.33020 1.38890 +/- 0.00601
74/1 1.44599 1.38979 +/- 0.00598
75/1 1.34985 1.38917 +/- 0.00592
76/1 1.36183 1.38876 +/- 0.00584
77/1 1.41080 1.38909 +/- 0.00576
78/1 1.43991 1.38984 +/- 0.00573
79/1 1.35613 1.38935 +/- 0.00566
80/1 1.31659 1.38831 +/- 0.00568
81/1 1.51344 1.39007 +/- 0.00587
82/1 1.38404 1.38999 +/- 0.00579
83/1 1.39613 1.39007 +/- 0.00571
84/1 1.43037 1.39061 +/- 0.00566
85/1 1.47316 1.39172 +/- 0.00569
86/1 1.39220 1.39172 +/- 0.00561
87/1 1.44400 1.39240 +/- 0.00558
88/1 1.42419 1.39281 +/- 0.00552
89/1 1.30930 1.39175 +/- 0.00556
90/1 1.46976 1.39273 +/- 0.00557
91/1 1.38334 1.39261 +/- 0.00550
92/1 1.35260 1.39212 +/- 0.00546
93/1 1.38505 1.39204 +/- 0.00539
94/1 1.38290 1.39193 +/- 0.00533
95/1 1.42597 1.39233 +/- 0.00528
96/1 1.41624 1.39261 +/- 0.00523
97/1 1.42053 1.39293 +/- 0.00518
98/1 1.36268 1.39258 +/- 0.00513
99/1 1.39175 1.39258 +/- 0.00507
100/1 1.38148 1.39245 +/- 0.00502
Creating state point statepoint.100.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 6.9022e-01 seconds
Reading cross sections = 6.7913e-01 seconds
Total time in simulation = 1.7892e+00 seconds
Time in transport only = 1.7650e+00 seconds
Time in inactive batches = 1.5005e-01 seconds
Time in active batches = 1.6391e+00 seconds
Time synchronizing fission bank = 4.2308e-03 seconds
Sampling source sites = 3.4593e-03 seconds
SEND/RECV source sites = 6.2601e-04 seconds
Time accumulating tallies = 9.5555e-05 seconds
Total time for finalization = 7.4948e-05 seconds
Total time elapsed = 2.4836e+00 seconds
Calculation Rate (inactive) = 66645.8 particles/second
Calculation Rate (active) = 54907.5 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.39516 +/- 0.00457
k-effective (Track-length) = 1.39245 +/- 0.00502
k-effective (Absorption) = 1.40443 +/- 0.00333
Combined k-effective = 1.40145 +/- 0.00319
Leakage Fraction = 0.00000 +/- 0.00000
Great! OpenMC already told us our k-effective. It also spit out a file called tallies.out
that shows our tallies. This is a very basic method to look at tally data; for more sophisticated methods, see other example notebooks.
[42]:
!cat tallies.out
============================> TALLY 1 <============================
Cell 3
U235
Total Reaction Rate 0.726151 +/- 0.00251702
Fission Rate 0.543836 +/- 0.00205084
Absorption Rate 0.652874 +/- 0.002424
(n,gamma) 0.10904 +/- 0.000385793
Geometry plotting¶
We saw before that we could call the Universe.plot()
method to show a universe while we were creating our geometry. There is also a built-in plotter in the codebase that is much faster than the Python plotter and has more options. The interface looks somewhat similar to the Universe.plot()
method. Instead though, we create Plot
instances, assign them to a Plots
collection, export it to XML, and then run OpenMC in geometry plotting mode. As an example, let’s specify that we want
the plot to be colored by material (rather than by cell) and we assign yellow to fuel and blue to water.
[43]:
plot = openmc.Plot()
plot.filename = 'pinplot'
plot.width = (pitch, pitch)
plot.pixels = (200, 200)
plot.color_by = 'material'
plot.colors = {uo2: 'yellow', water: 'blue'}
With our plot created, we need to add it to a Plots
collection which can be exported to XML.
[44]:
plots = openmc.Plots([plot])
plots.export_to_xml()
!cat plots.xml
<?xml version='1.0' encoding='utf-8'?>
<plots>
<plot basis="xy" color_by="material" filename="pinplot" id="1" type="slice">
<origin>0.0 0.0 0.0</origin>
<width>1.26 1.26</width>
<pixels>200 200</pixels>
<color id="1" rgb="255 255 0" />
<color id="4" rgb="0 0 255" />
</plot>
</plots>
Now we can run OpenMC in plotting mode by calling the plot_geometry()
function. Under the hood this is calling openmc --plot
.
[45]:
openmc.plot_geometry()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2020 MIT and OpenMC contributors
License | https://docs.openmc.org/en/latest/license.html
Version | 0.12.0
Git SHA1 | 3d90a9f857ec72eae897e054d4225180f1fa4d93
Date/Time | 2020-08-25 14:58:54
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading tallies XML file...
Preparing distributed cell instances...
Reading plot XML file...
=======================> PLOTTING SUMMARY <========================
Plot ID: 1
Plot file: pinplot.ppm
Universe depth: -1
Plot Type: Slice
Origin: 0.0 0.0 0.0
Width: 1.26 1.26
Coloring: Materials
Basis: XY
Pixels: 200 200
Processing plot 1: pinplot.ppm...
OpenMC writes out a peculiar image with a .ppm
extension. If you have ImageMagick installed, this can be converted into a more normal .png
file.
[46]:
!convert pinplot.ppm pinplot.png
We can use functionality from IPython to display the image inline in our notebook:
[47]:
from IPython.display import Image
Image("pinplot.png")
[47]:

That was a little bit cumbersome. Thankfully, OpenMC provides us with a method on the Plot
class that does all that “boilerplate” work.
[48]:
plot.to_ipython_image()
[48]:

Post Processing¶
This notebook demonstrates some basic post-processing tasks that can be performed with the Python API, such as plotting a 2D mesh tally and plotting neutron source sites from an eigenvalue calculation. The problem we will use is a simple reflected pin-cell.
[1]:
%matplotlib inline
from IPython.display import Image
import numpy as np
import matplotlib.pyplot as plt
import openmc
Generate Input Files¶
First we need to define materials that will be used in the problem. We’ll create three materials for the fuel, water, and cladding of the fuel pin.
[2]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
water.add_nuclide('B10', 8.0042e-6)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our three materials, we can now create a materials file object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials collection
materials = openmc.Materials([fuel, water, zircaloy])
# Export to "materials.xml"
materials.export_to_xml()
Now let’s move on to the geometry. Our problem will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces – in this case two cylinders and six reflective planes.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.45720)
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-0.63, boundary_type='reflective')
max_x = openmc.XPlane(x0=+0.63, boundary_type='reflective')
min_y = openmc.YPlane(y0=-0.63, boundary_type='reflective')
max_y = openmc.YPlane(y0=+0.63, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-0.63, boundary_type='reflective')
max_z = openmc.ZPlane(z0=+0.63, boundary_type='reflective')
With the surfaces defined, we can now create cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
pin_cell_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
pin_cell_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
pin_cell_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
pin_cell_universe.add_cell(moderator_cell)
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe.
[6]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = pin_cell_universe
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(universe_id=0, name='root universe')
root_universe.add_cell(root_cell)
We now must create a geometry that is assigned a root universe, put the geometry into a geometry file, and export it to XML.
[7]:
# Create Geometry and set root Universe
geometry = openmc.Geometry(root_universe)
[8]:
# Export to "geometry.xml"
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 10 inactive batches and 90 active batches each with 5000 particles.
[9]:
# OpenMC simulation parameters
settings = openmc.Settings()
settings.batches = 100
settings.inactive = 10
settings.particles = 5000
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -0.63, 0.63, 0.63, 0.63]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings.export_to_xml()
Let us also create a plot file that we can use to verify that our pin cell geometry was created successfully.
[10]:
plot = openmc.Plot.from_geometry(geometry)
plot.pixels = (250, 250)
plot.to_ipython_image()
[10]:

As we can see from the plot, we have a nice pin cell with fuel, cladding, and water! Before we run our simulation, we need to tell the code what we want to tally. The following code shows how to create a 2D mesh tally.
[11]:
# Instantiate an empty Tallies object
tallies = openmc.Tallies()
[12]:
# Create mesh which will be used for tally
mesh = openmc.RegularMesh()
mesh.dimension = [100, 100]
mesh.lower_left = [-0.63, -0.63]
mesh.upper_right = [0.63, 0.63]
# Create mesh filter for tally
mesh_filter = openmc.MeshFilter(mesh)
# Create mesh tally to score flux and fission rate
tally = openmc.Tally(name='flux')
tally.filters = [mesh_filter]
tally.scores = ['flux', 'fission']
tallies.append(tally)
[13]:
# Export to "tallies.xml"
tallies.export_to_xml()
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[14]:
# Run OpenMC!
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-19 06:22:24
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading B10 from /opt/data/hdf5/nndc_hdf5_v15/B10.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.04359
2/1 1.04323
3/1 1.04711
4/1 1.03892
5/1 1.02459
6/1 1.03936
7/1 1.03529
8/1 1.01590
9/1 1.03060
10/1 1.02892
11/1 1.03987
12/1 1.04395 1.04191 +/- 0.00204
13/1 1.04971 1.04451 +/- 0.00285
14/1 1.03880 1.04308 +/- 0.00247
15/1 1.03091 1.04065 +/- 0.00310
16/1 1.03618 1.03990 +/- 0.00264
17/1 1.04109 1.04007 +/- 0.00223
18/1 1.02978 1.03879 +/- 0.00232
19/1 1.06363 1.04155 +/- 0.00344
20/1 1.06549 1.04394 +/- 0.00390
21/1 1.03469 1.04310 +/- 0.00362
22/1 1.01925 1.04111 +/- 0.00386
23/1 1.03268 1.04046 +/- 0.00361
24/1 1.03906 1.04036 +/- 0.00334
25/1 1.02632 1.03943 +/- 0.00325
26/1 1.03906 1.03940 +/- 0.00304
27/1 1.05058 1.04006 +/- 0.00293
28/1 1.03248 1.03964 +/- 0.00279
29/1 1.04076 1.03970 +/- 0.00264
30/1 1.00994 1.03821 +/- 0.00292
31/1 1.04785 1.03867 +/- 0.00281
32/1 1.03080 1.03831 +/- 0.00270
33/1 1.01862 1.03746 +/- 0.00272
34/1 1.05370 1.03813 +/- 0.00269
35/1 1.02226 1.03750 +/- 0.00266
36/1 1.02862 1.03716 +/- 0.00258
37/1 1.04790 1.03755 +/- 0.00251
38/1 1.03762 1.03756 +/- 0.00242
39/1 1.02255 1.03704 +/- 0.00239
40/1 1.06094 1.03784 +/- 0.00245
41/1 1.03842 1.03786 +/- 0.00237
42/1 1.00628 1.03687 +/- 0.00249
43/1 1.04916 1.03724 +/- 0.00245
44/1 1.06237 1.03798 +/- 0.00248
45/1 1.08153 1.03922 +/- 0.00271
46/1 1.05649 1.03970 +/- 0.00268
47/1 1.06265 1.04032 +/- 0.00268
48/1 1.05728 1.04077 +/- 0.00265
49/1 1.07343 1.04161 +/- 0.00271
50/1 1.04640 1.04173 +/- 0.00265
51/1 1.05143 1.04196 +/- 0.00259
52/1 1.03639 1.04183 +/- 0.00253
53/1 1.04846 1.04199 +/- 0.00248
54/1 1.02435 1.04158 +/- 0.00245
55/1 1.04806 1.04173 +/- 0.00240
56/1 1.04798 1.04186 +/- 0.00235
57/1 1.06621 1.04238 +/- 0.00236
58/1 1.05734 1.04269 +/- 0.00233
59/1 1.04581 1.04276 +/- 0.00228
60/1 1.02682 1.04244 +/- 0.00226
61/1 1.05971 1.04278 +/- 0.00224
62/1 1.02357 1.04241 +/- 0.00223
63/1 1.02645 1.04211 +/- 0.00221
64/1 1.00711 1.04146 +/- 0.00226
65/1 1.06171 1.04183 +/- 0.00225
66/1 1.03444 1.04170 +/- 0.00221
67/1 1.05875 1.04199 +/- 0.00219
68/1 1.04640 1.04207 +/- 0.00216
69/1 1.04376 1.04210 +/- 0.00212
70/1 1.07078 1.04258 +/- 0.00214
71/1 1.03916 1.04252 +/- 0.00210
72/1 1.01843 1.04213 +/- 0.00211
73/1 1.03666 1.04205 +/- 0.00207
74/1 1.04625 1.04211 +/- 0.00204
75/1 1.05277 1.04228 +/- 0.00202
76/1 1.04944 1.04238 +/- 0.00199
77/1 1.01898 1.04203 +/- 0.00199
78/1 1.03283 1.04190 +/- 0.00197
79/1 1.02304 1.04163 +/- 0.00196
80/1 1.01539 1.04125 +/- 0.00196
81/1 1.03988 1.04123 +/- 0.00194
82/1 1.02138 1.04096 +/- 0.00193
83/1 1.02473 1.04073 +/- 0.00192
84/1 1.03810 1.04070 +/- 0.00189
85/1 1.07438 1.04115 +/- 0.00192
86/1 1.03048 1.04101 +/- 0.00190
87/1 1.06778 1.04135 +/- 0.00191
88/1 1.07341 1.04177 +/- 0.00192
89/1 1.06729 1.04209 +/- 0.00193
90/1 1.05069 1.04220 +/- 0.00191
91/1 1.07675 1.04262 +/- 0.00193
92/1 1.06470 1.04289 +/- 0.00193
93/1 1.02609 1.04269 +/- 0.00191
94/1 1.04761 1.04275 +/- 0.00189
95/1 1.08802 1.04328 +/- 0.00194
96/1 1.04162 1.04326 +/- 0.00192
97/1 1.04573 1.04329 +/- 0.00190
98/1 1.03232 1.04317 +/- 0.00188
99/1 1.03473 1.04307 +/- 0.00186
100/1 1.04505 1.04309 +/- 0.00184
Creating state point statepoint.100.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 6.4445e-01 seconds
Reading cross sections = 6.1129e-01 seconds
Total time in simulation = 2.0000e+02 seconds
Time in transport only = 1.9970e+02 seconds
Time in inactive batches = 2.9966e+00 seconds
Time in active batches = 1.9701e+02 seconds
Time synchronizing fission bank = 4.0040e-02 seconds
Sampling source sites = 3.1522e-02 seconds
SEND/RECV source sites = 8.3459e-03 seconds
Time accumulating tallies = 9.3582e-03 seconds
Total time for finalization = 4.6582e-02 seconds
Total time elapsed = 2.0072e+02 seconds
Calculation Rate (inactive) = 16685.4 particles/second
Calculation Rate (active) = 2284.19 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.04342 +/- 0.00159
k-effective (Track-length) = 1.04309 +/- 0.00184
k-effective (Absorption) = 1.04107 +/- 0.00140
Combined k-effective = 1.04195 +/- 0.00117
Leakage Fraction = 0.00000 +/- 0.00000
Tally Data Processing¶
Our simulation ran successfully and created a statepoint file with all the tally data in it. We begin our analysis here loading the statepoint file and ‘reading’ the results. By default, data from the statepoint file is only read into memory when it is requested. This helps keep the memory use to a minimum even when a statepoint file may be huge.
[15]:
# Load the statepoint file
sp = openmc.StatePoint('statepoint.100.h5')
Next we need to get the tally, which can be done with the StatePoint.get_tally(...)
method.
[16]:
tally = sp.get_tally(scores=['flux'])
print(tally)
Tally
ID = 1
Name = flux
Filters = MeshFilter
Nuclides = total
Scores = ['flux', 'fission']
Estimator = tracklength
The statepoint file actually stores the sum and sum-of-squares for each tally bin from which the mean and variance can be calculated as described here. The sum and sum-of-squares can be accessed using the sum
and sum_sq
properties:
[17]:
tally.sum
[17]:
array([[[0.40767451, 0. ]],
[[0.40933814, 0. ]],
[[0.4119165 , 0. ]],
...,
[[0.40854327, 0. ]],
[[0.40970805, 0. ]],
[[0.40948065, 0. ]]])
However, the mean and standard deviation of the mean are usually what you are more interested in. The Tally class also has properties mean
and std_dev
which automatically calculate these statistics on-the-fly.
[18]:
print(tally.mean.shape)
(tally.mean, tally.std_dev)
(10000, 1, 2)
[18]:
(array([[[0.00452972, 0. ]],
[[0.0045482 , 0. ]],
[[0.00457685, 0. ]],
...,
[[0.00453937, 0. ]],
[[0.00455231, 0. ]],
[[0.00454978, 0. ]]]),
array([[[2.03553236e-05, 0.00000000e+00]],
[[1.83847389e-05, 0.00000000e+00]],
[[1.68647098e-05, 0.00000000e+00]],
...,
[[1.71606078e-05, 0.00000000e+00]],
[[1.87645811e-05, 0.00000000e+00]],
[[1.94447454e-05, 0.00000000e+00]]]))
The tally data has three dimensions: one for filter combinations, one for nuclides, and one for scores. We see that there are 10000 filter combinations (corresponding to the 100 x 100 mesh bins), a single nuclide (since none was specified), and two scores. If we only want to look at a single score, we can use the get_slice(...)
method as follows.
[19]:
flux = tally.get_slice(scores=['flux'])
fission = tally.get_slice(scores=['fission'])
print(flux)
Tally
ID = 2
Name = flux
Filters = MeshFilter
Nuclides = total
Scores = ['flux']
Estimator = tracklength
To get the bins into a form that we can plot, we can simply change the shape of the array since it is a numpy array.
[20]:
flux.std_dev.shape = (100, 100)
flux.mean.shape = (100, 100)
fission.std_dev.shape = (100, 100)
fission.mean.shape = (100, 100)
[21]:
fig = plt.subplot(121)
fig.imshow(flux.mean)
fig2 = plt.subplot(122)
fig2.imshow(fission.mean)
[21]:
<matplotlib.image.AxesImage at 0x14d12e58cb38>

Now let’s say we want to look at the distribution of relative errors of our tally bins for flux. First we create a new variable called relative_error
and set it to the ratio of the standard deviation and the mean, being careful not to divide by zero in case some bins were never scored to.
[22]:
# Determine relative error
relative_error = np.zeros_like(flux.std_dev)
nonzero = flux.mean > 0
relative_error[nonzero] = flux.std_dev[nonzero] / flux.mean[nonzero]
# distribution of relative errors
ret = plt.hist(relative_error[nonzero], bins=50)

Source Sites¶
Source sites can be accessed from the source
property. As shown below, the source sites are represented as a numpy array with a structured datatype.
[23]:
sp.source
[23]:
array([((-0.28690552, -0.23731283, 0.51447853), ( 0.02705364, -0.14292142, 0.98936422), 1780128.70101981, 1., 0, 0),
((-0.28690552, -0.23731283, 0.51447853), (-0.16786951, 0.86432444, -0.47409186), 1553436.10501094, 1., 0, 0),
(( 0.17162994, 0.134092 , 0.42932363), ( 0.25199134, -0.11168216, 0.96126347), 829530.02360943, 1., 0, 0),
...,
((-0.24444068, -0.01351615, -0.41772172), ( 0.10437178, -0.86754673, 0.486281 ), 807617.55637656, 1., 0, 0),
((-0.2146841 , 0.14307096, 0.07419328), ( 0.89645066, -0.35557279, -0.26446968), 6036005.44157462, 1., 0, 0),
((-0.2146841 , 0.14307096, 0.07419328), (-0.95287644, -0.25857878, 0.15863005), 4923751.04163063, 1., 0, 0)],
dtype=[('r', [('x', '<f8'), ('y', '<f8'), ('z', '<f8')]), ('u', [('x', '<f8'), ('y', '<f8'), ('z', '<f8')]), ('E', '<f8'), ('wgt', '<f8'), ('delayed_group', '<i4'), ('particle', '<i4')])
If we want, say, only the energies from the source sites, we can simply index the source array with the name of the field:
[24]:
sp.source['E']
[24]:
array([1780128.70101981, 1553436.10501094, 829530.02360943, ...,
807617.55637656, 6036005.44157462, 4923751.04163063])
Now, we can look at things like the energy distribution of source sites. Note that we don’t directly use the matplotlib.pyplot.hist
method since our binning is logarithmic.
[25]:
# Create log-spaced energy bins from 1 keV to 10 MeV
energy_bins = np.logspace(3,7)
# Calculate pdf for source energies
probability, bin_edges = np.histogram(sp.source['E'], energy_bins, density=True)
# Make sure integrating the PDF gives us unity
print(sum(probability*np.diff(energy_bins)))
# Plot source energy PDF
plt.semilogx(energy_bins[:-1], probability*np.diff(energy_bins), drawstyle='steps')
plt.xlabel('Energy (eV)')
plt.ylabel('Probability/eV')
0.9999999999999999
[25]:
Text(0, 0.5, 'Probability/eV')

Let’s also look at the spatial distribution of the sites. To make the plot a little more interesting, we can also include the direction of the particle emitted from the source and color each source by the logarithm of its energy.
[26]:
plt.quiver(sp.source['r']['x'], sp.source['r']['y'],
sp.source['u']['x'], sp.source['u']['y'],
np.log(sp.source['E']), cmap='jet', scale=20.0)
plt.colorbar()
plt.xlim((-0.5,0.5))
plt.ylim((-0.5,0.5))
[26]:
(-0.5, 0.5)

Pandas Dataframes¶
This notebook demonstrates how systematic analysis of tally scores is possible using Pandas dataframes. A dataframe can be automatically generated using the Tally.get_pandas_dataframe(...)
method. Furthermore, by linking the tally data in a statepoint file with geometry and material information from a summary file, the dataframe can be shown with user-supplied labels.
[1]:
import glob
from IPython.display import Image
import matplotlib.pyplot as plt
import scipy.stats
import numpy as np
import pandas as pd
import openmc
%matplotlib inline
Generate Input Files¶
First we need to define materials that will be used in the problem. We will create three materials for the fuel, water, and cladding of the fuel pin.
[2]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
water.add_nuclide('B10', 8.0042e-6)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our three materials, we can now create a materials file object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials collection
materials = openmc.Materials([fuel, water, zircaloy])
# Export to "materials.xml"
materials.export_to_xml()
Now let’s move on to the geometry. This problem will be a square array of fuel pins for which we can use OpenMC’s lattice/universe feature. The basic universe will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces for fuel and clad, as well as the outer bounding surfaces of the problem.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.45720)
# Create boundary planes to surround the geometry
# Use both reflective and vacuum boundaries to make life interesting
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='vacuum')
min_y = openmc.YPlane(y0=-10.71, boundary_type='vacuum')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10.71, boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10.71, boundary_type='reflective')
With the surfaces defined, we can now construct a fuel pin cell from cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel', fill=fuel,
region=-fuel_outer_radius)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad', fill=zircaloy)
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator', fill=water,
region=+clad_outer_radius)
# Create a Universe to encapsulate a fuel pin
pin_cell_universe = openmc.Universe(name='1.6% Fuel Pin', cells=[
fuel_cell, clad_cell, moderator_cell
])
Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch.
[6]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel - 0BA')
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2
assembly.universes = [[pin_cell_universe] * 17] * 17
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe.
[7]:
# Create root Cell
root_cell = openmc.Cell(name='root cell', fill=assembly)
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(name='root universe')
root_universe.add_cell(root_cell)
We now must create a geometry that is assigned a root universe and export it to XML.
[8]:
# Create Geometry and export to "geometry.xml"
geometry = openmc.Geometry(root_universe)
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 5 inactive batches and 15 minimum active batches each with 2500 particles. We also tell OpenMC to turn tally triggers on, which means it will keep running until some criterion on the uncertainty of tallies is reached.
[9]:
# OpenMC simulation parameters
min_batches = 20
max_batches = 200
inactive = 5
particles = 2500
# Instantiate a Settings object
settings = openmc.Settings()
settings.batches = min_batches
settings.inactive = inactive
settings.particles = particles
settings.output = {'tallies': False}
settings.trigger_active = True
settings.trigger_max_batches = max_batches
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings.export_to_xml()
Let us also create a plot file that we can use to verify that our pin cell geometry was created successfully.
[10]:
# Instantiate a Plot
plot = openmc.Plot(plot_id=1)
plot.filename = 'materials-xy'
plot.origin = [0, 0, 0]
plot.width = [21.5, 21.5]
plot.pixels = [250, 250]
plot.color_by = 'material'
# Show plot
openmc.plot_inline(plot)

As we can see from the plot, we have a nice array of pin cells with fuel, cladding, and water! Before we run our simulation, we need to tell the code what we want to tally. The following code shows how to create a variety of tallies.
[11]:
# Instantiate an empty Tallies object
tallies = openmc.Tallies()
Instantiate a fission rate mesh Tally
[12]:
# Instantiate a tally Mesh
mesh = openmc.RegularMesh(mesh_id=1)
mesh.dimension = [17, 17]
mesh.lower_left = [-10.71, -10.71]
mesh.width = [1.26, 1.26]
# Instantiate tally Filter
mesh_filter = openmc.MeshFilter(mesh)
# Instantiate energy Filter
energy_filter = openmc.EnergyFilter([0, 0.625, 20.0e6])
# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter, energy_filter]
tally.scores = ['fission', 'nu-fission']
# Add mesh and Tally to Tallies
tallies.append(tally)
Instantiate a cell Tally with nuclides
[13]:
# Instantiate tally Filter
cell_filter = openmc.CellFilter(fuel_cell)
# Instantiate the tally
tally = openmc.Tally(name='cell tally')
tally.filters = [cell_filter]
tally.scores = ['scatter']
tally.nuclides = ['U235', 'U238']
# Add mesh and tally to Tallies
tallies.append(tally)
Create a “distribcell” Tally. The distribcell filter allows us to tally multiple repeated instances of the same cell throughout the geometry.
[14]:
# Instantiate tally Filter
distribcell_filter = openmc.DistribcellFilter(moderator_cell)
# Instantiate tally Trigger for kicks
trigger = openmc.Trigger(trigger_type='std_dev', threshold=5e-5)
trigger.scores = ['absorption']
# Instantiate the Tally
tally = openmc.Tally(name='distribcell tally')
tally.filters = [distribcell_filter]
tally.scores = ['absorption', 'scatter']
tally.triggers = [trigger]
# Add mesh and tally to Tallies
tallies.append(tally)
[15]:
# Export to "tallies.xml"
tallies.export_to_xml()
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[16]:
# Remove old HDF5 (summary, statepoint) files
!rm statepoint.*
# Run OpenMC!
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2020 MIT and OpenMC contributors
License | https://docs.openmc.org/en/latest/license.html
Version | 0.12.0
Git SHA1 | 3d90a9f857ec72eae897e054d4225180f1fa4d93
Date/Time | 2020-08-15 07:10:20
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /home/master/data/nuclear/endfb71_hdf5/U235.h5
Reading U238 from /home/master/data/nuclear/endfb71_hdf5/U238.h5
Reading O16 from /home/master/data/nuclear/endfb71_hdf5/O16.h5
Reading H1 from /home/master/data/nuclear/endfb71_hdf5/H1.h5
Reading B10 from /home/master/data/nuclear/endfb71_hdf5/B10.h5
Reading Zr90 from /home/master/data/nuclear/endfb71_hdf5/Zr90.h5
Minimum neutron data temperature: 294.000000 K
Maximum neutron data temperature: 294.000000 K
Reading tallies XML file...
Preparing distributed cell instances...
Writing summary.h5 file...
Maximum neutron transport energy: 20000000.000000 eV for U235
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 0.53544
2/1 0.62631
3/1 0.63917
4/1 0.67203
5/1 0.69300
6/1 0.64862
7/1 0.63937 0.64399 +/- 0.00463
8/1 0.67696 0.65498 +/- 0.01131
9/1 0.63216 0.64928 +/- 0.00982
10/1 0.70996 0.66141 +/- 0.01433
11/1 0.69761 0.66745 +/- 0.01316
12/1 0.68662 0.67019 +/- 0.01146
13/1 0.64374 0.66688 +/- 0.01046
14/1 0.69121 0.66958 +/- 0.00961
15/1 0.72125 0.67475 +/- 0.01003
16/1 0.72706 0.67950 +/- 0.01024
17/1 0.69623 0.68090 +/- 0.00945
18/1 0.70953 0.68310 +/- 0.00897
19/1 0.69026 0.68361 +/- 0.00832
20/1 0.68633 0.68379 +/- 0.00775
Triggers unsatisfied, max unc./thresh. is 75.24758750489383 for absorption in
tally 3
WARNING: The estimated number of batches is 84938 --- greater than max batches
Creating state point statepoint.020.h5...
21/1 0.68310 0.68375 +/- 0.00725
Triggers unsatisfied, max unc./thresh. is 71.20148627325992 for absorption in
tally 3
WARNING: The estimated number of batches is 81120 --- greater than max batches
22/1 0.68679 0.68393 +/- 0.00681
Triggers unsatisfied, max unc./thresh. is 66.94650483064697 for absorption in
tally 3
WARNING: The estimated number of batches is 76197 --- greater than max batches
23/1 0.67440 0.68340 +/- 0.00644
Triggers unsatisfied, max unc./thresh. is 63.553590826021285 for absorption in
tally 3
WARNING: The estimated number of batches is 72709 --- greater than max batches
24/1 0.67483 0.68295 +/- 0.00611
Triggers unsatisfied, max unc./thresh. is 60.37873858685279 for absorption in
tally 3
WARNING: The estimated number of batches is 69272 --- greater than max batches
25/1 0.71558 0.68458 +/- 0.00602
Triggers unsatisfied, max unc./thresh. is 60.34535216026281 for absorption in
tally 3
WARNING: The estimated number of batches is 72837 --- greater than max batches
26/1 0.71853 0.68620 +/- 0.00595
Triggers unsatisfied, max unc./thresh. is 59.60875760463032 for absorption in
tally 3
WARNING: The estimated number of batches is 74623 --- greater than max batches
27/1 0.67455 0.68567 +/- 0.00570
Triggers unsatisfied, max unc./thresh. is 57.228951643423976 for absorption in
tally 3
WARNING: The estimated number of batches is 72059 --- greater than max batches
28/1 0.69435 0.68605 +/- 0.00546
Triggers unsatisfied, max unc./thresh. is 56.194065573871285 for absorption in
tally 3
WARNING: The estimated number of batches is 72634 --- greater than max batches
29/1 0.67706 0.68567 +/- 0.00524
Triggers unsatisfied, max unc./thresh. is 53.86022066923874 for absorption in
tally 3
WARNING: The estimated number of batches is 69628 --- greater than max batches
30/1 0.69294 0.68596 +/- 0.00504
Triggers unsatisfied, max unc./thresh. is 51.73413206858763 for absorption in
tally 3
WARNING: The estimated number of batches is 66916 --- greater than max batches
31/1 0.69108 0.68616 +/- 0.00484
Triggers unsatisfied, max unc./thresh. is 49.71174462484801 for absorption in
tally 3
WARNING: The estimated number of batches is 64258 --- greater than max batches
32/1 0.68089 0.68596 +/- 0.00466
Triggers unsatisfied, max unc./thresh. is 50.80993117627794 for absorption in
tally 3
WARNING: The estimated number of batches is 69710 --- greater than max batches
33/1 0.67698 0.68564 +/- 0.00450
Triggers unsatisfied, max unc./thresh. is 50.46659333785448 for absorption in
tally 3
WARNING: The estimated number of batches is 71318 --- greater than max batches
34/1 0.68167 0.68551 +/- 0.00435
Triggers unsatisfied, max unc./thresh. is 48.852656603250665 for absorption in
tally 3
WARNING: The estimated number of batches is 69216 --- greater than max batches
35/1 0.67760 0.68524 +/- 0.00421
Triggers unsatisfied, max unc./thresh. is 48.5685583427197 for absorption in
tally 3
WARNING: The estimated number of batches is 70773 --- greater than max batches
36/1 0.67628 0.68495 +/- 0.00408
Triggers unsatisfied, max unc./thresh. is 47.77661998216646 for absorption in
tally 3
WARNING: The estimated number of batches is 70766 --- greater than max batches
37/1 0.66736 0.68440 +/- 0.00399
Triggers unsatisfied, max unc./thresh. is 46.57810773879176 for absorption in
tally 3
WARNING: The estimated number of batches is 69430 --- greater than max batches
38/1 0.71026 0.68519 +/- 0.00395
Triggers unsatisfied, max unc./thresh. is 45.876107560616674 for absorption in
tally 3
WARNING: The estimated number of batches is 69458 --- greater than max batches
39/1 0.67674 0.68494 +/- 0.00384
Triggers unsatisfied, max unc./thresh. is 45.30341918376721 for absorption in
tally 3
WARNING: The estimated number of batches is 69787 --- greater than max batches
40/1 0.69360 0.68519 +/- 0.00373
Triggers unsatisfied, max unc./thresh. is 44.018056562863386 for absorption in
tally 3
WARNING: The estimated number of batches is 67821 --- greater than max batches
41/1 0.70987 0.68587 +/- 0.00369
Triggers unsatisfied, max unc./thresh. is 42.781078099052785 for absorption in
tally 3
WARNING: The estimated number of batches is 65893 --- greater than max batches
42/1 0.68780 0.68592 +/- 0.00359
Triggers unsatisfied, max unc./thresh. is 41.60877069209228 for absorption in
tally 3
WARNING: The estimated number of batches is 64063 --- greater than max batches
43/1 0.69223 0.68609 +/- 0.00350
Triggers unsatisfied, max unc./thresh. is 42.44932759168626 for absorption in
tally 3
WARNING: The estimated number of batches is 68479 --- greater than max batches
44/1 0.69561 0.68633 +/- 0.00342
Triggers unsatisfied, max unc./thresh. is 41.34743776753899 for absorption in
tally 3
WARNING: The estimated number of batches is 66680 --- greater than max batches
45/1 0.67503 0.68605 +/- 0.00334
Triggers unsatisfied, max unc./thresh. is 40.97332186124358 for absorption in
tally 3
WARNING: The estimated number of batches is 67158 --- greater than max batches
46/1 0.67290 0.68573 +/- 0.00328
Triggers unsatisfied, max unc./thresh. is 40.24678448931756 for absorption in
tally 3
WARNING: The estimated number of batches is 66417 --- greater than max batches
47/1 0.67355 0.68544 +/- 0.00321
Triggers unsatisfied, max unc./thresh. is 40.42620640829592 for absorption in
tally 3
WARNING: The estimated number of batches is 68645 --- greater than max batches
48/1 0.71383 0.68610 +/- 0.00320
Triggers unsatisfied, max unc./thresh. is 39.90662320308606 for absorption in
tally 3
WARNING: The estimated number of batches is 68485 --- greater than max batches
49/1 0.68389 0.68605 +/- 0.00313
Triggers unsatisfied, max unc./thresh. is 39.075369568753906 for absorption in
tally 3
WARNING: The estimated number of batches is 67188 --- greater than max batches
50/1 0.73148 0.68706 +/- 0.00322
Triggers unsatisfied, max unc./thresh. is 38.57054567218653 for absorption in
tally 3
WARNING: The estimated number of batches is 66951 --- greater than max batches
51/1 0.69796 0.68730 +/- 0.00316
Triggers unsatisfied, max unc./thresh. is 38.21572703507316 for absorption in
tally 3
WARNING: The estimated number of batches is 67186 --- greater than max batches
52/1 0.70691 0.68771 +/- 0.00312
Triggers unsatisfied, max unc./thresh. is 37.50971908717773 for absorption in
tally 3
WARNING: The estimated number of batches is 66134 --- greater than max batches
53/1 0.69104 0.68778 +/- 0.00306
Triggers unsatisfied, max unc./thresh. is 36.824732312223716 for absorption in
tally 3
WARNING: The estimated number of batches is 65096 --- greater than max batches
54/1 0.74368 0.68892 +/- 0.00320
Triggers unsatisfied, max unc./thresh. is 36.20814737643575 for absorption in
tally 3
WARNING: The estimated number of batches is 64246 --- greater than max batches
55/1 0.67371 0.68862 +/- 0.00315
Triggers unsatisfied, max unc./thresh. is 35.48607231512293 for absorption in
tally 3
WARNING: The estimated number of batches is 62969 --- greater than max batches
56/1 0.67846 0.68842 +/- 0.00310
Triggers unsatisfied, max unc./thresh. is 35.34421893287461 for absorption in
tally 3
WARNING: The estimated number of batches is 63715 --- greater than max batches
57/1 0.66351 0.68794 +/- 0.00307
Triggers unsatisfied, max unc./thresh. is 34.67062652878957 for absorption in
tally 3
WARNING: The estimated number of batches is 62512 --- greater than max batches
58/1 0.67049 0.68761 +/- 0.00303
Triggers unsatisfied, max unc./thresh. is 34.22135922543247 for absorption in
tally 3
WARNING: The estimated number of batches is 62074 --- greater than max batches
59/1 0.66967 0.68728 +/- 0.00299
Triggers unsatisfied, max unc./thresh. is 33.66881484408945 for absorption in
tally 3
WARNING: The estimated number of batches is 61219 --- greater than max batches
60/1 0.70271 0.68756 +/- 0.00295
Triggers unsatisfied, max unc./thresh. is 33.19914799505717 for absorption in
tally 3
WARNING: The estimated number of batches is 60626 --- greater than max batches
61/1 0.70035 0.68779 +/- 0.00291
Triggers unsatisfied, max unc./thresh. is 32.65594936729897 for absorption in
tally 3
WARNING: The estimated number of batches is 59725 --- greater than max batches
62/1 0.66274 0.68735 +/- 0.00289
Triggers unsatisfied, max unc./thresh. is 32.15622046485561 for absorption in
tally 3
WARNING: The estimated number of batches is 58945 --- greater than max batches
63/1 0.68607 0.68733 +/- 0.00284
Triggers unsatisfied, max unc./thresh. is 31.601225649282494 for absorption in
tally 3
WARNING: The estimated number of batches is 57926 --- greater than max batches
64/1 0.66518 0.68695 +/- 0.00282
Triggers unsatisfied, max unc./thresh. is 31.12129365572805 for absorption in
tally 3
WARNING: The estimated number of batches is 57149 --- greater than max batches
65/1 0.65999 0.68650 +/- 0.00281
Triggers unsatisfied, max unc./thresh. is 30.641988019531464 for absorption in
tally 3
WARNING: The estimated number of batches is 56341 --- greater than max batches
66/1 0.67843 0.68637 +/- 0.00276
Triggers unsatisfied, max unc./thresh. is 30.320463443580458 for absorption in
tally 3
WARNING: The estimated number of batches is 56085 --- greater than max batches
67/1 0.69295 0.68648 +/- 0.00272
Triggers unsatisfied, max unc./thresh. is 30.06051080038397 for absorption in
tally 3
WARNING: The estimated number of batches is 56031 --- greater than max batches
68/1 0.69158 0.68656 +/- 0.00268
Triggers unsatisfied, max unc./thresh. is 29.7400907913873 for absorption in
tally 3
WARNING: The estimated number of batches is 55727 --- greater than max batches
69/1 0.69825 0.68674 +/- 0.00264
Triggers unsatisfied, max unc./thresh. is 29.278619659445805 for absorption in
tally 3
WARNING: The estimated number of batches is 54869 --- greater than max batches
70/1 0.73637 0.68750 +/- 0.00271
Triggers unsatisfied, max unc./thresh. is 28.945044018568716 for absorption in
tally 3
WARNING: The estimated number of batches is 54464 --- greater than max batches
71/1 0.64301 0.68683 +/- 0.00275
Triggers unsatisfied, max unc./thresh. is 28.73677928804667 for absorption in
tally 3
WARNING: The estimated number of batches is 54508 --- greater than max batches
72/1 0.71506 0.68725 +/- 0.00274
Triggers unsatisfied, max unc./thresh. is 29.20796537704291 for absorption in
tally 3
WARNING: The estimated number of batches is 57164 --- greater than max batches
73/1 0.69203 0.68732 +/- 0.00270
Triggers unsatisfied, max unc./thresh. is 29.56297016014581 for absorption in
tally 3
WARNING: The estimated number of batches is 59435 --- greater than max batches
74/1 0.69208 0.68739 +/- 0.00267
Triggers unsatisfied, max unc./thresh. is 29.545241442413783 for absorption in
tally 3
WARNING: The estimated number of batches is 60237 --- greater than max batches
75/1 0.65717 0.68696 +/- 0.00266
Triggers unsatisfied, max unc./thresh. is 29.284013224166248 for absorption in
tally 3
WARNING: The estimated number of batches is 60034 --- greater than max batches
76/1 0.70992 0.68728 +/- 0.00265
Triggers unsatisfied, max unc./thresh. is 29.00034584995327 for absorption in
tally 3
WARNING: The estimated number of batches is 59718 --- greater than max batches
77/1 0.65590 0.68685 +/- 0.00264
Triggers unsatisfied, max unc./thresh. is 28.867967845905174 for absorption in
tally 3
WARNING: The estimated number of batches is 60007 --- greater than max batches
78/1 0.64439 0.68626 +/- 0.00267
Triggers unsatisfied, max unc./thresh. is 29.016012595317935 for absorption in
tally 3
WARNING: The estimated number of batches is 61466 --- greater than max batches
79/1 0.66295 0.68595 +/- 0.00265
Triggers unsatisfied, max unc./thresh. is 28.626245464278142 for absorption in
tally 3
WARNING: The estimated number of batches is 60646 --- greater than max batches
80/1 0.66672 0.68569 +/- 0.00263
Triggers unsatisfied, max unc./thresh. is 28.24218910063624 for absorption in
tally 3
WARNING: The estimated number of batches is 59827 --- greater than max batches
81/1 0.69110 0.68576 +/- 0.00260
Triggers unsatisfied, max unc./thresh. is 27.917349908027763 for absorption in
tally 3
WARNING: The estimated number of batches is 59238 --- greater than max batches
82/1 0.67481 0.68562 +/- 0.00257
Triggers unsatisfied, max unc./thresh. is 28.01946018168837 for absorption in
tally 3
WARNING: The estimated number of batches is 60457 --- greater than max batches
83/1 0.72216 0.68609 +/- 0.00258
Triggers unsatisfied, max unc./thresh. is 27.931394620754766 for absorption in
tally 3
WARNING: The estimated number of batches is 60858 --- greater than max batches
84/1 0.68429 0.68607 +/- 0.00254
Triggers unsatisfied, max unc./thresh. is 27.713137470531738 for absorption in
tally 3
WARNING: The estimated number of batches is 60679 --- greater than max batches
85/1 0.65458 0.68567 +/- 0.00254
Triggers unsatisfied, max unc./thresh. is 27.364539968246927 for absorption in
tally 3
WARNING: The estimated number of batches is 59911 --- greater than max batches
86/1 0.69966 0.68585 +/- 0.00252
Triggers unsatisfied, max unc./thresh. is 27.178113974435043 for absorption in
tally 3
WARNING: The estimated number of batches is 59836 --- greater than max batches
87/1 0.64776 0.68538 +/- 0.00253
Triggers unsatisfied, max unc./thresh. is 26.941566345072534 for absorption in
tally 3
WARNING: The estimated number of batches is 59525 --- greater than max batches
88/1 0.62737 0.68468 +/- 0.00259
Triggers unsatisfied, max unc./thresh. is 26.73959660667411 for absorption in
tally 3
WARNING: The estimated number of batches is 59351 --- greater than max batches
89/1 0.69779 0.68484 +/- 0.00257
Triggers unsatisfied, max unc./thresh. is 26.490234865810894 for absorption in
tally 3
WARNING: The estimated number of batches is 58951 --- greater than max batches
90/1 0.67312 0.68470 +/- 0.00254
Triggers unsatisfied, max unc./thresh. is 26.24036001465229 for absorption in
tally 3
WARNING: The estimated number of batches is 58533 --- greater than max batches
91/1 0.69289 0.68480 +/- 0.00251
Triggers unsatisfied, max unc./thresh. is 25.936795778335345 for absorption in
tally 3
WARNING: The estimated number of batches is 57859 --- greater than max batches
92/1 0.69884 0.68496 +/- 0.00249
Triggers unsatisfied, max unc./thresh. is 25.695465963215582 for absorption in
tally 3
WARNING: The estimated number of batches is 57448 --- greater than max batches
93/1 0.71351 0.68528 +/- 0.00248
Triggers unsatisfied, max unc./thresh. is 25.49001212499821 for absorption in
tally 3
WARNING: The estimated number of batches is 57183 --- greater than max batches
94/1 0.65602 0.68495 +/- 0.00248
Triggers unsatisfied, max unc./thresh. is 25.350859463183905 for absorption in
tally 3
WARNING: The estimated number of batches is 57203 --- greater than max batches
95/1 0.72223 0.68537 +/- 0.00248
Triggers unsatisfied, max unc./thresh. is 25.157803279393637 for absorption in
tally 3
WARNING: The estimated number of batches is 56968 --- greater than max batches
96/1 0.67930 0.68530 +/- 0.00246
Triggers unsatisfied, max unc./thresh. is 24.92205849077747 for absorption in
tally 3
WARNING: The estimated number of batches is 56526 --- greater than max batches
97/1 0.66201 0.68505 +/- 0.00244
Triggers unsatisfied, max unc./thresh. is 24.653967027285237 for absorption in
tally 3
WARNING: The estimated number of batches is 55925 --- greater than max batches
98/1 0.71110 0.68533 +/- 0.00243
Triggers unsatisfied, max unc./thresh. is 24.566957281211884 for absorption in
tally 3
WARNING: The estimated number of batches is 56134 --- greater than max batches
99/1 0.69409 0.68542 +/- 0.00241
Triggers unsatisfied, max unc./thresh. is 24.581943149247135 for absorption in
tally 3
WARNING: The estimated number of batches is 56807 --- greater than max batches
100/1 0.71197 0.68570 +/- 0.00240
Triggers unsatisfied, max unc./thresh. is 24.444112571967217 for absorption in
tally 3
WARNING: The estimated number of batches is 56769 --- greater than max batches
101/1 0.71713 0.68603 +/- 0.00240
Triggers unsatisfied, max unc./thresh. is 24.18957776896016 for absorption in
tally 3
WARNING: The estimated number of batches is 56179 --- greater than max batches
102/1 0.68143 0.68598 +/- 0.00237
Triggers unsatisfied, max unc./thresh. is 23.97797098066553 for absorption in
tally 3
WARNING: The estimated number of batches is 55775 --- greater than max batches
103/1 0.69936 0.68612 +/- 0.00235
Triggers unsatisfied, max unc./thresh. is 24.253000406602812 for absorption in
tally 3
WARNING: The estimated number of batches is 57650 --- greater than max batches
104/1 0.65247 0.68578 +/- 0.00235
Triggers unsatisfied, max unc./thresh. is 24.593482483379837 for absorption in
tally 3
WARNING: The estimated number of batches is 59885 --- greater than max batches
105/1 0.66517 0.68557 +/- 0.00234
Triggers unsatisfied, max unc./thresh. is 24.37904760701804 for absorption in
tally 3
WARNING: The estimated number of batches is 59439 --- greater than max batches
106/1 0.67814 0.68550 +/- 0.00232
Triggers unsatisfied, max unc./thresh. is 24.142311084988883 for absorption in
tally 3
WARNING: The estimated number of batches is 58873 --- greater than max batches
107/1 0.67788 0.68542 +/- 0.00229
Triggers unsatisfied, max unc./thresh. is 23.935477435724106 for absorption in
tally 3
WARNING: The estimated number of batches is 58442 --- greater than max batches
108/1 0.68016 0.68537 +/- 0.00227
Triggers unsatisfied, max unc./thresh. is 24.532504688648594 for absorption in
tally 3
WARNING: The estimated number of batches is 61995 --- greater than max batches
109/1 0.66963 0.68522 +/- 0.00226
Triggers unsatisfied, max unc./thresh. is 24.354532539671386 for absorption in
tally 3
WARNING: The estimated number of batches is 61692 --- greater than max batches
110/1 0.67556 0.68513 +/- 0.00224
Triggers unsatisfied, max unc./thresh. is 24.16165322902175 for absorption in
tally 3
WARNING: The estimated number of batches is 61303 --- greater than max batches
111/1 0.68273 0.68511 +/- 0.00222
Triggers unsatisfied, max unc./thresh. is 24.00069508298176 for absorption in
tally 3
WARNING: The estimated number of batches is 61065 --- greater than max batches
112/1 0.69505 0.68520 +/- 0.00220
Triggers unsatisfied, max unc./thresh. is 23.791656279909404 for absorption in
tally 3
WARNING: The estimated number of batches is 60572 --- greater than max batches
113/1 0.69385 0.68528 +/- 0.00218
Triggers unsatisfied, max unc./thresh. is 23.667941020219764 for absorption in
tally 3
WARNING: The estimated number of batches is 60504 --- greater than max batches
114/1 0.65352 0.68499 +/- 0.00218
Triggers unsatisfied, max unc./thresh. is 23.469658485546123 for absorption in
tally 3
WARNING: The estimated number of batches is 60045 --- greater than max batches
115/1 0.68339 0.68497 +/- 0.00216
Triggers unsatisfied, max unc./thresh. is 23.259123161328624 for absorption in
tally 3
WARNING: The estimated number of batches is 59514 --- greater than max batches
116/1 0.65854 0.68474 +/- 0.00215
Triggers unsatisfied, max unc./thresh. is 23.06250977653337 for absorption in
tally 3
WARNING: The estimated number of batches is 59044 --- greater than max batches
117/1 0.66907 0.68460 +/- 0.00214
Triggers unsatisfied, max unc./thresh. is 22.874382198219536 for absorption in
tally 3
WARNING: The estimated number of batches is 58608 --- greater than max batches
118/1 0.68165 0.68457 +/- 0.00212
Triggers unsatisfied, max unc./thresh. is 22.709602691165983 for absorption in
tally 3
WARNING: The estimated number of batches is 58283 --- greater than max batches
119/1 0.70967 0.68479 +/- 0.00211
Triggers unsatisfied, max unc./thresh. is 22.509869996658225 for absorption in
tally 3
WARNING: The estimated number of batches is 57769 --- greater than max batches
120/1 0.65543 0.68453 +/- 0.00211
Triggers unsatisfied, max unc./thresh. is 22.422104322874098 for absorption in
tally 3
WARNING: The estimated number of batches is 57822 --- greater than max batches
121/1 0.67305 0.68444 +/- 0.00209
Triggers unsatisfied, max unc./thresh. is 22.32834321567902 for absorption in
tally 3
WARNING: The estimated number of batches is 57838 --- greater than max batches
122/1 0.68206 0.68441 +/- 0.00207
Triggers unsatisfied, max unc./thresh. is 22.155196965032374 for absorption in
tally 3
WARNING: The estimated number of batches is 57435 --- greater than max batches
123/1 0.71125 0.68464 +/- 0.00207
Triggers unsatisfied, max unc./thresh. is 21.96683678913398 for absorption in
tally 3
WARNING: The estimated number of batches is 56945 --- greater than max batches
124/1 0.65918 0.68443 +/- 0.00206
Triggers unsatisfied, max unc./thresh. is 21.78216358933223 for absorption in
tally 3
WARNING: The estimated number of batches is 56467 --- greater than max batches
125/1 0.68122 0.68440 +/- 0.00205
Triggers unsatisfied, max unc./thresh. is 21.681928420505304 for absorption in
tally 3
WARNING: The estimated number of batches is 56418 --- greater than max batches
126/1 0.66900 0.68427 +/- 0.00203
Triggers unsatisfied, max unc./thresh. is 21.60631058168512 for absorption in
tally 3
WARNING: The estimated number of batches is 56492 --- greater than max batches
127/1 0.66742 0.68414 +/- 0.00202
Triggers unsatisfied, max unc./thresh. is 21.468291123480988 for absorption in
tally 3
WARNING: The estimated number of batches is 56234 --- greater than max batches
128/1 0.66971 0.68402 +/- 0.00201
Triggers unsatisfied, max unc./thresh. is 21.313238544974386 for absorption in
tally 3
WARNING: The estimated number of batches is 55879 --- greater than max batches
129/1 0.68183 0.68400 +/- 0.00199
Triggers unsatisfied, max unc./thresh. is 21.314008888585132 for absorption in
tally 3
WARNING: The estimated number of batches is 56337 --- greater than max batches
130/1 0.68403 0.68400 +/- 0.00197
Triggers unsatisfied, max unc./thresh. is 21.159444482258046 for absorption in
tally 3
WARNING: The estimated number of batches is 55971 --- greater than max batches
131/1 0.69137 0.68406 +/- 0.00196
Triggers unsatisfied, max unc./thresh. is 21.24931160989673 for absorption in
tally 3
WARNING: The estimated number of batches is 56899 --- greater than max batches
132/1 0.67481 0.68399 +/- 0.00195
Triggers unsatisfied, max unc./thresh. is 21.164512281281944 for absorption in
tally 3
WARNING: The estimated number of batches is 56893 --- greater than max batches
133/1 0.70390 0.68414 +/- 0.00194
Triggers unsatisfied, max unc./thresh. is 21.084176856795946 for absorption in
tally 3
WARNING: The estimated number of batches is 56907 --- greater than max batches
134/1 0.67961 0.68411 +/- 0.00192
Triggers unsatisfied, max unc./thresh. is 21.48684646255342 for absorption in
tally 3
WARNING: The estimated number of batches is 59563 --- greater than max batches
135/1 0.65362 0.68387 +/- 0.00192
Triggers unsatisfied, max unc./thresh. is 21.41235779526348 for absorption in
tally 3
WARNING: The estimated number of batches is 59609 --- greater than max batches
136/1 0.63946 0.68353 +/- 0.00194
Triggers unsatisfied, max unc./thresh. is 21.30299014546295 for absorption in
tally 3
WARNING: The estimated number of batches is 59456 --- greater than max batches
137/1 0.64818 0.68327 +/- 0.00194
Triggers unsatisfied, max unc./thresh. is 21.159761745415484 for absorption in
tally 3
WARNING: The estimated number of batches is 59107 --- greater than max batches
138/1 0.68975 0.68331 +/- 0.00193
Triggers unsatisfied, max unc./thresh. is 21.000094566393475 for absorption in
tally 3
WARNING: The estimated number of batches is 58659 --- greater than max batches
139/1 0.67280 0.68324 +/- 0.00191
Triggers unsatisfied, max unc./thresh. is 20.853656171297644 for absorption in
tally 3
WARNING: The estimated number of batches is 58279 --- greater than max batches
140/1 0.66857 0.68313 +/- 0.00190
Triggers unsatisfied, max unc./thresh. is 20.709591767033707 for absorption in
tally 3
WARNING: The estimated number of batches is 57905 --- greater than max batches
141/1 0.68175 0.68312 +/- 0.00189
Triggers unsatisfied, max unc./thresh. is 20.560813068689416 for absorption in
tally 3
WARNING: The estimated number of batches is 57499 --- greater than max batches
142/1 0.72210 0.68340 +/- 0.00190
Triggers unsatisfied, max unc./thresh. is 20.54814917791921 for absorption in
tally 3
WARNING: The estimated number of batches is 57851 --- greater than max batches
143/1 0.67361 0.68333 +/- 0.00188
Triggers unsatisfied, max unc./thresh. is 20.4177880049802 for absorption in
tally 3
WARNING: The estimated number of batches is 57536 --- greater than max batches
144/1 0.65862 0.68315 +/- 0.00188
Triggers unsatisfied, max unc./thresh. is 21.229890183572195 for absorption in
tally 3
WARNING: The estimated number of batches is 62654 --- greater than max batches
145/1 0.69713 0.68325 +/- 0.00187
Triggers unsatisfied, max unc./thresh. is 21.34513800240435 for absorption in
tally 3
WARNING: The estimated number of batches is 63792 --- greater than max batches
146/1 0.72980 0.68358 +/- 0.00188
Triggers unsatisfied, max unc./thresh. is 21.60165210412777 for absorption in
tally 3
WARNING: The estimated number of batches is 65801 --- greater than max batches
147/1 0.70004 0.68370 +/- 0.00187
Triggers unsatisfied, max unc./thresh. is 21.596734424310384 for absorption in
tally 3
WARNING: The estimated number of batches is 66237 --- greater than max batches
148/1 0.68882 0.68374 +/- 0.00186
Triggers unsatisfied, max unc./thresh. is 21.447240534346236 for absorption in
tally 3
WARNING: The estimated number of batches is 65783 --- greater than max batches
149/1 0.70401 0.68388 +/- 0.00185
Triggers unsatisfied, max unc./thresh. is 21.424993974056104 for absorption in
tally 3
WARNING: The estimated number of batches is 66106 --- greater than max batches
150/1 0.72110 0.68413 +/- 0.00186
Triggers unsatisfied, max unc./thresh. is 21.27792348945665 for absorption in
tally 3
WARNING: The estimated number of batches is 65654 --- greater than max batches
151/1 0.65918 0.68396 +/- 0.00185
Triggers unsatisfied, max unc./thresh. is 21.378637401006184 for absorption in
tally 3
WARNING: The estimated number of batches is 66734 --- greater than max batches
152/1 0.67751 0.68392 +/- 0.00184
Triggers unsatisfied, max unc./thresh. is 21.25974745003047 for absorption in
tally 3
WARNING: The estimated number of batches is 66446 --- greater than max batches
153/1 0.69302 0.68398 +/- 0.00183
Triggers unsatisfied, max unc./thresh. is 21.16055371271148 for absorption in
tally 3
WARNING: The estimated number of batches is 66275 --- greater than max batches
154/1 0.67102 0.68389 +/- 0.00182
Triggers unsatisfied, max unc./thresh. is 21.0227808264386 for absorption in
tally 3
WARNING: The estimated number of batches is 65857 --- greater than max batches
155/1 0.64427 0.68363 +/- 0.00183
Triggers unsatisfied, max unc./thresh. is 20.882547322553506 for absorption in
tally 3
WARNING: The estimated number of batches is 65418 --- greater than max batches
156/1 0.68488 0.68364 +/- 0.00181
Triggers unsatisfied, max unc./thresh. is 20.797424126850476 for absorption in
tally 3
WARNING: The estimated number of batches is 65318 --- greater than max batches
157/1 0.67337 0.68357 +/- 0.00180
Triggers unsatisfied, max unc./thresh. is 20.67015745584828 for absorption in
tally 3
WARNING: The estimated number of batches is 64948 --- greater than max batches
158/1 0.66662 0.68346 +/- 0.00180
Triggers unsatisfied, max unc./thresh. is 20.568519956722266 for absorption in
tally 3
WARNING: The estimated number of batches is 64734 --- greater than max batches
159/1 0.62697 0.68309 +/- 0.00182
Triggers unsatisfied, max unc./thresh. is 20.47085213159483 for absorption in
tally 3
WARNING: The estimated number of batches is 64540 --- greater than max batches
160/1 0.68300 0.68309 +/- 0.00181
Triggers unsatisfied, max unc./thresh. is 20.34586209866351 for absorption in
tally 3
WARNING: The estimated number of batches is 64168 --- greater than max batches
161/1 0.68918 0.68313 +/- 0.00180
Triggers unsatisfied, max unc./thresh. is 20.23505377614212 for absorption in
tally 3
WARNING: The estimated number of batches is 63881 --- greater than max batches
162/1 0.70939 0.68330 +/- 0.00179
Triggers unsatisfied, max unc./thresh. is 20.21114215977674 for absorption in
tally 3
WARNING: The estimated number of batches is 64138 --- greater than max batches
163/1 0.69681 0.68338 +/- 0.00179
Triggers unsatisfied, max unc./thresh. is 20.163170350438893 for absorption in
tally 3
WARNING: The estimated number of batches is 64241 --- greater than max batches
164/1 0.66454 0.68326 +/- 0.00178
Triggers unsatisfied, max unc./thresh. is 20.109882525638955 for absorption in
tally 3
WARNING: The estimated number of batches is 64306 --- greater than max batches
165/1 0.68804 0.68329 +/- 0.00177
Triggers unsatisfied, max unc./thresh. is 20.033653897136055 for absorption in
tally 3
WARNING: The estimated number of batches is 64221 --- greater than max batches
166/1 0.66078 0.68315 +/- 0.00176
Triggers unsatisfied, max unc./thresh. is 19.916131324861123 for absorption in
tally 3
WARNING: The estimated number of batches is 63867 --- greater than max batches
167/1 0.65762 0.68300 +/- 0.00176
Triggers unsatisfied, max unc./thresh. is 19.85097950868946 for absorption in
tally 3
WARNING: The estimated number of batches is 63843 --- greater than max batches
168/1 0.69267 0.68306 +/- 0.00175
Triggers unsatisfied, max unc./thresh. is 19.729436003984436 for absorption in
tally 3
WARNING: The estimated number of batches is 63453 --- greater than max batches
169/1 0.67859 0.68303 +/- 0.00174
Triggers unsatisfied, max unc./thresh. is 19.61178427698242 for absorption in
tally 3
WARNING: The estimated number of batches is 63084 --- greater than max batches
170/1 0.66545 0.68292 +/- 0.00173
Triggers unsatisfied, max unc./thresh. is 19.495197332905757 for absorption in
tally 3
WARNING: The estimated number of batches is 62716 --- greater than max batches
171/1 0.66716 0.68283 +/- 0.00172
Triggers unsatisfied, max unc./thresh. is 19.47044861415963 for absorption in
tally 3
WARNING: The estimated number of batches is 62936 --- greater than max batches
172/1 0.70008 0.68293 +/- 0.00172
Triggers unsatisfied, max unc./thresh. is 19.382801191970024 for absorption in
tally 3
WARNING: The estimated number of batches is 62746 --- greater than max batches
173/1 0.69417 0.68300 +/- 0.00171
Triggers unsatisfied, max unc./thresh. is 19.270038663528165 for absorption in
tally 3
WARNING: The estimated number of batches is 62390 --- greater than max batches
174/1 0.66458 0.68289 +/- 0.00170
Triggers unsatisfied, max unc./thresh. is 19.281533726364312 for absorption in
tally 3
WARNING: The estimated number of batches is 62836 --- greater than max batches
175/1 0.65867 0.68275 +/- 0.00170
Triggers unsatisfied, max unc./thresh. is 19.234847030404104 for absorption in
tally 3
WARNING: The estimated number of batches is 62902 --- greater than max batches
176/1 0.69631 0.68283 +/- 0.00169
Triggers unsatisfied, max unc./thresh. is 19.13381099709457 for absorption in
tally 3
WARNING: The estimated number of batches is 62609 --- greater than max batches
177/1 0.71142 0.68299 +/- 0.00169
Triggers unsatisfied, max unc./thresh. is 19.022563643493143 for absorption in
tally 3
WARNING: The estimated number of batches is 62245 --- greater than max batches
178/1 0.68640 0.68301 +/- 0.00168
Triggers unsatisfied, max unc./thresh. is 19.03176453708651 for absorption in
tally 3
WARNING: The estimated number of batches is 62667 --- greater than max batches
179/1 0.70448 0.68313 +/- 0.00167
Triggers unsatisfied, max unc./thresh. is 19.088395456136563 for absorption in
tally 3
WARNING: The estimated number of batches is 63405 --- greater than max batches
180/1 0.70538 0.68326 +/- 0.00167
Triggers unsatisfied, max unc./thresh. is 18.98864751831452 for absorption in
tally 3
WARNING: The estimated number of batches is 63105 --- greater than max batches
181/1 0.65591 0.68311 +/- 0.00166
Triggers unsatisfied, max unc./thresh. is 18.911017891051518 for absorption in
tally 3
WARNING: The estimated number of batches is 62948 --- greater than max batches
182/1 0.72818 0.68336 +/- 0.00167
Triggers unsatisfied, max unc./thresh. is 18.808510226366458 for absorption in
tally 3
WARNING: The estimated number of batches is 62621 --- greater than max batches
183/1 0.67896 0.68334 +/- 0.00167
Triggers unsatisfied, max unc./thresh. is 18.825142861337717 for absorption in
tally 3
WARNING: The estimated number of batches is 63086 --- greater than max batches
184/1 0.65442 0.68317 +/- 0.00166
Triggers unsatisfied, max unc./thresh. is 18.79514029258707 for absorption in
tally 3
WARNING: The estimated number of batches is 63239 --- greater than max batches
185/1 0.68885 0.68321 +/- 0.00165
Triggers unsatisfied, max unc./thresh. is 18.76276176864163 for absorption in
tally 3
WARNING: The estimated number of batches is 63373 --- greater than max batches
186/1 0.68893 0.68324 +/- 0.00165
Triggers unsatisfied, max unc./thresh. is 18.690155368597363 for absorption in
tally 3
WARNING: The estimated number of batches is 63233 --- greater than max batches
187/1 0.68918 0.68327 +/- 0.00164
Triggers unsatisfied, max unc./thresh. is 18.590144288270153 for absorption in
tally 3
WARNING: The estimated number of batches is 62904 --- greater than max batches
188/1 0.69854 0.68335 +/- 0.00163
Triggers unsatisfied, max unc./thresh. is 18.61460656150607 for absorption in
tally 3
WARNING: The estimated number of batches is 63416 --- greater than max batches
189/1 0.66324 0.68324 +/- 0.00162
Triggers unsatisfied, max unc./thresh. is 18.518608099237504 for absorption in
tally 3
WARNING: The estimated number of batches is 63106 --- greater than max batches
190/1 0.69450 0.68331 +/- 0.00162
Triggers unsatisfied, max unc./thresh. is 18.425351661292233 for absorption in
tally 3
WARNING: The estimated number of batches is 62812 --- greater than max batches
191/1 0.68953 0.68334 +/- 0.00161
Triggers unsatisfied, max unc./thresh. is 18.328779429843646 for absorption in
tally 3
WARNING: The estimated number of batches is 62491 --- greater than max batches
192/1 0.66621 0.68325 +/- 0.00160
Triggers unsatisfied, max unc./thresh. is 18.28094973389564 for absorption in
tally 3
WARNING: The estimated number of batches is 62500 --- greater than max batches
193/1 0.71102 0.68339 +/- 0.00160
Triggers unsatisfied, max unc./thresh. is 18.19949730061142 for absorption in
tally 3
WARNING: The estimated number of batches is 62275 --- greater than max batches
194/1 0.65341 0.68324 +/- 0.00160
Triggers unsatisfied, max unc./thresh. is 18.159054737369345 for absorption in
tally 3
WARNING: The estimated number of batches is 62328 --- greater than max batches
195/1 0.70061 0.68333 +/- 0.00159
Triggers unsatisfied, max unc./thresh. is 18.082465954324594 for absorption in
tally 3
WARNING: The estimated number of batches is 62131 --- greater than max batches
196/1 0.69339 0.68338 +/- 0.00159
Triggers unsatisfied, max unc./thresh. is 18.043133483791827 for absorption in
tally 3
WARNING: The estimated number of batches is 62186 --- greater than max batches
197/1 0.64411 0.68318 +/- 0.00159
Triggers unsatisfied, max unc./thresh. is 18.019303623546417 for absorption in
tally 3
WARNING: The estimated number of batches is 62347 --- greater than max batches
198/1 0.66626 0.68309 +/- 0.00159
Triggers unsatisfied, max unc./thresh. is 17.968092739058083 for absorption in
tally 3
WARNING: The estimated number of batches is 62316 --- greater than max batches
199/1 0.67839 0.68306 +/- 0.00158
Triggers unsatisfied, max unc./thresh. is 17.91968515142146 for absorption in
tally 3
WARNING: The estimated number of batches is 62302 --- greater than max batches
200/1 0.66459 0.68297 +/- 0.00157
Triggers unsatisfied, max unc./thresh. is 17.82970764669685 for absorption in
tally 3
WARNING: The estimated number of batches is 61996 --- greater than max batches
Creating state point statepoint.200.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 2.9309e-01 seconds
Reading cross sections = 2.8108e-01 seconds
Total time in simulation = 1.1321e+01 seconds
Time in transport only = 1.1242e+01 seconds
Time in inactive batches = 1.6721e-01 seconds
Time in active batches = 1.1153e+01 seconds
Time synchronizing fission bank = 2.2958e-02 seconds
Sampling source sites = 1.8701e-02 seconds
SEND/RECV source sites = 3.9403e-03 seconds
Time accumulating tallies = 9.9349e-04 seconds
Total time for finalization = 5.2200e-07 seconds
Total time elapsed = 1.1620e+01 seconds
Calculation Rate (inactive) = 74758.2 particles/second
Calculation Rate (active) = 43708.5 particles/second
============================> RESULTS <============================
k-effective (Collision) = 0.68198 +/- 0.00141
k-effective (Track-length) = 0.68297 +/- 0.00157
k-effective (Absorption) = 0.68161 +/- 0.00145
Combined k-effective = 0.68209 +/- 0.00118
Leakage Fraction = 0.34033 +/- 0.00074
Tally Data Processing¶
[17]:
# We do not know how many batches were needed to satisfy the
# tally trigger(s), so find the statepoint file(s)
statepoints = glob.glob('statepoint.*.h5')
# Load the last statepoint file
sp = openmc.StatePoint(statepoints[-1])
Analyze the mesh fission rate tally
[18]:
# Find the mesh tally with the StatePoint API
tally = sp.get_tally(name='mesh tally')
# Print a little info about the mesh tally to the screen
print(tally)
Tally
ID = 1
Name = mesh tally
Filters = MeshFilter, EnergyFilter
Nuclides = total
Scores = ['fission', 'nu-fission']
Estimator = tracklength
Use the new Tally data retrieval API with pure NumPy
[19]:
# Get the relative error for the thermal fission reaction
# rates in the four corner pins
data = tally.get_values(scores=['fission'],
filters=[openmc.MeshFilter, openmc.EnergyFilter], \
filter_bins=[((1,1),(1,17), (17,1), (17,17)), \
((0., 0.625),)], value='rel_err')
print(data)
[[[0.04508259]]
[[0.0221707 ]]
[[0.10763375]]
[[0.05107401]]]
[20]:
# Get a pandas dataframe for the mesh tally data
df = tally.get_pandas_dataframe(nuclides=False)
# Set the Pandas float display settings
pd.options.display.float_format = '{:.2e}'.format
# Print the first twenty rows in the dataframe
df.head(20)
[20]:
mesh 1 | energy low [eV] | energy high [eV] | score | mean | std. dev. | |||
---|---|---|---|---|---|---|---|---|
x | y | z | ||||||
0 | 1 | 1 | 1 | 0.00e+00 | 6.25e-01 | fission | 2.27e-04 | 1.02e-05 |
1 | 1 | 1 | 1 | 0.00e+00 | 6.25e-01 | nu-fission | 5.54e-04 | 2.50e-05 |
2 | 1 | 1 | 1 | 6.25e-01 | 2.00e+07 | fission | 7.19e-05 | 1.82e-06 |
3 | 1 | 1 | 1 | 6.25e-01 | 2.00e+07 | nu-fission | 1.89e-04 | 4.69e-06 |
4 | 2 | 1 | 1 | 0.00e+00 | 6.25e-01 | fission | 2.35e-04 | 9.82e-06 |
5 | 2 | 1 | 1 | 0.00e+00 | 6.25e-01 | nu-fission | 5.71e-04 | 2.39e-05 |
6 | 2 | 1 | 1 | 6.25e-01 | 2.00e+07 | fission | 6.88e-05 | 1.61e-06 |
7 | 2 | 1 | 1 | 6.25e-01 | 2.00e+07 | nu-fission | 1.81e-04 | 4.15e-06 |
8 | 3 | 1 | 1 | 0.00e+00 | 6.25e-01 | fission | 2.31e-04 | 1.13e-05 |
9 | 3 | 1 | 1 | 0.00e+00 | 6.25e-01 | nu-fission | 5.63e-04 | 2.76e-05 |
10 | 3 | 1 | 1 | 6.25e-01 | 2.00e+07 | fission | 6.95e-05 | 1.76e-06 |
11 | 3 | 1 | 1 | 6.25e-01 | 2.00e+07 | nu-fission | 1.83e-04 | 4.53e-06 |
12 | 4 | 1 | 1 | 0.00e+00 | 6.25e-01 | fission | 2.07e-04 | 9.85e-06 |
13 | 4 | 1 | 1 | 0.00e+00 | 6.25e-01 | nu-fission | 5.04e-04 | 2.40e-05 |
14 | 4 | 1 | 1 | 6.25e-01 | 2.00e+07 | fission | 6.48e-05 | 1.45e-06 |
15 | 4 | 1 | 1 | 6.25e-01 | 2.00e+07 | nu-fission | 1.71e-04 | 3.81e-06 |
16 | 5 | 1 | 1 | 0.00e+00 | 6.25e-01 | fission | 2.20e-04 | 1.07e-05 |
17 | 5 | 1 | 1 | 0.00e+00 | 6.25e-01 | nu-fission | 5.37e-04 | 2.60e-05 |
18 | 5 | 1 | 1 | 6.25e-01 | 2.00e+07 | fission | 6.76e-05 | 1.78e-06 |
19 | 5 | 1 | 1 | 6.25e-01 | 2.00e+07 | nu-fission | 1.78e-04 | 4.63e-06 |
[21]:
# Create a boxplot to view the distribution of
# fission and nu-fission rates in the pins
bp = df.boxplot(column='mean', by='score')

[22]:
# Extract thermal nu-fission rates from pandas
fiss = df[df['score'] == 'nu-fission']
fiss = fiss[fiss['energy low [eV]'] == 0.0]
# Extract mean and reshape as 2D NumPy arrays
mean = fiss['mean'].values.reshape((17,17))
plt.imshow(mean, interpolation='nearest')
plt.title('fission rate')
plt.xlabel('x')
plt.ylabel('y')
plt.colorbar()
[22]:
<matplotlib.colorbar.Colorbar at 0x7fd070ca32b0>

Analyze the cell+nuclides scatter-y2 rate tally
[23]:
# Find the cell Tally with the StatePoint API
tally = sp.get_tally(name='cell tally')
# Print a little info about the cell tally to the screen
print(tally)
Tally
ID = 2
Name = cell tally
Filters = CellFilter
Nuclides = U235 U238
Scores = ['scatter']
Estimator = tracklength
[24]:
# Get a pandas dataframe for the cell tally data
df = tally.get_pandas_dataframe()
# Print the first twenty rows in the dataframe
df.head(20)
[24]:
cell | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|
0 | 1 | U235 | scatter | 3.81e-02 | 4.13e-05 |
1 | 1 | U238 | scatter | 2.34e+00 | 2.41e-03 |
Use the new Tally data retrieval API with pure NumPy
[25]:
# Get the standard deviations the total scattering rate
data = tally.get_values(scores=['scatter'],
nuclides=['U238', 'U235'], value='std_dev')
print(data)
[[[2.41367509e-03]
[4.12533801e-05]]]
Analyze the distribcell tally
[26]:
# Find the distribcell Tally with the StatePoint API
tally = sp.get_tally(name='distribcell tally')
# Print a little info about the distribcell tally to the screen
print(tally)
Tally
ID = 3
Name = distribcell tally
Filters = DistribcellFilter
Nuclides = total
Scores = ['absorption', 'scatter']
Estimator = tracklength
Use the new Tally data retrieval API with pure NumPy
[27]:
# Get the relative error for the scattering reaction rates in
# the first 10 distribcell instances
data = tally.get_values(scores=['scatter'], filters=[openmc.DistribcellFilter],
filter_bins=[tuple(range(10))], value='rel_err')
print(data)
[[[0.0131914 ]]
[[0.01252949]]
[[0.01241481]]
[[0.01194961]]
[[0.01186091]]
[[0.0127257 ]]
[[0.01358576]]
[[0.0130368 ]]
[[0.014031 ]]
[[0.0141883 ]]]
Print the distribcell tally dataframe
[28]:
# Get a pandas dataframe for the distribcell tally data
df = tally.get_pandas_dataframe(nuclides=False)
# Print the last twenty rows in the dataframe
df.tail(20)
[28]:
level 1 | level 2 | level 3 | distribcell | score | mean | std. dev. | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
univ | cell | lat | univ | cell | |||||||
id | id | id | x | y | id | id | |||||
558 | 3 | 4 | 2 | 7 | 16 | 1 | 3 | 279 | absorption | 7.11e-04 | 1.10e-05 |
559 | 3 | 4 | 2 | 7 | 16 | 1 | 3 | 279 | scatter | 8.91e-02 | 6.60e-04 |
560 | 3 | 4 | 2 | 8 | 16 | 1 | 3 | 280 | absorption | 6.75e-04 | 1.02e-05 |
561 | 3 | 4 | 2 | 8 | 16 | 1 | 3 | 280 | scatter | 8.35e-02 | 6.11e-04 |
562 | 3 | 4 | 2 | 9 | 16 | 1 | 3 | 281 | absorption | 6.10e-04 | 1.02e-05 |
563 | 3 | 4 | 2 | 9 | 16 | 1 | 3 | 281 | scatter | 7.75e-02 | 6.10e-04 |
564 | 3 | 4 | 2 | 10 | 16 | 1 | 3 | 282 | absorption | 5.67e-04 | 9.88e-06 |
565 | 3 | 4 | 2 | 10 | 16 | 1 | 3 | 282 | scatter | 7.11e-02 | 5.99e-04 |
566 | 3 | 4 | 2 | 11 | 16 | 1 | 3 | 283 | absorption | 5.06e-04 | 9.35e-06 |
567 | 3 | 4 | 2 | 11 | 16 | 1 | 3 | 283 | scatter | 6.39e-02 | 5.53e-04 |
568 | 3 | 4 | 2 | 12 | 16 | 1 | 3 | 284 | absorption | 4.35e-04 | 8.22e-06 |
569 | 3 | 4 | 2 | 12 | 16 | 1 | 3 | 284 | scatter | 5.62e-02 | 5.18e-04 |
570 | 3 | 4 | 2 | 13 | 16 | 1 | 3 | 285 | absorption | 3.73e-04 | 7.90e-06 |
571 | 3 | 4 | 2 | 13 | 16 | 1 | 3 | 285 | scatter | 4.76e-02 | 4.92e-04 |
572 | 3 | 4 | 2 | 14 | 16 | 1 | 3 | 286 | absorption | 2.98e-04 | 7.30e-06 |
573 | 3 | 4 | 2 | 14 | 16 | 1 | 3 | 286 | scatter | 3.82e-02 | 4.17e-04 |
574 | 3 | 4 | 2 | 15 | 16 | 1 | 3 | 287 | absorption | 2.05e-04 | 5.96e-06 |
575 | 3 | 4 | 2 | 15 | 16 | 1 | 3 | 287 | scatter | 2.86e-02 | 3.72e-04 |
576 | 3 | 4 | 2 | 16 | 16 | 1 | 3 | 288 | absorption | 1.22e-04 | 4.12e-06 |
577 | 3 | 4 | 2 | 16 | 16 | 1 | 3 | 288 | scatter | 1.82e-02 | 2.59e-04 |
[29]:
# Show summary statistics for absorption distribcell tally data
absorption = df[df['score'] == 'absorption']
absorption[['mean', 'std. dev.']].dropna().describe()
# Note that the maximum standard deviation does indeed
# meet the 5e-5 threshold set by the tally trigger
[29]:
mean | std. dev. | |
---|---|---|
count | 2.89e+02 | 2.89e+02 |
mean | 4.19e-04 | 6.86e-06 |
std | 2.41e-04 | 2.51e-06 |
min | 1.68e-05 | 1.07e-06 |
25% | 2.06e-04 | 5.09e-06 |
50% | 3.98e-04 | 6.90e-06 |
75% | 6.17e-04 | 8.44e-06 |
max | 8.70e-04 | 1.52e-05 |
Perform a statistical test comparing the tally sample distributions for two categories of fuel pins.
[30]:
# Extract tally data from pins in the pins divided along y=-x diagonal
multi_index = ('level 2', 'lat',)
lower = df[df[multi_index + ('x',)] + df[multi_index + ('y',)] < 16]
upper = df[df[multi_index + ('x',)] + df[multi_index + ('y',)] > 16]
lower = lower[lower['score'] == 'absorption']
upper = upper[upper['score'] == 'absorption']
# Perform non-parametric Mann-Whitney U Test to see if the
# absorption rates (may) come from same sampling distribution
u, p = scipy.stats.mannwhitneyu(lower['mean'], upper['mean'])
print('Mann-Whitney Test p-value: {0}'.format(p))
Mann-Whitney Test p-value: 0.47449458604689265
Note that the symmetry implied by the y=-x diagonal ensures that the two sampling distributions are identical. Indeed, as illustrated by the test above, for any reasonable significance level (e.g., \(\alpha\)=0.05) one would not reject the null hypothesis that the two sampling distributions are identical.
Next, perform the same test but with two groupings of pins which are not symmetrically identical to one another.
[31]:
# Extract tally data from pins in the pins divided along y=x diagonal
multi_index = ('level 2', 'lat',)
lower = df[df[multi_index + ('x',)] > df[multi_index + ('y',)]]
upper = df[df[multi_index + ('x',)] < df[multi_index + ('y',)]]
lower = lower[lower['score'] == 'absorption']
upper = upper[upper['score'] == 'absorption']
# Perform non-parametric Mann-Whitney U Test to see if the
# absorption rates (may) come from same sampling distribution
u, p = scipy.stats.mannwhitneyu(lower['mean'], upper['mean'])
print('Mann-Whitney Test p-value: {0}'.format(p))
Mann-Whitney Test p-value: 2.499381683224802e-42
Note that the asymmetry implied by the y=x diagonal ensures that the two sampling distributions are not identical. Indeed, as illustrated by the test above, for any reasonable significance level (e.g., \(\alpha\)=0.05) one would reject the null hypothesis that the two sampling distributions are identical.
[32]:
# Extract the scatter tally data from pandas
scatter = df[df['score'] == 'scatter']
scatter['rel. err.'] = scatter['std. dev.'] / scatter['mean']
# Show a scatter plot of the mean vs. the std. dev.
scatter.plot(kind='scatter', x='mean', y='rel. err.', title='Scattering Rates')
<ipython-input-32-c935bd379fee>:4: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
scatter['rel. err.'] = scatter['std. dev.'] / scatter['mean']
[32]:
<AxesSubplot:title={'center':'Scattering Rates'}, xlabel='mean', ylabel='rel. err.'>

[33]:
# Plot a histogram and kernel density estimate for the scattering rates
scatter['mean'].plot(kind='hist', bins=25)
scatter['mean'].plot(kind='kde')
plt.title('Scattering Rates')
plt.xlabel('Mean')
plt.legend(['KDE', 'Histogram'])
[33]:
<matplotlib.legend.Legend at 0x7fd070fbd1f0>

Tally Arithmetic¶
This notebook shows the how tallies can be combined (added, subtracted, multiplied, etc.) using the Python API in order to create derived tallies. Since no covariance information is obtained, it is assumed that tallies are completely independent of one another when propagating uncertainties. The target problem is a simple pin cell.
[1]:
import glob
from IPython.display import Image
import numpy as np
import openmc
Generate Input Files¶
First we need to define materials that will be used in the problem. We’ll create three materials for the fuel, water, and cladding of the fuel pin.
[2]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
water.add_nuclide('B10', 8.0042e-6)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our three materials, we can now create a materials file object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials collection
materials_file = openmc.Materials([fuel, water, zircaloy])
# Export to "materials.xml"
materials_file.export_to_xml()
Now let’s move on to the geometry. Our problem will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces – in this case two cylinders and six planes.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.45720)
# Create boundary planes to surround the geometry
# Use both reflective and vacuum boundaries to make life interesting
min_x = openmc.XPlane(x0=-0.63, boundary_type='reflective')
max_x = openmc.XPlane(x0=+0.63, boundary_type='reflective')
min_y = openmc.YPlane(y0=-0.63, boundary_type='reflective')
max_y = openmc.YPlane(y0=+0.63, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-100., boundary_type='vacuum')
max_z = openmc.ZPlane(z0=+100., boundary_type='vacuum')
With the surfaces defined, we can now create cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
pin_cell_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
pin_cell_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
pin_cell_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
pin_cell_universe.add_cell(moderator_cell)
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe.
[6]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = pin_cell_universe
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(universe_id=0, name='root universe')
root_universe.add_cell(root_cell)
We now must create a geometry that is assigned a root universe, put the geometry into a geometry file, and export it to XML.
[7]:
# Create Geometry and set root Universe
geometry = openmc.Geometry(root_universe)
[8]:
# Export to "geometry.xml"
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 5 inactive batches and 15 active batches each with 2500 particles.
[9]:
# OpenMC simulation parameters
batches = 20
inactive = 5
particles = 2500
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': True}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -100., 0.63, 0.63, 100.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Let us also create a plot file that we can use to verify that our pin cell geometry was created successfully.
[10]:
# Instantiate a Plot
plot = openmc.Plot(plot_id=1)
plot.filename = 'materials-xy'
plot.origin = [0, 0, 0]
plot.width = [1.26, 1.26]
plot.pixels = [250, 250]
plot.color_by = 'material'
# Show plot
openmc.plot_inline(plot)

As we can see from the plot, we have a nice pin cell with fuel, cladding, and water! Before we run our simulation, we need to tell the code what we want to tally. The following code shows how to create a variety of tallies.
[11]:
# Instantiate an empty Tallies object
tallies_file = openmc.Tallies()
[12]:
# Create Tallies to compute microscopic multi-group cross-sections
# Instantiate energy filter for multi-group cross-section Tallies
energy_filter = openmc.EnergyFilter([0., 0.625, 20.0e6])
# Instantiate flux Tally in moderator and fuel
tally = openmc.Tally(name='flux')
tally.filters = [openmc.CellFilter([fuel_cell, moderator_cell])]
tally.filters.append(energy_filter)
tally.scores = ['flux']
tallies_file.append(tally)
# Instantiate reaction rate Tally in fuel
tally = openmc.Tally(name='fuel rxn rates')
tally.filters = [openmc.CellFilter(fuel_cell)]
tally.filters.append(energy_filter)
tally.scores = ['nu-fission', 'scatter']
tally.nuclides = ['U238', 'U235']
tallies_file.append(tally)
# Instantiate reaction rate Tally in moderator
tally = openmc.Tally(name='moderator rxn rates')
tally.filters = [openmc.CellFilter(moderator_cell)]
tally.filters.append(energy_filter)
tally.scores = ['absorption', 'total']
tally.nuclides = ['O16', 'H1']
tallies_file.append(tally)
# Instantiate a tally mesh
mesh = openmc.RegularMesh(mesh_id=1)
mesh.dimension = [1, 1, 1]
mesh.lower_left = [-0.63, -0.63, -100.]
mesh.width = [1.26, 1.26, 200.]
meshsurface_filter = openmc.MeshSurfaceFilter(mesh)
# Instantiate thermal, fast, and total leakage tallies
leak = openmc.Tally(name='leakage')
leak.filters = [meshsurface_filter]
leak.scores = ['current']
tallies_file.append(leak)
thermal_leak = openmc.Tally(name='thermal leakage')
thermal_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0., 0.625])]
thermal_leak.scores = ['current']
tallies_file.append(thermal_leak)
fast_leak = openmc.Tally(name='fast leakage')
fast_leak.filters = [meshsurface_filter, openmc.EnergyFilter([0.625, 20.0e6])]
fast_leak.scores = ['current']
tallies_file.append(fast_leak)
[13]:
# K-Eigenvalue (infinity) tallies
fiss_rate = openmc.Tally(name='fiss. rate')
abs_rate = openmc.Tally(name='abs. rate')
fiss_rate.scores = ['nu-fission']
abs_rate.scores = ['absorption']
tallies_file += (fiss_rate, abs_rate)
[14]:
# Resonance Escape Probability tallies
therm_abs_rate = openmc.Tally(name='therm. abs. rate')
therm_abs_rate.scores = ['absorption']
therm_abs_rate.filters = [openmc.EnergyFilter([0., 0.625])]
tallies_file.append(therm_abs_rate)
[15]:
# Thermal Flux Utilization tallies
fuel_therm_abs_rate = openmc.Tally(name='fuel therm. abs. rate')
fuel_therm_abs_rate.scores = ['absorption']
fuel_therm_abs_rate.filters = [openmc.EnergyFilter([0., 0.625]),
openmc.CellFilter([fuel_cell])]
tallies_file.append(fuel_therm_abs_rate)
[16]:
# Fast Fission Factor tallies
therm_fiss_rate = openmc.Tally(name='therm. fiss. rate')
therm_fiss_rate.scores = ['nu-fission']
therm_fiss_rate.filters = [openmc.EnergyFilter([0., 0.625])]
tallies_file.append(therm_fiss_rate)
[17]:
# Instantiate energy filter to illustrate Tally slicing
fine_energy_filter = openmc.EnergyFilter(np.logspace(np.log10(1e-2), np.log10(20.0e6), 10))
# Instantiate flux Tally in moderator and fuel
tally = openmc.Tally(name='need-to-slice')
tally.filters = [openmc.CellFilter([fuel_cell, moderator_cell])]
tally.filters.append(fine_energy_filter)
tally.scores = ['nu-fission', 'scatter']
tally.nuclides = ['H1', 'U238']
tallies_file.append(tally)
[18]:
# Export to "tallies.xml"
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=6.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[19]:
# Run OpenMC!
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-18 22:51:02
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading B10 from /opt/data/hdf5/nndc_hdf5_v15/B10.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 0.96168
2/1 0.96651
3/1 1.00678
4/1 0.98773
5/1 1.01883
6/1 1.02959
7/1 0.99859 1.01409 +/- 0.01550
8/1 1.03441 1.02086 +/- 0.01123
9/1 1.06097 1.03089 +/- 0.01279
10/1 1.06132 1.03698 +/- 0.01163
11/1 1.04687 1.03863 +/- 0.00964
12/1 1.02982 1.03737 +/- 0.00824
13/1 1.03520 1.03710 +/- 0.00714
14/1 0.99508 1.03243 +/- 0.00784
15/1 1.03987 1.03317 +/- 0.00705
16/1 1.02743 1.03265 +/- 0.00640
17/1 1.02975 1.03241 +/- 0.00585
18/1 0.99671 1.02966 +/- 0.00604
19/1 1.02040 1.02900 +/- 0.00563
20/1 1.02024 1.02842 +/- 0.00527
Creating state point statepoint.20.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 3.4427e-01 seconds
Reading cross sections = 3.1628e-01 seconds
Total time in simulation = 3.7319e+00 seconds
Time in transport only = 3.6302e+00 seconds
Time in inactive batches = 4.9601e-01 seconds
Time in active batches = 3.2359e+00 seconds
Time synchronizing fission bank = 2.8100e-03 seconds
Sampling source sites = 2.4682e-03 seconds
SEND/RECV source sites = 3.2484e-04 seconds
Time accumulating tallies = 4.4538e-05 seconds
Total time for finalization = 9.3656e-04 seconds
Total time elapsed = 4.0859e+00 seconds
Calculation Rate (inactive) = 25201.2 particles/second
Calculation Rate (active) = 11588.7 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.02889 +/- 0.00492
k-effective (Track-length) = 1.02842 +/- 0.00527
k-effective (Absorption) = 1.02637 +/- 0.00349
Combined k-effective = 1.02700 +/- 0.00291
Leakage Fraction = 0.01717 +/- 0.00107
Tally Data Processing¶
Our simulation ran successfully and created a statepoint file with all the tally data in it. We begin our analysis here loading the statepoint file and ‘reading’ the results. By default, the tally results are not read into memory because they might be large, even large enough to exceed the available memory on a computer.
[20]:
# Load the statepoint file
sp = openmc.StatePoint('statepoint.20.h5')
We have a tally of the total fission rate and the total absorption rate, so we can calculate k-eff as:
[21]:
# Get the fission and absorption rate tallies
fiss_rate = sp.get_tally(name='fiss. rate')
abs_rate = sp.get_tally(name='abs. rate')
# Get the leakage tally
leak = sp.get_tally(name='leakage')
leak = leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)
# Compute k-infinity using tally arithmetic
keff = fiss_rate / (abs_rate + leak)
keff.get_pandas_dataframe()
[21]:
nuclide | score | mean | std. dev. | |
---|---|---|---|---|
0 | total | (nu-fission / (absorption + current)) | 1.023002 | 0.006647 |
Notice that even though the neutron production rate, absorption rate, and current are separate tallies, we still get a first-order estimate of the uncertainty on the quotient of them automatically!
Often in textbooks you’ll see k-eff represented using the six-factor formula
Let’s analyze each of these factors, starting with the resonance escape probability which is defined as
[22]:
# Compute resonance escape probability using tally arithmetic
therm_abs_rate = sp.get_tally(name='therm. abs. rate')
thermal_leak = sp.get_tally(name='thermal leakage')
thermal_leak = thermal_leak.summation(filter_type=openmc.MeshSurfaceFilter, remove_filter=True)
res_esc = (therm_abs_rate + thermal_leak) / (abs_rate + thermal_leak)
res_esc.get_pandas_dataframe()
[22]:
energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | total | ((absorption + current) / (absorption + current)) | 0.694368 | 0.004606 |
The fast fission factor can be calculated as
[23]:
# Compute fast fission factor factor using tally arithmetic
therm_fiss_rate = sp.get_tally(name='therm. fiss. rate')
fast_fiss = fiss_rate / therm_fiss_rate
fast_fiss.get_pandas_dataframe()
[23]:
energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | total | (nu-fission / nu-fission) | 1.203099 | 0.009615 |
The thermal flux utilization is calculated as
[24]:
# Compute thermal flux utilization factor using tally arithmetic
fuel_therm_abs_rate = sp.get_tally(name='fuel therm. abs. rate')
therm_util = fuel_therm_abs_rate / therm_abs_rate
therm_util.get_pandas_dataframe()
[24]:
energy low [eV] | energy high [eV] | cell | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | 1 | total | (absorption / absorption) | 0.749423 | 0.006089 |
The next factor is the number of fission neutrons produced per absorption in fuel, calculated as
[25]:
# Compute neutrons produced per absorption (eta) using tally arithmetic
eta = therm_fiss_rate / fuel_therm_abs_rate
eta.get_pandas_dataframe()
[25]:
energy low [eV] | energy high [eV] | cell | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | 1 | total | (nu-fission / absorption) | 1.663727 | 0.014403 |
There are two leakage factors to account for fast and thermal leakage. The fast non-leakage probability is computed as
[26]:
p_fnl = (abs_rate + thermal_leak) / (abs_rate + leak)
p_fnl.get_pandas_dataframe()
[26]:
energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | total | ((absorption + current) / (absorption + current)) | 0.984668 | 0.005509 |
The final factor is the thermal non-leakage probability and is computed as
[27]:
p_tnl = therm_abs_rate / (therm_abs_rate + thermal_leak)
p_tnl.get_pandas_dataframe()
[27]:
energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | total | (absorption / (absorption + current)) | 0.997439 | 0.007548 |
Now we can calculate \(k_{eff}\) using the product of the factors form the four-factor formula.
[28]:
keff = res_esc * fast_fiss * therm_util * eta * p_fnl * p_tnl
keff.get_pandas_dataframe()
[28]:
energy low [eV] | energy high [eV] | cell | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 0.0 | 0.625 | 1 | total | (((((((absorption + current) / (absorption + c... | 1.023002 | 0.018791 |
We see that the value we’ve obtained here has exactly the same mean as before. However, because of the way it was calculated, the standard deviation appears to be larger.
Let’s move on to a more complicated example now. Before we set up tallies to get reaction rates in the fuel and moderator in two energy groups for two different nuclides. We can use tally arithmetic to divide each of these reaction rates by the flux to get microscopic multi-group cross sections.
[29]:
# Compute microscopic multi-group cross-sections
flux = sp.get_tally(name='flux')
flux = flux.get_slice(filters=[openmc.CellFilter], filter_bins=[(fuel_cell.id,)])
fuel_rxn_rates = sp.get_tally(name='fuel rxn rates')
mod_rxn_rates = sp.get_tally(name='moderator rxn rates')
[30]:
fuel_xs = fuel_rxn_rates / flux
fuel_xs.get_pandas_dataframe()
[30]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | (U238 / total) | (nu-fission / flux) | 6.659486e-07 | 5.627975e-09 |
1 | 1 | 0.000 | 6.250000e-01 | (U238 / total) | (scatter / flux) | 2.099901e-01 | 1.748379e-03 |
2 | 1 | 0.000 | 6.250000e-01 | (U235 / total) | (nu-fission / flux) | 3.566329e-01 | 3.030782e-03 |
3 | 1 | 0.000 | 6.250000e-01 | (U235 / total) | (scatter / flux) | 5.555466e-03 | 4.635318e-05 |
4 | 1 | 0.625 | 2.000000e+07 | (U238 / total) | (nu-fission / flux) | 7.251304e-03 | 5.161998e-05 |
5 | 1 | 0.625 | 2.000000e+07 | (U238 / total) | (scatter / flux) | 2.272661e-01 | 9.576939e-04 |
6 | 1 | 0.625 | 2.000000e+07 | (U235 / total) | (nu-fission / flux) | 7.920169e-03 | 5.751231e-05 |
7 | 1 | 0.625 | 2.000000e+07 | (U235 / total) | (scatter / flux) | 3.358280e-03 | 1.341281e-05 |
We see that when the two tallies with multiple bins were divided, the derived tally contains the outer product of the combinations. If the filters/scores are the same, no outer product is needed. The get_values(...)
method allows us to obtain a subset of tally scores. In the following example, we obtain just the neutron production microscopic cross sections.
[31]:
# Show how to use Tally.get_values(...) with a CrossScore
nu_fiss_xs = fuel_xs.get_values(scores=['(nu-fission / flux)'])
print(nu_fiss_xs)
[[[6.65948580e-07]
[3.56632881e-01]]
[[7.25130446e-03]
[7.92016892e-03]]]
The same idea can be used not only for scores but also for filters and nuclides.
[32]:
# Show how to use Tally.get_values(...) with a CrossScore and CrossNuclide
u235_scatter_xs = fuel_xs.get_values(nuclides=['(U235 / total)'],
scores=['(scatter / flux)'])
print(u235_scatter_xs)
[[[0.00555547]]
[[0.00335828]]]
[33]:
# Show how to use Tally.get_values(...) with a CrossFilter and CrossScore
fast_scatter_xs = fuel_xs.get_values(filters=[openmc.EnergyFilter],
filter_bins=[((0.625, 20.0e6),)],
scores=['(scatter / flux)'])
print(fast_scatter_xs)
[[[0.22726611]
[0.00335828]]]
A more advanced method is to use get_slice(...)
to create a new derived tally that is a subset of an existing tally. This has the benefit that we can use get_pandas_dataframe()
to see the tallies in a more human-readable format.
[34]:
# "Slice" the nu-fission data into a new derived Tally
nu_fission_rates = fuel_rxn_rates.get_slice(scores=['nu-fission'])
nu_fission_rates.get_pandas_dataframe()
[34]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | U238 | nu-fission | 0.000002 | 9.679304e-09 |
1 | 1 | 0.000 | 6.250000e-01 | U235 | nu-fission | 0.854805 | 5.239673e-03 |
2 | 1 | 0.625 | 2.000000e+07 | U238 | nu-fission | 0.082978 | 5.346135e-04 |
3 | 1 | 0.625 | 2.000000e+07 | U235 | nu-fission | 0.090632 | 5.981942e-04 |
[35]:
# "Slice" the H-1 scatter data in the moderator Cell into a new derived Tally
need_to_slice = sp.get_tally(name='need-to-slice')
slice_test = need_to_slice.get_slice(scores=['scatter'], nuclides=['H1'],
filters=[openmc.CellFilter], filter_bins=[(moderator_cell.id,)])
slice_test.get_pandas_dataframe()
[35]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 3 | 1.000000e-02 | 1.080060e-01 | H1 | scatter | 4.541188 | 0.025230 |
1 | 3 | 1.080060e-01 | 1.166529e+00 | H1 | scatter | 2.001332 | 0.006754 |
2 | 3 | 1.166529e+00 | 1.259921e+01 | H1 | scatter | 1.639292 | 0.011374 |
3 | 3 | 1.259921e+01 | 1.360790e+02 | H1 | scatter | 1.821633 | 0.009590 |
4 | 3 | 1.360790e+02 | 1.469734e+03 | H1 | scatter | 2.032395 | 0.009953 |
5 | 3 | 1.469734e+03 | 1.587401e+04 | H1 | scatter | 2.120745 | 0.011090 |
6 | 3 | 1.587401e+04 | 1.714488e+05 | H1 | scatter | 2.181709 | 0.013602 |
7 | 3 | 1.714488e+05 | 1.851749e+06 | H1 | scatter | 2.013644 | 0.009219 |
8 | 3 | 1.851749e+06 | 2.000000e+07 | H1 | scatter | 0.372640 | 0.002903 |
Using the C/C++ API¶
This notebook shows how to use the OpenMC C/C++ API through the openmc.lib module. This module is particularly useful for multiphysics coupling because it allows you to update the density of materials and the temperatures of cells in memory, without stopping the simulation.
Warning: these bindings are still somewhat experimental and may be subject to change in future versions of OpenMC.
[1]:
%matplotlib inline
import openmc
import openmc.lib
Generate Input Files
Let’s start by creating a fuel rod geometry. We will make 10 zones in the z-direction which will allow us to make changes to each zone. Changes in temperature have to be made on the cell, so will make 10 cells in the axial direction. Changes in density have to be made on the material, so we will make 10 water materials.
Materials: we will make a fuel, helium, zircaloy, and 10 water materials.
[2]:
material_list = []
[3]:
uo2 = openmc.Material(material_id=1, name='UO2 fuel at 2.4% wt enrichment')
uo2.set_density('g/cm3', 10.29769)
uo2.add_element('U', 1., enrichment=2.4)
uo2.add_element('O', 2.)
material_list.append(uo2)
helium = openmc.Material(material_id=2, name='Helium for gap')
helium.set_density('g/cm3', 0.001598)
helium.add_element('He', 2.4044e-4)
material_list.append(helium)
zircaloy = openmc.Material(material_id=3, name='Zircaloy 4')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_element('Sn', 0.014, 'wo')
zircaloy.add_element('Fe', 0.00165, 'wo')
zircaloy.add_element('Cr', 0.001, 'wo')
zircaloy.add_element('Zr', 0.98335, 'wo')
material_list.append(zircaloy)
for i in range(4, 14):
water = openmc.Material(material_id=i)
water.set_density('g/cm3', 0.7)
water.add_element('H', 2.0)
water.add_element('O', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')
material_list.append(water)
materials_file = openmc.Materials(material_list)
materials_file.export_to_xml()
Cells: we will make a fuel cylinder, a gap cylinder, a cladding cylinder, and a water exterior. Each one will be broken into 10 cells which are the 10 axial zones. The z_list is the list of axial positions that delimit those 10 zones. To keep track of all the cells, we will create lists: fuel_list, gap_list, clad_list, and water_list.
[4]:
pitch = 1.25984
fuel_or = openmc.ZCylinder(r=0.39218)
clad_ir = openmc.ZCylinder(r=0.40005)
clad_or = openmc.ZCylinder(r=0.4572)
left = openmc.XPlane(x0=-pitch/2)
right = openmc.XPlane(x0=pitch/2)
back = openmc.YPlane(y0=-pitch/2)
front = openmc.YPlane(y0=pitch/2)
z = [0., 30., 60., 90., 120., 150., 180., 210., 240., 270., 300.]
z_list = [openmc.ZPlane(z0=z_i) for z_i in z]
[5]:
left.boundary_type = 'reflective'
right.boundary_type = 'reflective'
front.boundary_type = 'reflective'
back.boundary_type = 'reflective'
z_list[0].boundary_type = 'vacuum'
z_list[-1].boundary_type = 'vacuum'
[6]:
fuel_list = []
gap_list = []
clad_list = []
water_list = []
for i in range(1, 11):
fuel_list.append(openmc.Cell(cell_id=i))
gap_list.append(openmc.Cell(cell_id=i+10))
clad_list.append(openmc.Cell(cell_id=i+20))
water_list.append(openmc.Cell(cell_id=i+30))
for j, fuels in enumerate(fuel_list):
fuels.region = -fuel_or & +z_list[j] & -z_list[j+1]
fuels.fill = uo2
fuels.temperature = 800.
for j, gaps in enumerate(gap_list):
gaps.region = +fuel_or & -clad_ir & +z_list[j] & -z_list[j+1]
gaps.fill = helium
gaps.temperature = 700.
for j, clads in enumerate(clad_list):
clads.region = +clad_ir & -clad_or & +z_list[j] & -z_list[j+1]
clads.fill = zircaloy
clads.temperature = 600.
for j, waters in enumerate(water_list):
waters.region = +clad_or & +left & -right & +back & -front & +z_list[j] & -z_list[j+1]
waters.fill = material_list[j+3]
waters.temperature = 500.
[7]:
root = openmc.Universe(name='root universe')
root.add_cells(fuel_list)
root.add_cells(gap_list)
root.add_cells(clad_list)
root.add_cells(water_list)
geometry_file = openmc.Geometry(root)
geometry_file.export_to_xml()
If you are coupling this externally to a heat transfer solver, you will want to know the heat deposited by each fuel cell. So let’s create a cell filter for the recoverable fission heat.
[8]:
cell_filter = openmc.CellFilter(fuel_list)
t = openmc.Tally(tally_id=1)
t.filters.append(cell_filter)
t.scores = ['fission-q-recoverable']
tallies = openmc.Tallies([t])
tallies.export_to_xml()
Let’s plot our geometry to make sure it looks like we expect. Since we made new water materials in each axial cell, and we have centered the plot at 150, we should see one color for the water material in the bottom half and a different color for the water material in the top half.
[19]:
root.plot(basis='yz', width=[2, 10], color_by='material', origin=[0., 0., 150.], pixels=[400, 400])
[19]:
<matplotlib.image.AxesImage at 0x126d642e0>

Settings: everything will be standard except for the temperature settings. Since we will be working with specified temperatures, you will need temperature dependent data. I typically use the endf data found here: https://openmc.org/official-data-libraries/ Make sure your cross sections environment variable is pointing to temperature-dependent data before using the following settings.
[13]:
lower_left = [-0.62992, -pitch/2, 0]
upper_right = [+0.62992, +pitch/2, +300]
uniform_dist = openmc.stats.Box(lower_left, upper_right, only_fissionable=True)
settings_file = openmc.Settings()
settings_file.batches = 100
settings_file.inactive = 10
settings_file.particles = 10000
settings_file.temperature = {'multipole': True, 'method': 'interpolation', 'range': [290, 2500]}
settings_file.source = openmc.source.Source(space=uniform_dist)
settings_file.export_to_xml()
To run a regular simulation, just use openmc.run(). However, we want to run a simulation that we can stop in the middle and update the material and cell properties. So we will use openmc.lib.
[14]:
openmc.lib.init()
openmc.lib.simulation_init()
There are 10 inactive batches, so we need to run next_batch() at least 10 times before the tally is activated.
[15]:
for _ in range(14):
openmc.lib.next_batch()
Let’s take a look at the tally. There are 10 entries, one for each cell in the fuel.
[16]:
t = openmc.lib.tallies[1]
print(t.mean)
[[ 4178272.4202991 ]
[ 9595363.82759911]
[12307462.30060902]
[11772927.66594472]
[11892601.29001472]
[12203397.88895767]
[12851791.20965905]
[11760027.45873386]
[ 9293110.94735569]
[ 4511597.61592287]]
Now, let’s make some changes to the temperatures. For this, we need to identify each cell by its id. We can use get_temperature() to compare the temperatures of the cells before and after the change.
[17]:
print("fuel temperature is: ")
print(openmc.lib.cells[5].get_temperature())
print("gap temperature is: ")
print(openmc.lib.cells[15].get_temperature())
print("clad temperature is: ")
print(openmc.lib.cells[25].get_temperature())
print("water temperature is: ")
print(openmc.lib.cells[35].get_temperature())
fuel temperature is:
800.0
gap temperature is:
700.0
clad temperature is:
600.0
water temperature is:
500.00000000000006
[18]:
for i in range(1, 11):
temp = 900.0
openmc.lib.cells[i].set_temperature(temp)
[19]:
print("fuel temperature is: ")
print(openmc.lib.cells[5].get_temperature())
fuel temperature is:
899.9999999999999
Let’s make a similar change for the water density. Again, we need to identify each material by its id.
[20]:
for i in range(4, 14):
density = 0.65
openmc.lib.materials[i].set_density(density, units='g/cm3')
The new batches we run will use the new material and cell properties.
[21]:
for _ in range(14):
openmc.lib.next_batch()
When you’re ready to end the simulation, use the following:
[22]:
openmc.lib.simulation_finalize()
openmc.lib.finalize()
Functional Expansions¶
OpenMC’s general tally system accommodates a wide range of tally filters. While most filters are meant to identify regions of phase space that contribute to a tally, there are a special set of functional expansion filters that will multiply the tally by a set of orthogonal functions, e.g. Legendre polynomials, so that continuous functions of space or angle can be reconstructed from the tallied moments.
In this example, we will determine the spatial dependence of the flux along the \(z\) axis by making a Legendre polynomial expansion. Let us represent the flux along the z axis, \(\phi(z)\), by the function
where \(z'\) is the position normalized to the range [-1, 1]. Since \(P_n(z')\) are known functions, our only task is to determine the expansion coefficients, \(a_n\). By the orthogonality properties of the Legendre polynomials, one can deduce that the coefficients, \(a_n\), are given by
Thus, the problem reduces to finding the integral of the flux times each Legendre polynomial – a problem which can be solved by using a Monte Carlo tally. By using a Legendre polynomial filter, we obtain stochastic estimates of these integrals for each polynomial order.
[1]:
%matplotlib inline
import openmc
import numpy as np
import matplotlib.pyplot as plt
To begin, let us first create a simple model. The model will be a slab of fuel material with reflective boundaries conditions in the x- and y-directions and vacuum boundaries in the z-direction. However, to make the distribution slightly more interesting, we’ll put some B4C in the middle of the slab.
[2]:
# Define fuel and B4C materials
fuel = openmc.Material()
fuel.add_element('U', 1.0, enrichment=4.5)
fuel.add_nuclide('O16', 2.0)
fuel.set_density('g/cm3', 10.0)
b4c = openmc.Material()
b4c.add_element('B', 4.0)
b4c.add_element('C', 1.0)
b4c.set_density('g/cm3', 2.5)
[3]:
# Define surfaces used to construct regions
zmin, zmax = -10., 10.
box = openmc.model.rectangular_prism(10., 10., boundary_type='reflective')
bottom = openmc.ZPlane(z0=zmin, boundary_type='vacuum')
boron_lower = openmc.ZPlane(z0=-0.5)
boron_upper = openmc.ZPlane(z0=0.5)
top = openmc.ZPlane(z0=zmax, boundary_type='vacuum')
# Create three cells and add them to geometry
fuel1 = openmc.Cell(fill=fuel, region=box & +bottom & -boron_lower)
absorber = openmc.Cell(fill=b4c, region=box & +boron_lower & -boron_upper)
fuel2 = openmc.Cell(fill=fuel, region=box & +boron_upper & -top)
geom = openmc.Geometry([fuel1, absorber, fuel2])
For the starting source, we’ll use a uniform distribution over the entire box geometry.
[4]:
settings = openmc.Settings()
spatial_dist = openmc.stats.Box(*geom.bounding_box)
settings.source = openmc.Source(space=spatial_dist)
settings.batches = 210
settings.inactive = 10
settings.particles = 1000
Defining the tally is relatively straightforward. One simply needs to list ‘flux’ as a score and then add an expansion filter. For this case, we will want to use the SpatialLegendreFilter
class which multiplies tally scores by Legendre polynomials evaluated on normalized spatial positions along an axis.
[5]:
# Create a flux tally
flux_tally = openmc.Tally()
flux_tally.scores = ['flux']
# Create a Legendre polynomial expansion filter and add to tally
order = 8
expand_filter = openmc.SpatialLegendreFilter(order, 'z', zmin, zmax)
flux_tally.filters.append(expand_filter)
The last thing we need to do is create a Tallies
collection and export the entire model, which we’ll do using the Model
convenience class.
[6]:
tallies = openmc.Tallies([flux_tally])
model = openmc.model.Model(geometry=geom, settings=settings, tallies=tallies)
Running a simulation is now as simple as calling the run()
method of Model
.
[7]:
sp_file = model.run(output=False)
Now that the run is finished, we need to load the results from the statepoint file.
[8]:
with openmc.StatePoint(sp_file) as sp:
df = sp.tallies[flux_tally.id].get_pandas_dataframe()
We’ve used the get_pandas_dataframe()
method that returns tally data as a Pandas dataframe. Let’s see what the raw data looks like.
[9]:
df
[9]:
spatiallegendre | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|
0 | P0 | total | flux | 36.523601 | 0.081540 |
1 | P1 | total | flux | -0.002830 | 0.041466 |
2 | P2 | total | flux | -4.411923 | 0.027161 |
3 | P3 | total | flux | 0.004316 | 0.020245 |
4 | P4 | total | flux | -0.277281 | 0.014558 |
5 | P5 | total | flux | 0.010604 | 0.011350 |
6 | P6 | total | flux | 0.109212 | 0.010280 |
7 | P7 | total | flux | -0.002705 | 0.009100 |
8 | P8 | total | flux | -0.088469 | 0.007889 |
Since the expansion coefficients are given as
we just need to multiply the Legendre moments by \((2n + 1)/2\).
[10]:
n = np.arange(order + 1)
a_n = (2*n + 1)/2 * df['mean']
To plot the flux distribution, we can use the numpy.polynomial.Legendre
class which represents a truncated Legendre polynomial series. Since we really want to plot \(\phi(z)\) and not \(\phi(z')\) we first need to perform a change of variables. Since
and, for this case, \(z = 10z'\), it follows that
[11]:
phi = np.polynomial.Legendre(a_n/10, domain=(zmin, zmax))
Let’s plot it and see how our flux looks!
[12]:
z = np.linspace(zmin, zmax, 1000)
plt.plot(z, phi(z))
plt.xlabel('Z position [cm]')
plt.ylabel('Flux [n/src]')
[12]:
Text(0, 0.5, 'Flux [n/src]')

As you might expect, we get a rough cosine shape but with a flux depression in the middle due to the boron slab that we introduced. To get a more accurate distribution, we’d likely need to use a higher order expansion.
One more thing we can do is confirm that integrating the distribution gives us the same value as the first moment (since \(P_0(z') = 1\)). This can easily be done by numerically integrating using the trapezoidal rule:
[13]:
np.trapz(phi(z), z)
[13]:
36.523562389125146
In addition to being able to tally Legendre moments, there are also functional expansion filters available for spherical harmonics (SphericalHarmonicsFilter
) and Zernike polynomials over a unit disk (ZernikeFilter
). A separate LegendreFilter
class can also be used for determining Legendre scattering moments (i.e., an expansion of the scattering cosine, \(\mu\)).
Zernike polynomials¶
Now let’s look at an example of functional expansion tallies using Zernike polynomials as the basis functions.
In this example, we will determine the spatial dependence of the flux along the radial direction \(r'\) and \(/\) or azimuthal angle \(\theta\) by making a Zernike polynomial expansion. Let us represent the flux along the radial and azimuthal direction, \(\phi(r', \theta)\), by the function
where \(r'\) is the position normalized to the range [0, r] (r is the radius of cylindrical geometry), and the azimuthal lies within the range [0, $ 2:nbsphinx-math:`pi`$].
Since \(Z_n^m(r', \theta)\) are known functions, we need to determine the expansion coefficients, \(a_n^m\). By the orthogonality properties of the Zernike polynomials, one can deduce that the coefficients, \(a_n^m\), are given by
Similarly, the problem reduces to finding the integral of the flux times each Zernike polynomial.
To begin with, let us first create a simple model. The model will be a pin-cell fuel material with vacuum boundary condition in both radial direction and axial direction.
[14]:
# Define fuel
fuel = openmc.Material()
fuel.add_element('U', 1.0, enrichment=5.0)
fuel.add_nuclide('O16', 2.0)
fuel.set_density('g/cm3', 10.0)
[15]:
# Define surfaces used to construct regions
zmin, zmax, radius = -1., 1., 0.5
pin = openmc.ZCylinder(x0=0.0, y0=0.0, r=radius, boundary_type='vacuum')
bottom = openmc.ZPlane(z0=zmin, boundary_type='vacuum')
top = openmc.ZPlane(z0=zmax, boundary_type='vacuum')
# Create three cells and add them to geometry
fuel = openmc.Cell(fill=fuel, region= -pin & +bottom & -top)
geom = openmc.Geometry([fuel])
For the starting source, we’ll use a uniform distribution over the entire box geometry.
[16]:
settings = openmc.Settings()
spatial_dist = openmc.stats.Box(*geom.bounding_box)
settings.source = openmc.Source(space=spatial_dist)
settings.batches = 100
settings.inactive = 20
settings.particles = 100000
Defining the tally is relatively straightforward. One simply needs to list ‘flux’ as a score and then add an expansion filter. For this case, we will want to use the SpatialLegendreFilter
, ZernikeFilter
, ZernikeRadialFilter
classes which multiplies tally scores by Legendre, azimuthal Zernike and radial-only Zernike polynomials evaluated on normalized spatial positions along radial and axial directions.
[17]:
# Create a flux tally
flux_tally_legendre = openmc.Tally()
flux_tally_legendre.scores = ['flux']
# Create a Legendre polynomial expansion filter and add to tally
order = 10
cell_filter = openmc.CellFilter(fuel)
legendre_filter = openmc.SpatialLegendreFilter(order, 'z', zmin, zmax)
flux_tally_legendre.filters = [cell_filter, legendre_filter]
# Create a Zernike azimuthal polynomial expansion filter and add to tally
flux_tally_zernike = openmc.Tally()
flux_tally_zernike.scores = ['flux']
zernike_filter = openmc.ZernikeFilter(order=order, x=0.0, y=0.0, r=radius)
flux_tally_zernike.filters = [cell_filter, zernike_filter]
# Create a Zernike radial polynomial expansion filter and add to tally
flux_tally_zernike1d = openmc.Tally()
flux_tally_zernike1d.scores = ['flux']
zernike1d_filter = openmc.ZernikeRadialFilter(order=order, x=0.0, y=0.0, r=radius)
flux_tally_zernike1d.filters = [cell_filter, zernike1d_filter]
The last thing we need to do is create a Tallies
collection and export the entire model, which we’ll do using the Model
convenience class.
[18]:
tallies = openmc.Tallies([flux_tally_legendre, flux_tally_zernike, flux_tally_zernike1d])
model = openmc.model.Model(geometry=geom, settings=settings, tallies=tallies)
Running a simulation is now as simple as calling the run()
method of Model
.
[19]:
sp_file = model.run(output=False)
Now that the run is finished, we need to load the results from the statepoint file.
[20]:
with openmc.StatePoint(sp_file) as sp:
df1 = sp.tallies[flux_tally_legendre.id].get_pandas_dataframe()
We’ve used the get_pandas_dataframe()
method that returns tally data as a Pandas dataframe. Let’s see what the raw data looks like.
[21]:
df1
[21]:
cell | spatiallegendre | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 4 | P0 | total | flux | 0.543425 | 0.000599 |
1 | 4 | P1 | total | flux | 0.000635 | 0.000523 |
2 | 4 | P2 | total | flux | -0.055989 | 0.000342 |
3 | 4 | P3 | total | flux | -0.000053 | 0.000284 |
4 | 4 | P4 | total | flux | -0.000749 | 0.000230 |
5 | 4 | P5 | total | flux | 0.000111 | 0.000149 |
6 | 4 | P6 | total | flux | -0.000692 | 0.000177 |
7 | 4 | P7 | total | flux | -0.000064 | 0.000152 |
8 | 4 | P8 | total | flux | -0.000139 | 0.000142 |
9 | 4 | P9 | total | flux | 0.000201 | 0.000119 |
10 | 4 | P10 | total | flux | -0.000051 | 0.000112 |
Since the scaling factors for expansion coefficients will be provided by the Python API, thus, we do not need to multiply the moments by scaling factors.
[22]:
a_n = df1['mean']
Loading the coefficients is realized via calling the OpenMC Python API as follows:
[23]:
phi = openmc.legendre_from_expcoef(a_n, domain=(zmin, zmax))
Let’s plot it and see how our flux looks!
[24]:
z = np.linspace(zmin, zmax, 1000)
plt.plot(z, phi(z))
plt.xlabel('Z position [cm]')
plt.ylabel('Flux [n/src]')
[24]:
Text(0, 0.5, 'Flux [n/src]')

A rough cosine shape is obtained. One can also numerically integrate the function using the trapezoidal rule.
[25]:
np.trapz(phi(z), z)
[25]:
0.543424143829605
The following cases show how to reconstruct the flux distribution Zernike polynomials tallied results.
[26]:
with openmc.StatePoint(sp_file) as sp:
df2 = sp.tallies[flux_tally_zernike.id].get_pandas_dataframe()
[27]:
df2
[27]:
cell | zernike | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 4 | Z0,0 | total | flux | 0.543425 | 0.000599 |
1 | 4 | Z1,-1 | total | flux | -0.000236 | 0.000398 |
2 | 4 | Z1,1 | total | flux | 0.000126 | 0.000325 |
3 | 4 | Z2,-2 | total | flux | -0.000104 | 0.000206 |
4 | 4 | Z2,0 | total | flux | -0.064291 | 0.000404 |
... | ... | ... | ... | ... | ... | ... |
61 | 4 | Z10,2 | total | flux | -0.000130 | 0.000099 |
62 | 4 | Z10,4 | total | flux | -0.000057 | 0.000092 |
63 | 4 | Z10,6 | total | flux | -0.000048 | 0.000109 |
64 | 4 | Z10,8 | total | flux | -0.000056 | 0.000092 |
65 | 4 | Z10,10 | total | flux | -0.000046 | 0.000098 |
66 rows × 6 columns
Let’s plot the flux in radial direction with specific azimuthal angle (\(\theta = 0.0\)).
[28]:
z_n = df2['mean']
zz = openmc.Zernike(z_n, radius)
rr = np.linspace(0, radius, 100)
plt.plot(rr, zz(rr, 0.0))
plt.xlabel('Radial position [cm]')
plt.ylabel('Flux')
[28]:
Text(0, 0.5, 'Flux')

A polar figure with all azimuthal can be plotted like this:
[29]:
z_n = df2['mean']
zz = openmc.Zernike(z_n, radius=radius)
#
# Using linspace so that the endpoint of 360 is included...
azimuths = np.radians(np.linspace(0, 360, 50))
zeniths = np.linspace(0, radius, 100)
r, theta = np.meshgrid(zeniths, azimuths)
values = zz(zeniths, azimuths)
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.contourf(theta, r, values, cmap='jet')
plt.show()

Sometimes, we just need the radial-only Zernike polynomial tallied flux distribution. Let us extract the tallied coefficients first.
[30]:
with openmc.StatePoint(sp_file) as sp:
df3 = sp.tallies[flux_tally_zernike1d.id].get_pandas_dataframe()
[31]:
df3
[31]:
cell | zernikeradial | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 4 | Z0,0 | total | flux | 0.543425 | 0.000599 |
1 | 4 | Z2,0 | total | flux | -0.064291 | 0.000404 |
2 | 4 | Z4,0 | total | flux | -0.000601 | 0.000223 |
3 | 4 | Z6,0 | total | flux | -0.000454 | 0.000227 |
4 | 4 | Z8,0 | total | flux | -0.000011 | 0.000166 |
5 | 4 | Z10,0 | total | flux | -0.000102 | 0.000161 |
A plot along with r-axis is also done.
[32]:
z_n = df3['mean']
zz = openmc.ZernikeRadial(z_n, radius=radius)
rr = np.linspace(0, radius, 50)
plt.plot(rr, zz(rr))
plt.xlabel('Radial position [cm]')
plt.ylabel('Flux')
[32]:
Text(0, 0.5, 'Flux')

Similarly, we can also re-construct the polar figure based on radial-only Zernike polinomial coefficients.
[33]:
z_n = df3['mean']
zz = openmc.ZernikeRadial(z_n, radius=radius)
azimuths = np.radians(np.linspace(0, 360, 50))
zeniths = np.linspace(0, radius, 100)
r, theta = np.meshgrid(zeniths, azimuths)
values = [[i for i in zz(zeniths)] for j in range(len(azimuths))]
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'), figsize=(6,6))
ax.contourf(theta, r, values, cmap='jet')
plt.show()

Based on Legendre polynomial coefficients and the azimuthal or radial-only Zernike coefficient, it’s possible to reconstruct the flux both on radial and axial directions.
[34]:
# Reconstruct 3-D flux based on radial only Zernike and Legendre polynomials
z_n = df3['mean']
zz = openmc.ZernikeRadial(z_n, radius=radius)
azimuths = np.radians(np.linspace(0, 360, 100)) # azimuthal mesh
zeniths = np.linspace(0, radius, 100) # radial mesh
zmin, zmax = -1.0, 1.0
z = np.linspace(zmin, zmax, 100) # axial mesh
#
# flux = np.matmul(np.matrix(phi(z)).transpose(), np.matrix(zz(zeniths)))
# flux = np.array(flux) # change np.matrix to np.array
# np.matrix is not recommended for use anymore
flux = np.array([phi(z)]).T @ np.array([zz(zeniths)])
#
plt.figure(figsize=(5,10))
plt.title('Flux distribution')
plt.xlabel('Radial Position [cm]')
plt.ylabel('Axial Height [cm]')
plt.pcolor(zeniths, z, flux, cmap='jet')
plt.colorbar()
[34]:
<matplotlib.colorbar.Colorbar at 0x7ffe048a2f90>

One can also reconstruct the 3D flux distribution based on Legendre and Zernike polynomial tallied coefficients.
[35]:
# Define needed function first
def cart2pol(x, y):
rho = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
return(rho, phi)
# Reconstruct 3-D flux based on azimuthal Zernike and Legendre polynomials
z_n = df2['mean']
zz = openmc.Zernike(z_n, radius=radius)
#
xstep = 2.0*radius/20
hstep = (zmax - zmin)/20
x = np.linspace(-radius, radius, 50)
x = np.array(x)
[X,Y] = np.meshgrid(x,x)
h = np.linspace(zmin, zmax, 50)
h = np.array(h)
[r, theta] = cart2pol(X,Y)
flux3d = np.zeros((len(x), len(x), len(h)))
flux3d.fill(np.nan)
#
for i in range(len(x)):
for j in range(len(x)):
if r[i][j]<=radius:
for k in range(len(h)):
flux3d[i][j][k] = phi(h[k]) * zz(r[i][j], theta[i][j])
Let us print out with VTK format.
[36]:
# You'll need to install pyevtk as a prerequisite
from pyevtk.hl import gridToVTK
import numpy as np
#
# Dimensions
nx, ny, nz = len(x), len(x), len(h)
lx, ly, lz = 2.0*radius, 2.0*radius, (zmax-zmin)
dx, dy, dz = lx/nx, ly/ny, lz/nz
#
ncells = nx * ny * nz
npoints = (nx + 1) * (ny + 1) * (nz + 1)
#
# Coordinates
x = np.arange(0, lx + 0.1*dx, dx, dtype='float64')
y = np.arange(0, ly + 0.1*dy, dy, dtype='float64')
z = np.arange(0, lz + 0.1*dz, dz, dtype='float64')
# Print out
path = gridToVTK("./rectilinear", x, y, z, cellData = {"flux3d" : flux3d})
Use VisIt or ParaView to plot it as you want. Then, the plot can be loaded and shown as follows.
[37]:
f1 = plt.imread('./images/flux3d.png')
plt.imshow(f1, cmap='jet')
[37]:
<matplotlib.image.AxesImage at 0x7ffe050592d0>

[ ]:
Criticality Search¶
This notebook illustrates the usage of the OpenMC Python API’s generic eigenvalue search capability. In this Notebook, we will do a critical boron concentration search of a typical PWR pin cell.
To use the search functionality, we must create a function which creates our model according to the input parameter we wish to search for (in this case, the boron concentration).
This notebook will first create that function, and then, run the search.
[1]:
# Initialize third-party libraries and the OpenMC Python API
import matplotlib.pyplot as plt
import numpy as np
import openmc
import openmc.model
%matplotlib inline
Create Parametrized Model¶
To perform the search we will use the openmc.search_for_keff
function. This function requires a different function be defined which creates an parametrized model to analyze. This model is required to be stored in an openmc.model.Model
object. The first parameter of this function will be modified during the search process for our critical eigenvalue.
Our model will be a pin-cell from the Multi-Group Mode Part II assembly, except this time the entire model building process will be contained within a function, and the Boron concentration will be parametrized.
[2]:
# Create the model. `ppm_Boron` will be the parametric variable.
def build_model(ppm_Boron):
# Create the pin materials
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_element('U', 1., enrichment=1.6)
fuel.add_element('O', 2.)
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_element('Zr', 1.)
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.741)
water.add_element('H', 2.)
water.add_element('O', 1.)
# Include the amount of boron in the water based on the ppm,
# neglecting the other constituents of boric acid
water.add_element('B', ppm_Boron * 1e-6)
# Instantiate a Materials object
materials = openmc.Materials([fuel, zircaloy, water])
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(r=0.39218)
clad_outer_radius = openmc.ZCylinder(r=0.45720)
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-0.63, boundary_type='reflective')
max_x = openmc.XPlane(x0=+0.63, boundary_type='reflective')
min_y = openmc.YPlane(y0=-0.63, boundary_type='reflective')
max_y = openmc.YPlane(y0=+0.63, boundary_type='reflective')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius & (+min_x & -max_x & +min_y & -max_y)
# Create root Universe
root_universe = openmc.Universe(name='root universe')
root_universe.add_cells([fuel_cell, clad_cell, moderator_cell])
# Create Geometry and set root universe
geometry = openmc.Geometry(root_universe)
# Instantiate a Settings object
settings = openmc.Settings()
# Set simulation parameters
settings.batches = 300
settings.inactive = 20
settings.particles = 1000
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -10, 0.63, 0.63, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings.source = openmc.source.Source(space=uniform_dist)
# We dont need a tallies file so dont waste the disk input/output time
settings.output = {'tallies': False}
model = openmc.model.Model(geometry, materials, settings)
return model
Search for the Critical Boron Concentration¶
To perform the search we imply call the openmc.search_for_keff
function and pass in the relvant arguments. For our purposes we will be passing in the model building function (build_model
defined above), a bracketed range for the expected critical Boron concentration (1,000 to 2,500 ppm), the tolerance, and the method we wish to use.
Instead of the bracketed range we could have used a single initial guess, but have elected not to in this example. Finally, due to the high noise inherent in using as few histories as are used in this example, our tolerance on the final keff value will be rather large (1.e-2) and the default ‘bisection’ method will be used for the search.
[3]:
# Perform the search
crit_ppm, guesses, keffs = openmc.search_for_keff(build_model, bracket=[1000., 2500.],
tol=1e-2, print_iterations=True)
print('Critical Boron Concentration: {:4.0f} ppm'.format(crit_ppm))
Iteration: 1; Guess of 1.00e+03 produced a keff of 1.08504 +/- 0.00169
Iteration: 2; Guess of 2.50e+03 produced a keff of 0.95243 +/- 0.00158
Iteration: 3; Guess of 1.75e+03 produced a keff of 1.01269 +/- 0.00163
Iteration: 4; Guess of 2.12e+03 produced a keff of 0.98165 +/- 0.00155
Iteration: 5; Guess of 1.94e+03 produced a keff of 0.99773 +/- 0.00158
Iteration: 6; Guess of 1.84e+03 produced a keff of 1.00872 +/- 0.00170
Iteration: 7; Guess of 1.89e+03 produced a keff of 1.00462 +/- 0.00154
Iteration: 8; Guess of 1.91e+03 produced a keff of 1.00202 +/- 0.00154
Iteration: 9; Guess of 1.93e+03 produced a keff of 0.99816 +/- 0.00155
Critical Boron Concentration: 1926 ppm
Finally, the openmc.search_for_keff
function also provided us with List
s of the guesses and corresponding keff values generated during the search process with OpenMC. Let’s use that information to make a quick plot of the value of keff versus the boron concentration.
[4]:
plt.figure(figsize=(8, 4.5))
plt.title('Eigenvalue versus Boron Concentration')
# Create a scatter plot using the mean value of keff
plt.scatter(guesses, [keffs[i].nominal_value for i in range(len(keffs))])
plt.xlabel('Boron Concentration [ppm]')
plt.ylabel('Eigenvalue')
plt.show()

We see a nearly linear reactivity coefficient for the boron concentration, exactly as one would expect for a pure 1/v absorber at small concentrations.
Nuclear Data¶
In this notebook, we will go through the salient features of the openmc.data
package in the Python API. This package enables inspection, analysis, and conversion of nuclear data from ACE files. Most importantly, the package provides a mean to generate HDF5 nuclear data libraries that are used by the transport solver.
[1]:
%matplotlib inline
import os
from pprint import pprint
import shutil
import subprocess
import urllib.request
import h5py
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm
from matplotlib.patches import Rectangle
import openmc.data
Physical Data¶
Some very helpful physical data is available as part of openmc.data
: atomic masses, natural abundances, and atomic weights.
[2]:
openmc.data.atomic_mass('Fe54')
[2]:
53.939608306
[3]:
openmc.data.NATURAL_ABUNDANCE['H2']
[3]:
0.00015574
[4]:
openmc.data.atomic_weight('C')
[4]:
12.011115164864455
The IncidentNeutron class¶
The most useful class within the openmc.data
API is IncidentNeutron
, which stores to continuous-energy incident neutron data. This class has factory methods from_ace
, from_endf
, and from_hdf5
which take a data file on disk and parse it into a hierarchy of classes in memory. To demonstrate this feature, we will download an ACE file (which can be produced with NJOY 2016) and then load it in using the IncidentNeutron.from_ace
method.
[5]:
url = 'https://anl.box.com/shared/static/kxm7s57z3xgfbeq29h54n7q6js8rd11c.ace'
filename, headers = urllib.request.urlretrieve(url, 'gd157.ace')
[6]:
# Load ACE data into object
gd157 = openmc.data.IncidentNeutron.from_ace('gd157.ace')
gd157
[6]:
<IncidentNeutron: Gd157>
Cross sections¶
From Python, it’s easy to explore (and modify) the nuclear data. Let’s start off by reading the total cross section. Reactions are indexed using their “MT” number – a unique identifier for each reaction defined by the ENDF-6 format. The MT number for the total cross section is 1.
[7]:
total = gd157[1]
total
[7]:
<Reaction: MT=1 (n,total)>
Cross sections for each reaction can be stored at multiple temperatures. To see what temperatures are available, we can look at the reaction’s xs
attribute.
[8]:
total.xs
[8]:
{'294K': <openmc.data.function.Tabulated1D at 0x1461b4d2f160>}
To find the cross section at a particular energy, 1 eV for example, simply get the cross section at the appropriate temperature and then call it as a function. Note that our nuclear data uses eV as the unit of energy.
[9]:
total.xs['294K'](1.0)
[9]:
142.64747
The xs
attribute can also be called on an array of energies.
[10]:
total.xs['294K']([1.0, 2.0, 3.0])
[10]:
array([142.64747 , 38.6541761 , 175.40019642])
A quick way to plot cross sections is to use the energy
attribute of IncidentNeutron
. This gives an array of all the energy values used in cross section interpolation for each temperature present.
[11]:
gd157.energy
[11]:
{'294K': array([1.0000e-05, 1.0325e-05, 1.0650e-05, ..., 1.9500e+07, 1.9900e+07,
2.0000e+07])}
[12]:
energies = gd157.energy['294K']
total_xs = total.xs['294K'](energies)
plt.loglog(energies, total_xs)
plt.xlabel('Energy (eV)')
plt.ylabel('Cross section (b)')
[12]:
Text(0, 0.5, 'Cross section (b)')

Reaction Data¶
Most of the interesting data for an IncidentNeutron
instance is contained within the reactions
attribute, which is a dictionary mapping MT values to Reaction
objects.
[13]:
pprint(list(gd157.reactions.values())[:10])
[<Reaction: MT=1 (n,total)>,
<Reaction: MT=101 (n,disappear)>,
<Reaction: MT=301 heating>,
<Reaction: MT=2 (n,elastic)>,
<Reaction: MT=16 (n,2n)>,
<Reaction: MT=17 (n,3n)>,
<Reaction: MT=22 (n,na)>,
<Reaction: MT=24 (n,2na)>,
<Reaction: MT=28 (n,np)>,
<Reaction: MT=41 (n,2np)>]
Let’s suppose we want to look more closely at the (n,2n) reaction. This reaction has an energy threshold
[14]:
n2n = gd157[16]
print('Threshold = {} eV'.format(n2n.xs['294K'].x[0]))
Threshold = 6400881.0 eV
The (n,2n) cross section, like all basic cross sections, is represented by the Tabulated1D
class. The energy and cross section values in the table can be directly accessed with the x
and y
attributes. Using the x
and y
has the nice benefit of automatically acounting for reaction thresholds.
[15]:
n2n.xs
[15]:
{'294K': <openmc.data.function.Tabulated1D at 0x1461b4d594e0>}
[16]:
xs = n2n.xs['294K']
plt.plot(xs.x, xs.y)
plt.xlabel('Energy (eV)')
plt.ylabel('Cross section (b)')
plt.xlim((xs.x[0], xs.x[-1]))
[16]:
(6400881.0, 20000000.0)

To get information on the energy and angle distribution of the neutrons emitted in the reaction, we need to look at the products
attribute.
[17]:
n2n.products
[17]:
[<Product: neutron, emission=prompt, yield=polynomial>,
<Product: photon, emission=prompt, yield=tabulated>]
[18]:
neutron = n2n.products[0]
neutron.distribution
[18]:
[<openmc.data.correlated.CorrelatedAngleEnergy at 0x1461b4a66908>]
We see that the neutrons emitted have a correlated angle-energy distribution. Let’s look at the energy_out
attribute to see what the outgoing energy distributions are.
[19]:
dist = neutron.distribution[0]
dist.energy_out
[19]:
[<openmc.stats.univariate.Tabular at 0x1461b4d59550>,
<openmc.stats.univariate.Tabular at 0x1461b4d59630>,
<openmc.stats.univariate.Tabular at 0x1461b4d596d8>,
<openmc.stats.univariate.Tabular at 0x1461b4d59828>,
<openmc.stats.univariate.Tabular at 0x1461b4d59a20>,
<openmc.stats.univariate.Tabular at 0x1461b4d59c88>,
<openmc.stats.univariate.Tabular at 0x1461b4d59f98>,
<openmc.stats.univariate.Tabular at 0x1461b4d63358>,
<openmc.stats.univariate.Tabular at 0x1461b4d63748>,
<openmc.stats.univariate.Tabular at 0x1461b4d63ba8>,
<openmc.stats.univariate.Tabular at 0x1461b4d6b0b8>,
<openmc.stats.univariate.Tabular at 0x1461b4d6b588>,
<openmc.stats.univariate.Tabular at 0x1461b4d6bb00>,
<openmc.stats.univariate.Tabular at 0x1461b4aaa0f0>,
<openmc.stats.univariate.Tabular at 0x1461b4aaa710>,
<openmc.stats.univariate.Tabular at 0x1461b4aaad68>,
<openmc.stats.univariate.Tabular at 0x1461b4ab1470>,
<openmc.stats.univariate.Tabular at 0x1461b4ab1b70>,
<openmc.stats.univariate.Tabular at 0x1461b4ab92b0>,
<openmc.stats.univariate.Tabular at 0x1461b4ab9a20>,
<openmc.stats.univariate.Tabular at 0x1461b4ac1208>,
<openmc.stats.univariate.Tabular at 0x1461b4ac19e8>,
<openmc.stats.univariate.Tabular at 0x1461b4ac9240>,
<openmc.stats.univariate.Tabular at 0x1461b4ac9a90>,
<openmc.stats.univariate.Tabular at 0x1461b4acf358>,
<openmc.stats.univariate.Tabular at 0x1461b4acfc18>,
<openmc.stats.univariate.Tabular at 0x1461b4ad7470>,
<openmc.stats.univariate.Tabular at 0x1461b4ad7d30>,
<openmc.stats.univariate.Tabular at 0x1461b4ae0668>,
<openmc.stats.univariate.Tabular at 0x1461b4ae0f98>]
Here we see we have a tabulated outgoing energy distribution for each incoming energy. Note that the same probability distribution classes that we could use to create a source definition are also used within the openmc.data
package. Let’s plot every fifth distribution to get an idea of what they look like.
[20]:
for e_in, e_out_dist in zip(dist.energy[::5], dist.energy_out[::5]):
plt.semilogy(e_out_dist.x, e_out_dist.p, label='E={:.2f} MeV'.format(e_in/1e6))
plt.ylim(top=1e-6)
plt.legend()
plt.xlabel('Outgoing energy (eV)')
plt.ylabel('Probability/eV')
plt.show()

Unresolved resonance probability tables¶
We can also look at unresolved resonance probability tables which are stored in a ProbabilityTables
object. In the following example, we’ll create a plot showing what the total cross section probability tables look like as a function of incoming energy.
[21]:
fig = plt.figure()
ax = fig.add_subplot(111)
cm = matplotlib.cm.Spectral_r
# Determine size of probability tables
urr = gd157.urr['294K']
n_energy = urr.table.shape[0]
n_band = urr.table.shape[2]
for i in range(n_energy):
# Get bounds on energy
if i > 0:
e_left = urr.energy[i] - 0.5*(urr.energy[i] - urr.energy[i-1])
else:
e_left = urr.energy[i] - 0.5*(urr.energy[i+1] - urr.energy[i])
if i < n_energy - 1:
e_right = urr.energy[i] + 0.5*(urr.energy[i+1] - urr.energy[i])
else:
e_right = urr.energy[i] + 0.5*(urr.energy[i] - urr.energy[i-1])
for j in range(n_band):
# Determine maximum probability for a single band
max_prob = np.diff(urr.table[i,0,:]).max()
# Determine bottom of band
if j > 0:
xs_bottom = urr.table[i,1,j] - 0.5*(urr.table[i,1,j] - urr.table[i,1,j-1])
value = (urr.table[i,0,j] - urr.table[i,0,j-1])/max_prob
else:
xs_bottom = urr.table[i,1,j] - 0.5*(urr.table[i,1,j+1] - urr.table[i,1,j])
value = urr.table[i,0,j]/max_prob
# Determine top of band
if j < n_band - 1:
xs_top = urr.table[i,1,j] + 0.5*(urr.table[i,1,j+1] - urr.table[i,1,j])
else:
xs_top = urr.table[i,1,j] + 0.5*(urr.table[i,1,j] - urr.table[i,1,j-1])
# Draw rectangle with appropriate color
ax.add_patch(Rectangle((e_left, xs_bottom), e_right - e_left, xs_top - xs_bottom,
color=cm(value)))
# Overlay total cross section
ax.plot(gd157.energy['294K'], total.xs['294K'](gd157.energy['294K']), 'k')
# Make plot pretty and labeled
ax.set_xlim(1.0, 1.0e5)
ax.set_ylim(1e-1, 1e4)
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xlabel('Energy (eV)')
ax.set_ylabel('Cross section(b)')
[21]:
Text(0, 0.5, 'Cross section(b)')

Exporting HDF5 data¶
If you have an instance IncidentNeutron
that was created from ACE or HDF5 data, you can easily write it to disk using the export_to_hdf5()
method. This can be used to convert ACE to HDF5 or to take an existing data set and actually modify cross sections.
[22]:
gd157.export_to_hdf5('gd157.h5', 'w')
With few exceptions, the HDF5 file encodes the same data as the ACE file.
[23]:
gd157_reconstructed = openmc.data.IncidentNeutron.from_hdf5('gd157.h5')
np.all(gd157[16].xs['294K'].y == gd157_reconstructed[16].xs['294K'].y)
[23]:
True
And one of the best parts of using HDF5 is that it is a widely used format with lots of third-party support. You can use h5py
, for example, to inspect the data.
[24]:
h5file = h5py.File('gd157.h5', 'r')
main_group = h5file['Gd157/reactions']
for name, obj in sorted(list(main_group.items()))[:10]:
if 'reaction_' in name:
print('{}, {}'.format(name, obj.attrs['label'].decode()))
reaction_002, (n,elastic)
reaction_016, (n,2n)
reaction_017, (n,3n)
reaction_022, (n,na)
reaction_024, (n,2na)
reaction_028, (n,np)
reaction_041, (n,2np)
reaction_051, (n,n1)
reaction_052, (n,n2)
reaction_053, (n,n3)
[25]:
n2n_group = main_group['reaction_016']
pprint(list(n2n_group.values()))
[<HDF5 group "/Gd157/reactions/reaction_016/294K" (1 members)>,
<HDF5 group "/Gd157/reactions/reaction_016/product_0" (2 members)>,
<HDF5 group "/Gd157/reactions/reaction_016/product_1" (2 members)>]
So we see that the hierarchy of data within the HDF5 mirrors the hierarchy of Python objects that we manipulated before.
[26]:
n2n_group['294K/xs'][()]
[26]:
array([0.000000e+00, 3.026796e-13, 1.291101e-02, 6.511110e-02,
3.926270e-01, 5.752268e-01, 6.969600e-01, 7.399378e-01,
9.635450e-01, 1.142130e+00, 1.308020e+00, 1.463500e+00,
1.557600e+00, 1.640550e+00, 1.688960e+00, 1.711400e+00,
1.739450e+00, 1.782070e+00, 1.816650e+00, 1.845280e+00,
1.865409e+00, 1.867240e+00, 1.881558e+00, 1.881560e+00,
1.881800e+00, 1.894470e+00, 1.869570e+00, 1.821200e+00,
1.716000e+00, 1.600540e+00, 1.431620e+00, 1.283460e+00,
1.101660e+00, 1.065300e+00, 9.307300e-01, 8.029800e-01,
7.777400e-01])
Working with ENDF files¶
In addition to being able to load ACE and HDF5 data, we can also load ENDF data directly into an IncidentNeutron
instance using the from_endf()
factory method. Let’s download the ENDF/B-VII.1 evaluation for \(^{157}\)Gd and load it in:
[27]:
# Download ENDF file
url = 'https://t2.lanl.gov/nis/data/data/ENDFB-VII.1-neutron/Gd/157'
filename, headers = urllib.request.urlretrieve(url, 'gd157.endf')
# Load into memory
gd157_endf = openmc.data.IncidentNeutron.from_endf(filename)
gd157_endf
[27]:
<IncidentNeutron: Gd157>
Just as before, we can get a reaction by indexing the object directly:
[28]:
elastic = gd157_endf[2]
However, if we look at the cross section now, we see that it isn’t represented as tabulated data anymore.
[29]:
elastic.xs
[29]:
{'0K': <openmc.data.function.ResonancesWithBackground at 0x1461b11824a8>}
If you had Cython installed when you built/installed OpenMC, you should be able to evaluate resonant cross sections from ENDF data directly, i.e., OpenMC will reconstruct resonances behind the scenes for you.
[30]:
elastic.xs['0K'](0.0253)
[30]:
998.7871174521487
When data is loaded from an ENDF file, there is also a special resonances
attribute that contains resolved and unresolved resonance region data (from MF=2 in an ENDF file).
[31]:
gd157_endf.resonances.ranges
[31]:
[<openmc.data.resonance.ReichMoore at 0x1461b1fcb898>,
<openmc.data.resonance.Unresolved at 0x1461b2692ba8>]
We see that \(^{157}\)Gd has a resolved resonance region represented in the Reich-Moore format as well as an unresolved resonance region. We can look at the min/max energy of each region by doing the following:
[32]:
[(r.energy_min, r.energy_max) for r in gd157_endf.resonances.ranges]
[32]:
[(1e-05, 306.6), (306.6, 54881.1)]
With knowledge of the energy bounds, let’s create an array of energies over the entire resolved resonance range and plot the elastic scattering cross section.
[33]:
# Create log-spaced array of energies
resolved = gd157_endf.resonances.resolved
energies = np.logspace(np.log10(resolved.energy_min),
np.log10(resolved.energy_max), 1000)
# Evaluate elastic scattering xs at energies
xs = elastic.xs['0K'](energies)
# Plot cross section vs energies
plt.loglog(energies, xs)
plt.xlabel('Energy (eV)')
plt.ylabel('Cross section (b)')
[33]:
Text(0, 0.5, 'Cross section (b)')

Resonance ranges also have a useful parameters
attribute that shows the energies and widths for resonances.
[34]:
resolved.parameters.head(10)
[34]:
energy | L | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | |
---|---|---|---|---|---|---|---|
0 | 0.0314 | 0 | 2.0 | 0.000474 | 0.1072 | 0.0 | 0.0 |
1 | 2.8250 | 0 | 2.0 | 0.000345 | 0.0970 | 0.0 | 0.0 |
2 | 16.2400 | 0 | 1.0 | 0.000400 | 0.0910 | 0.0 | 0.0 |
3 | 16.7700 | 0 | 2.0 | 0.012800 | 0.0805 | 0.0 | 0.0 |
4 | 20.5600 | 0 | 2.0 | 0.011360 | 0.0880 | 0.0 | 0.0 |
5 | 21.6500 | 0 | 2.0 | 0.000376 | 0.1140 | 0.0 | 0.0 |
6 | 23.3300 | 0 | 1.0 | 0.000813 | 0.1210 | 0.0 | 0.0 |
7 | 25.4000 | 0 | 2.0 | 0.001840 | 0.0850 | 0.0 | 0.0 |
8 | 40.1700 | 0 | 1.0 | 0.001307 | 0.1100 | 0.0 | 0.0 |
9 | 44.2200 | 0 | 2.0 | 0.008960 | 0.0960 | 0.0 | 0.0 |
Heavy-nuclide resonance scattering¶
OpenMC has two methods for accounting for resonance upscattering in heavy nuclides, DBRC and RVS. These methods rely on 0 K elastic scattering data being present. If you have an existing ACE/HDF5 dataset and you need to add 0 K elastic scattering data to it, this can be done using the IncidentNeutron.add_elastic_0K_from_endf()
method. Let’s do this with our original gd157
object that we instantiated from an ACE file.
[35]:
gd157.add_elastic_0K_from_endf('gd157.endf')
Let’s check to make sure that we have both the room temperature elastic scattering cross section as well as a 0K cross section.
[36]:
gd157[2].xs
[36]:
{'294K': <openmc.data.function.Tabulated1D at 0x1461b4d2ff60>,
'0K': <openmc.data.function.Tabulated1D at 0x1461b0e59128>}
Generating data from NJOY¶
To run OpenMC in continuous-energy mode, you generally need to have ACE files already available that can be converted to OpenMC’s native HDF5 format. If you don’t already have suitable ACE files or need to generate new data, both the IncidentNeutron
and ThermalScattering
classes include from_njoy()
methods that will run NJOY to generate ACE files and then read those files to create OpenMC class instances. The from_njoy()
methods take as input the name
of an ENDF file on disk. By default, it is assumed that you have an executable named njoy
available on your path. This can be configured with the optional njoy_exec
argument. Additionally, if you want to show the progress of NJOY as it is running, you can pass stdout=True
.
Let’s use IncidentNeutron.from_njoy()
to run NJOY to create data for \(^2\)H using an ENDF file. We’ll specify that we want data specifically at 300, 400, and 500 K.
[37]:
# Download ENDF file
url = 'https://t2.lanl.gov/nis/data/data/ENDFB-VII.1-neutron/H/2'
filename, headers = urllib.request.urlretrieve(url, 'h2.endf')
# Run NJOY to create deuterium data
h2 = openmc.data.IncidentNeutron.from_njoy('h2.endf', temperatures=[300., 400., 500.], stdout=True)
njoy 2016.49 25Jan19 07/19/19 06:12:49
*****************************************************************************
reconr... 0.0s
broadr... 0.1s
300.0 deg 0.1s
400.0 deg 0.2s
500.0 deg 0.3s
heatr... 0.3s
gaspr... 0.6s
purr... 0.7s
mat = 128 0.7s
---message from purr---mat 128 has no resonance parameters
copy as is to nout
acer... 0.7s
acer... 1.0s
acer... 1.1s
1.2s
*****************************************************************************
Now we can use our h2
object just as we did before.
[38]:
h2[2].xs
[38]:
{'300K': <openmc.data.function.Tabulated1D at 0x1461b066ebe0>,
'400K': <openmc.data.function.Tabulated1D at 0x1461b23a0978>,
'500K': <openmc.data.function.Tabulated1D at 0x1461b1009400>,
'0K': <openmc.data.function.Tabulated1D at 0x1461b101b358>}
Note that 0 K elastic scattering data is automatically added when using from_njoy()
so that resonance elastic scattering treatments can be used.
Windowed multipole¶
OpenMC can also be used with an experimental format called windowed multipole. Windowed multipole allows for analytic on-the-fly Doppler broadening of the resolved resonance range. Windowed multipole data can be downloaded with the openmc-get-multipole-data
script. This data can be used in the transport solver, but it can also be used directly in the Python API.
[39]:
url = 'https://github.com/mit-crpg/WMP_Library/releases/download/v1.1/092238.h5'
filename, headers = urllib.request.urlretrieve(url, '092238.h5')
[40]:
u238_multipole = openmc.data.WindowedMultipole.from_hdf5('092238.h5')
The WindowedMultipole
object can be called with energy and temperature values. Calling the object gives a tuple of 3 cross sections: elastic scattering, radiative capture, and fission.
[41]:
u238_multipole(1.0, 294)
[41]:
(array(9.13284265), array(0.50530278), array(2.9316765e-06))
An array can be passed for the energy argument.
[42]:
E = np.linspace(5, 25, 1000)
plt.semilogy(E, u238_multipole(E, 293.606)[1])
[42]:
[<matplotlib.lines.Line2D at 0x1461b0ed9978>]

The real advantage to multipole is that it can be used to generate cross sections at any temperature. For example, this plot shows the Doppler broadening of the 6.67 eV resonance between 0 K and 900 K.
[43]:
E = np.linspace(6.1, 7.1, 1000)
plt.semilogy(E, u238_multipole(E, 0)[1])
plt.semilogy(E, u238_multipole(E, 900)[1])
[43]:
[<matplotlib.lines.Line2D at 0x1461b09755c0>]

Nuclear Data: Resonance Covariance¶
In this notebook we will explore features of the Python API that allow us to import and manipulate resonance covariance data. A full description of the ENDF-VI and ENDF-VII formats can be found in the ENDF102 manual.
[1]:
%matplotlib inline
import os
from pprint import pprint
import shutil
import subprocess
import urllib.request
import h5py
import numpy as np
import matplotlib.pyplot as plt
import openmc.data
ENDF: Resonance Covariance Data¶
Let’s download the ENDF/B-VII.1 evaluation for \(^{157}\)Gd and load it in:
[2]:
# Download ENDF file
url = 'https://t2.lanl.gov/nis/data/data/ENDFB-VII.1-neutron/Gd/157'
filename, headers = urllib.request.urlretrieve(url, 'gd157.endf')
# Load into memory
gd157_endf = openmc.data.IncidentNeutron.from_endf(filename, covariance=True)
gd157_endf
[2]:
<IncidentNeutron: Gd157>
We can access the parameters contained within File 32 in a similar manner to the File 2 parameters from before.
[3]:
gd157_endf.resonance_covariance.ranges[0].parameters[:5]
[3]:
energy | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | L | |
---|---|---|---|---|---|---|---|
0 | 0.0314 | 2.0 | 0.000474 | 0.1072 | 0.0 | 0.0 | 0 |
1 | 2.8250 | 2.0 | 0.000345 | 0.0970 | 0.0 | 0.0 | 0 |
2 | 16.2400 | 1.0 | 0.000400 | 0.0910 | 0.0 | 0.0 | 0 |
3 | 16.7700 | 2.0 | 0.012800 | 0.0805 | 0.0 | 0.0 | 0 |
4 | 20.5600 | 2.0 | 0.011360 | 0.0880 | 0.0 | 0.0 | 0 |
The newly created object will contain multiple resonance regions within gd157_endf.resonance_covariance.ranges
. We can access the full covariance matrix from File 32 for a given range by:
[4]:
covariance = gd157_endf.resonance_covariance.ranges[0].covariance
This covariance matrix currently only stores the upper triangular portion as covariance matrices are symmetric. Plotting the covariance matrix:
[5]:
plt.imshow(covariance, cmap='seismic',vmin=-0.008, vmax=0.008)
plt.colorbar()
[5]:
<matplotlib.colorbar.Colorbar at 0x14cf90c73550>

The correlation matrix can be constructed using the covariance matrix and also give some insight into the relations among the parameters.
[6]:
corr = np.zeros([len(covariance),len(covariance)])
for i in range(len(covariance)):
for j in range(len(covariance)):
corr[i, j]=covariance[i, j]/covariance[i, i]**(0.5)/covariance[j, j]**(0.5)
plt.imshow(corr, cmap='seismic',vmin=-1.0, vmax=1.0)
plt.colorbar()
[6]:
<matplotlib.colorbar.Colorbar at 0x14cf90b682e8>

Sampling and Reconstruction¶
The covariance module also has the ability to sample a new set of parameters using the covariance matrix. Currently the sampling uses numpy.multivariate_normal(). Because parameters are assumed to have a multivariate normal distribution this method doesn’t not currently guarantee that sampled parameters will be positive.
[7]:
rm_resonance = gd157_endf.resonances.ranges[0]
n_samples = 5
samples = gd157_endf.resonance_covariance.ranges[0].sample(n_samples)
type(samples[0])
/home/romano/openmc/openmc/data/resonance_covariance.py:233: UserWarning: Sampling routine does not guarantee positive values for parameters. This can lead to undefined behavior in the reconstruction routine.
warnings.warn(warn_str)
[7]:
openmc.data.resonance.ReichMoore
The sampling routine requires the incorporation of the openmc.data.ResonanceRange
for the same resonance range object. This allows each sample itself to be its own openmc.data.ResonanceRange
with a new set of parameters. Looking at some of the sampled parameters below:
[8]:
print('Sample 1')
samples[0].parameters[:5]
Sample 1
[8]:
energy | L | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | |
---|---|---|---|---|---|---|---|
0 | 0.030679 | 0 | 2.0 | 0.000473 | 0.108576 | 0.0 | 0.0 |
1 | 2.823843 | 0 | 2.0 | 0.000351 | 0.086418 | 0.0 | 0.0 |
2 | 16.281147 | 0 | 1.0 | 0.000458 | 0.106825 | 0.0 | 0.0 |
3 | 16.771760 | 0 | 2.0 | 0.013598 | 0.072837 | 0.0 | 0.0 |
4 | 20.561545 | 0 | 2.0 | 0.011164 | 0.086616 | 0.0 | 0.0 |
[9]:
print('Sample 2')
samples[1].parameters[:5]
Sample 2
[9]:
energy | L | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | |
---|---|---|---|---|---|---|---|
0 | 0.032858 | 0 | 2.0 | 0.000479 | 0.105208 | 0.0 | 0.0 |
1 | 2.823859 | 0 | 2.0 | 0.000361 | 0.093748 | 0.0 | 0.0 |
2 | 16.203069 | 0 | 1.0 | 0.000264 | 0.015233 | 0.0 | 0.0 |
3 | 16.765055 | 0 | 2.0 | 0.013648 | 0.076119 | 0.0 | 0.0 |
4 | 20.557679 | 0 | 2.0 | 0.011140 | 0.097548 | 0.0 | 0.0 |
We can reconstruct the cross section from the sampled parameters using the reconstruct method of openmc.data.ResonanceRange
. For more on reconstruction see the Nuclear Data example notebook.
[10]:
gd157_endf.resonances.ranges
[10]:
[<openmc.data.resonance.ReichMoore at 0x14cf93855d68>,
<openmc.data.resonance.Unresolved at 0x14cf938c02e8>]
[11]:
energy_range = [rm_resonance.energy_min, rm_resonance.energy_max]
energies = np.logspace(np.log10(energy_range[0]),
np.log10(energy_range[1]), 10000)
for sample in samples:
xs = sample.reconstruct(energies)
elastic_xs = xs[2]
plt.loglog(energies, elastic_xs)
plt.xlabel('Energy (eV)')
plt.ylabel('Cross section (b)')
[11]:
Text(0, 0.5, 'Cross section (b)')

Subset Selection¶
Another capability of the covariance module is selecting a subset of the resonance parameters and the corresponding subset of the covariance matrix. We can do this by specifying the value we want to discriminate and the bounds within one energy region. Selecting only resonances with J=2:
[12]:
lower_bound = 2; # inclusive
upper_bound = 2; # inclusive
rm_res_cov_sub = gd157_endf.resonance_covariance.ranges[0].subset('J',[lower_bound,upper_bound])
rm_res_cov_sub.file2res.parameters[:5]
[12]:
energy | L | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | |
---|---|---|---|---|---|---|---|
0 | 0.0314 | 0 | 2.0 | 0.000474 | 0.1072 | 0.0 | 0.0 |
1 | 2.8250 | 0 | 2.0 | 0.000345 | 0.0970 | 0.0 | 0.0 |
3 | 16.7700 | 0 | 2.0 | 0.012800 | 0.0805 | 0.0 | 0.0 |
4 | 20.5600 | 0 | 2.0 | 0.011360 | 0.0880 | 0.0 | 0.0 |
5 | 21.6500 | 0 | 2.0 | 0.000376 | 0.1140 | 0.0 | 0.0 |
The subset method will also store the corresponding subset of the covariance matrix
[13]:
rm_res_cov_sub.covariance
gd157_endf.resonance_covariance.ranges[0].covariance.shape
[13]:
(180, 180)
Checking the size of the new covariance matrix to be sure it was sampled properly:
[14]:
old_n_parameters = gd157_endf.resonance_covariance.ranges[0].parameters.shape[0]
old_shape = gd157_endf.resonance_covariance.ranges[0].covariance.shape
new_n_parameters = rm_res_cov_sub.file2res.parameters.shape[0]
new_shape = rm_res_cov_sub.covariance.shape
print('Number of parameters\nOriginal: '+str(old_n_parameters)+'\nSubet: '+str(new_n_parameters)+'\nCovariance Size\nOriginal: '+str(old_shape)+'\nSubset: '+str(new_shape))
Number of parameters
Original: 60
Subet: 36
Covariance Size
Original: (180, 180)
Subset: (108, 108)
And finally, we can sample from the subset as well
[15]:
samples_sub = rm_res_cov_sub.sample(n_samples)
samples_sub[0].parameters[:5]
[15]:
energy | L | J | neutronWidth | captureWidth | fissionWidthA | fissionWidthB | |
---|---|---|---|---|---|---|---|
0 | 0.030488 | 0 | 2.0 | 0.000473 | 0.108946 | 0.0 | 0.0 |
1 | 2.825944 | 0 | 2.0 | 0.000328 | 0.098328 | 0.0 | 0.0 |
2 | 16.773886 | 0 | 2.0 | 0.012984 | 0.076779 | 0.0 | 0.0 |
3 | 20.565737 | 0 | 2.0 | 0.011628 | 0.088958 | 0.0 | 0.0 |
4 | 21.646469 | 0 | 2.0 | 0.000389 | 0.127833 | 0.0 | 0.0 |
Pincell Depletion¶
This notebook is intended to introduce the reader to the depletion interface contained in OpenMC. It is recommended that you are moderately familiar with building models using the OpenMC Python API. The earlier examples are excellent starting points, as this notebook will not focus heavily on model building.
If you have a real power reactor, the fuel composition is constantly changing as fission events produce energy, remove some fissile isotopes, and produce fission products. Other reactions, like \((n, \alpha)\) and \((n, \gamma)\) will alter the composition as well. Furthermore, some nuclides undergo spontaneous decay with widely ranging frequencies. Depletion is the process of modeling this behavior.
In this notebook, we will model a simple fuel pin in an infinite lattice using the Python API. We will then build and examine some of the necessary components for performing depletion analysis. Then, we will use the depletion interface in OpenMC to simulate the fuel pin producing power over several months. Lastly, we will wrap up with some helpful tips to improve the fidelity of depletion simulations.
[1]:
%matplotlib inline
import math
import openmc
Build the Geometry¶
Much of this section is borrowed from the “Modeling a Pin-Cell” example. If you find yourself not understanding some aspects of this section, feel free to refer to that example, as some details may be glossed over for brevity.
First, we will create our fuel, cladding, and water materials to represent a typical PWR.
[2]:
fuel = openmc.Material(name="uo2")
[3]:
fuel.add_element("U", 1, percent_type="ao", enrichment=4.25)
fuel.add_element("O", 2)
fuel.set_density("g/cc", 10.4)
[4]:
clad = openmc.Material(name="clad")
[5]:
clad.add_element("Zr", 1)
clad.set_density("g/cc", 6)
[6]:
water = openmc.Material(name="water")
[7]:
water.add_element("O", 1)
water.add_element("H", 2)
water.set_density("g/cc", 1.0)
[8]:
water.add_s_alpha_beta("c_H_in_H2O")
[9]:
materials = openmc.Materials([fuel, clad, water])
Here, we are going to use the openmc.model.pin
function to build our pin cell. The pin
function anticipates concentric cylinders and materials to fill the inner regions. One additional material is needed than the number of cylinders to cover the domain outside the final ring.
To do this, we define two radii for the outer radius of our fuel pin, and the outer radius of the cladding.
[10]:
radii = [0.42, 0.45]
Using these radii, we define concentric ZCylinder
objects. So long as the cylinders are concentric and increasing in radius, any orientation can be used. We also take advantage of the fact that the openmc.Materials
object is a subclass of the list
object to assign materials to the regions defined by the surfaces.
[11]:
pin_surfaces = [openmc.ZCylinder(r=r) for r in radii]
[12]:
pin_univ = openmc.model.pin(pin_surfaces, materials)
The first material, in our case fuel
, is placed inside the first cylinder in the inner-most region. The second material, clad
, fills the space between our cylinders, while water
is placed outside the last ring. The pin
function returns an openmc.Universe
object, and has some additional features we will mention later.
[13]:
pin_univ.plot()
[13]:
<matplotlib.image.AxesImage at 0x7f9aea1a50d0>

[14]:
bound_box = openmc.rectangular_prism(0.62, 0.62, boundary_type="reflective")
[15]:
root_cell = openmc.Cell(fill=pin_univ, region=bound_box)
[16]:
root_univ = openmc.Universe(cells=[root_cell])
[17]:
geometry = openmc.Geometry(root_univ)
Lastly we construct our settings. For the sake of time, a relatively low number of particles will be used.
[18]:
settings = openmc.Settings()
[19]:
settings.particles = 100
settings.inactive = 10
settings.batches = 50
The depletion interface relies on OpenMC
to perform the transport simulation and obtain reaction rates and other important information. We then have to create the xml
input files that openmc
expects, specifically geometry.xml
, settings.xml
, and materials.xml
.
[20]:
geometry.export_to_xml()
[21]:
settings.export_to_xml()
Before we write the material file, we must add one bit of information: the volume of our fuel. In order to translate the reaction rates obtained by openmc
to meaningful units for depletion, we have to normalize them to a correct power. This requires us to know, or be able to calculate, how much fuel is in our problem. Correctly setting the volumes is a critical step, and can lead to incorrect answers, as the fuel is over- or under-depleted due to poor normalization.
For our problem, we can assign the “volume” to be the cross-sectional area of our fuel. This is identical to modeling our fuel pin inside a box with height of 1 cm.
[23]:
fuel.volume = math.pi * radii[0] ** 2
[24]:
materials.export_to_xml()
Setting up for depletion¶
The OpenMC depletion interface can be accessed from the openmc.deplete
module, and has a variety of classes that will help us.
[25]:
import openmc.deplete
In order to run the depletion calculation we need the following information:
- Nuclide decay, fission yield, and reaction data
- Operational power or power density
- Desired depletion schedule
- Desired time integration scheme
The first item is necessary to determine the paths by which nuclides transmute over the depletion simulation. This includes spontaneous decay, fission product yield distributions, and nuclides produced through neutron-reactions. For example, * Te129 decays to I129 with a half life of ~70 minutes * A fission event for U-235 produces fission products like Xe135 according to a distribution * For thermal problems, Am241 will produce metastable Am242 about 8% of the time during an \((n,\gamma)\) reaction. The other 92% of capture reactions will produce ground state Am242
These data are often distributed with other nuclear data, like incident neutron cross sections with ENDF/B-VII. OpenMC uses the `openmc.deplete.Chain
<https://docs.openmc.org/en/latest/pythonapi/generated/openmc.deplete.Chain.html#openmc.deplete.Chain>`__ to collect represent the various decay and transmutation pathways in a single object. While a complete Chain
can be created using nuclear data files, users may prefer to download pre-generated XML-representations instead. Such files can
be found at https://openmc.org/depletion-chains/ and include full and compressed chains, with capture branching ratios derived using PWR- or SFR-spectra.
For this problem, we will be using a much smaller depletion chain that contains very few nuclides. In a realistic problem, over 1000 isotopes may be included in the depletion chain.
[26]:
chain = openmc.deplete.Chain.from_xml("./chain_simple.xml")
[27]:
chain.nuclide_dict
[27]:
OrderedDict([('I135', 0),
('Xe135', 1),
('Xe136', 2),
('Cs135', 3),
('Gd157', 4),
('Gd156', 5),
('U234', 6),
('U235', 7),
('U238', 8)])
The primary entry point for depletion is the openmc.deplete.Operator
. It relies on the openmc.deplete.Chain
and helper classes to run openmc
, retrieve and normalize reaction rates, and other perform other tasks. For a thorough description, please see the full API documentation.
We will create our Operator using the geometry and settings from above, and our simple chain file. The materials are read in automatically using the materials.xml
file.
[28]:
operator = openmc.deplete.Operator(geometry, settings, "./chain_simple.xml")
We will then simulate our fuel pin operating at linear power of 174 W/cm, or 174 W given a unit height for our problem.
[29]:
power = 174
For this problem, we will take depletion step sizes of 30 days, and instruct OpenMC to re-run a transport simulation every 30 days until we have modeled the problem over a six month cycle. The depletion interface expects the time to be given in seconds, so we will have to convert. Note that these values are not cumulative.
[30]:
time_steps = [30 * 24 * 60 * 60] * 6
And lastly, we will use the basic predictor, or forward Euler, time integration scheme. Other, more advanced methods are provided to the user through openmc.deplete
[31]:
integrator = openmc.deplete.PredictorIntegrator(operator, time_steps, power)
To perform the simulation, we use the integrate
method, and let openmc
take care of the rest.
[32]:
integrator.integrate()
Processing the outputs¶
The depletion simulation produces a few output files. First, the statepoint files from each individual transport simulation are written to openmc_simulation_n<N>.h5
, where <N>
indicates the current depletion step. Any tallies that we defined in tallies.xml
will be included in these files across our simulations. We have 7 such files, one for each our of 6 depletion steps and the initial state.
[33]:
!ls *.h5
c5g7.h5 openmc_simulation_n2.h5 openmc_simulation_n6.h5
depletion_results.h5 openmc_simulation_n3.h5 statepoint.50.h5
openmc_simulation_n0.h5 openmc_simulation_n4.h5 summary.h5
openmc_simulation_n1.h5 openmc_simulation_n5.h5
The depletion_results.h5
file contains information that is aggregated over all time steps through depletion. This includes the multiplication factor, as well as concentrations. We can process this file using the openmc.deplete.ResultsList
object
[34]:
results = openmc.deplete.ResultsList.from_hdf5("./depletion_results.h5")
[35]:
time, k = results.get_eigenvalue()
[36]:
time /= (24 * 60 * 60) # convert back to days from seconds
[37]:
k
[37]:
array([[0.76882937, 0.00982155],
[0.75724033, 0.00827689],
[0.75532242, 0.01031746],
[0.74796855, 0.00919769],
[0.74066561, 0.01157708],
[0.73184492, 0.00971504],
[0.7207293 , 0.00703074]])
The first column of k
is the value of k-combined
at each point in our simulation, while the second column contains the associated uncertainty. We can plot this using matplotlib
[38]:
from matplotlib import pyplot
[39]:
pyplot.errorbar(time, k[:, 0], yerr=k[:, 1])
pyplot.xlabel("Time [d]")
pyplot.ylabel("$k_{eff}\pm \sigma$");

Due to the low number of particles selected, we have not only a very high uncertainty, but likely a horrendously poor fission source. This pin cell should have \(k>1\), but we can still see the decline over time due to fuel consumption.
We can then examine concentrations of atoms in each of our materials. This requires knowing the material ID, which can be obtained from the materials.xml
file.
[40]:
_time, u5 = results.get_atoms("1", "U235")
_time, xe135 = results.get_atoms("1", "Xe135")
[41]:
pyplot.plot(time, u5, label="U235")
pyplot.xlabel("Time [d]")
pyplot.ylabel("Number of atoms - U235");

[42]:
pyplot.plot(time, xe135, label="Xe135")
pyplot.xlabel("Time [d]")
pyplot.ylabel("Number of atoms - Xe135");

We can also examine reaction rates over time using the ResultsList
[43]:
_time, u5_fission = results.get_reaction_rate("1", "U235", "fission")
[44]:
pyplot.plot(time, u5_fission)
pyplot.xlabel("Time [d]")
pyplot.ylabel("Fission reactions / s");

Helpful tips¶
Depletion is a tricky task to get correct. Use too short of time steps and you may never get your results due to running many transport simulations. Use long of time steps and you may get incorrect answers. Consider the xenon plot from above. Xenon-135 is a fission product with a thermal absorption cross section on the order of millions of barns, but has a half life of ~9 hours. Taking smaller time steps at the beginning of your simulation to build up some equilibrium in your fission products is highly recommended.
When possible, differentiate materials that reappear in multiple places. If we had built an entire core with the single fuel
material, every pin would be depleted using the same averaged spectrum and reaction rates which is incorrect. The Operator
can differentiate these materials using the diff_burnable_mats
argument, but note that the volumes will be copied from the original material.
Using higher-order integrators, like the CECMIntegrator
, EPCRK4Integrator
with a fourth order Runge-Kutta, or the LEQIIntegrator
, can improve the accuracy of a simulation, or at least allow you to take longer depletion steps between transport simulations with similar accuracy.
Fuel pins with integrated burnable absorbers, like gadolinia, experience strong flux gradients until the absorbers are mostly burned away. This means that the spectrum and magnitude of the flux at the edge of the fuel pin can be vastly different than that in the interior. The helper pin
function can be used to subdivide regions into equal volume segments, as follows.
[45]:
div_surfs_1 = [openmc.ZCylinder(r=1)]
div_1 = openmc.model.pin(div_surfs_1, [fuel, water], subdivisions={0: 10})
[46]:
div_1.plot(width=(2.0, 2.0))
[46]:
<matplotlib.image.AxesImage at 0x7f9ae8572a90>

The innermost region has been divided into 10 equal volume regions. We can pass additional arguments to divide multiple regions, except for the region outside the last cylinder.
Register depletion chain¶
The depletion chain we created can be registered into the OpenMC cross_sections.xml
file, so we don’t have to always pass the chain_file
argument to the Operator
. To do this, we create a DataLibrary
using openmc.data
. Without any arguments, the from_xml
method will look for the file located at OPENMC_CROSS_SECTIONS
. For this example, we will just create a bare library.
[47]:
data_lib = openmc.data.DataLibrary()
[48]:
data_lib.register_file("./chain_simple.xml")
[49]:
data_lib.export_to_xml()
[50]:
!cat cross_sections.xml
<?xml version='1.0' encoding='utf-8'?>
<cross_sections>
<depletion_chain path="chain_simple.xml" type="depletion_chain" />
</cross_sections>
This allows us to make an Operator
simply with the geometry and settings arguments, provided we exported our library to OPENMC_CROSS_SECTIONS
. For a problem where we built and registered a Chain
using all the available nuclear data, we might see something like the following.
[51]:
new_op = openmc.deplete.Operator(geometry, settings)
[52]:
len(new_op.chain.nuclide_dict)
[52]:
3820
[53]:
[nuc.name for nuc in new_op.chain.nuclides[:10]]
[53]:
['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7', 'He3', 'He4', 'He5']
[54]:
[nuc.name for nuc in new_op.chain.nuclides[-10:]]
[54]:
['Ds268',
'Ds269',
'Ds270',
'Ds270_m1',
'Ds271',
'Ds271_m1',
'Ds272',
'Ds273',
'Ds279_m1',
'Rg272']
Choice of depletion step size¶
A general rule of thumb is to use depletion step sizes around 2 MWd/kgHM, where kgHM is really the initial heavy metal mass in kg. If your problem includes integral burnable absorbers, these typically require shorter time steps at or below 1 MWd/kgHM. These are typically valid for the predictor scheme, as the point of recent schemes is to extend this step size. A good convergence study, where the step size is decreased until some convergence metric is satisfied, is a beneficial exercise.
We can use the Operator
to determine our maximum step size using this recommendation. The heavy_metal
attribute returns the mass of initial heavy metal in g, which, using our power, can be used to compute this step size.
[55]:
operator.heavy_metal
[55]:
5.080339195584719
[56]:
max_step = 2 * operator.heavy_metal / power * 1E3
[57]:
print("\"Maximum\" depletion step: {:5.3} [d]".format(max_step))
"Maximum" depletion step: 58.4 [d]
Alternatively, if we were provided the power density of our problem, we can provide this directly with openmc.deplete.PredictorIntegrator(operator, time_steps, power_density=pdens)
. The values of power
and power_density
do not have to be scalars. For problems with variable power, we can provide an iterable with the same number of elements as time_steps
.
Geometry¶
Modeling Hexagonal Lattices¶
In this example, we will create a hexagonal lattice and show how the orientation can be changed via the cell rotation property. Let’s first just set up some materials and universes that we will use to fill the lattice.
[1]:
%matplotlib inline
import openmc
[2]:
fuel = openmc.Material(name='fuel')
fuel.add_nuclide('U235', 1.0)
fuel.set_density('g/cm3', 10.0)
fuel2 = openmc.Material(name='fuel2')
fuel2.add_nuclide('U238', 1.0)
fuel2.set_density('g/cm3', 10.0)
water = openmc.Material(name='water')
water.add_nuclide('H1', 2.0)
water.add_nuclide('O16', 1.0)
water.set_density('g/cm3', 1.0)
materials = openmc.Materials((fuel, fuel2, water))
materials.export_to_xml()
With our three materials, we will set up two universes that represent pin-cells: one with a small pin and one with a big pin. Since we will be using these universes in a lattice, it’s always a good idea to have an “outer” universe as well that is applied outside the defined lattice.
[3]:
r_pin = openmc.ZCylinder(r=0.25)
fuel_cell = openmc.Cell(fill=fuel, region=-r_pin)
water_cell = openmc.Cell(fill=water, region=+r_pin)
pin_universe = openmc.Universe(cells=(fuel_cell, water_cell))
r_big_pin = openmc.ZCylinder(r=0.5)
fuel2_cell = openmc.Cell(fill=fuel2, region=-r_big_pin)
water2_cell = openmc.Cell(fill=water, region=+r_big_pin)
big_pin_universe = openmc.Universe(cells=(fuel2_cell, water2_cell))
all_water_cell = openmc.Cell(fill=water)
outer_universe = openmc.Universe(cells=(all_water_cell,))
Now let’s create a hexagonal lattice using the HexLattice
class:
[4]:
lattice = openmc.HexLattice()
We need to set the center
of the lattice, the pitch
, an outer
universe (which is applied to all lattice elements outside of those that are defined), and a list of universes
. Let’s start with the easy ones first. Note that for a 2D lattice, we only need to specify a single number for the pitch.
[5]:
lattice.center = (0., 0.)
lattice.pitch = (1.25,)
lattice.outer = outer_universe
Now we need to set the universes
property on our lattice. It needs to be set to a list of lists of Universes, where each list of Universes corresponds to a ring of the lattice. The rings are ordered from outermost to innermost, and within each ring the indexing starts at the “top”. To help visualize the proper indices, we can use the show_indices()
helper method.
[6]:
print(lattice.show_indices(num_rings=4))
(0, 0)
(0,17) (0, 1)
(0,16) (1, 0) (0, 2)
(0,15) (1,11) (1, 1) (0, 3)
(1,10) (2, 0) (1, 2)
(0,14) (2, 5) (2, 1) (0, 4)
(1, 9) (3, 0) (1, 3)
(0,13) (2, 4) (2, 2) (0, 5)
(1, 8) (2, 3) (1, 4)
(0,12) (1, 7) (1, 5) (0, 6)
(0,11) (1, 6) (0, 7)
(0,10) (0, 8)
(0, 9)
Let’s set up a lattice where the first element in each ring is the big pin universe and all other elements are regular pin universes.
From the diagram above, we see that the outer ring has 18 elements, the first ring has 12, and the second ring has 6 elements. The innermost ring of any hexagonal lattice will have only a single element.
We build these rings through ‘list concatenation’ as follows:
[7]:
outer_ring = [big_pin_universe] + [pin_universe]*17 # Adds up to 18
ring_1 = [big_pin_universe] + [pin_universe]*11 # Adds up to 12
ring_2 = [big_pin_universe] + [pin_universe]*5 # Adds up to 6
inner_ring = [big_pin_universe]
We can now assign the rings (and the universes they contain) to our lattice.
[8]:
lattice.universes = [outer_ring,
ring_1,
ring_2,
inner_ring]
print(lattice)
HexLattice
ID = 4
Name =
Orientation = y
# Rings = 4
# Axial = None
Center = (0.0, 0.0)
Pitch = (1.25,)
Outer = 3
Universes
2
1 1
1 2 1
1 1 1 1
1 2 1
1 1 1 1
1 2 1
1 1 1 1
1 1 1
1 1 1 1
1 1 1
1 1
1
Now let’s put our lattice inside a circular cell that will serve as the top-level cell for our geometry.
[9]:
outer_surface = openmc.ZCylinder(r=5.0, boundary_type='vacuum')
main_cell = openmc.Cell(fill=lattice, region=-outer_surface)
geometry = openmc.Geometry([main_cell])
geometry.export_to_xml()
Now let’s create a plot to see what our geometry looks like.
[10]:
plot = openmc.Plot.from_geometry(geometry)
plot.color_by = 'material'
plot.colors = colors = {
water: 'blue',
fuel: 'olive',
fuel2: 'yellow'
}
plot.to_ipython_image()
[10]:

At this point, if we wanted to simulate the model, we would need to create an instance of openmc.Settings
, export it to XML, and run.
Lattice orientation¶
Now let’s say we want our hexagonal lattice orientated such that two sides of the lattice are parallel to the x-axis. This can be achieved by two means: either we can rotate the cell that contains the lattice, or we can can change the HexLattice.orientation
attribute. By default, the orientation
is set to “y”, indicating that two sides of the lattice are parallel to the y-axis, but we can also change it to “x” to make them parallel to the x-axis.
[11]:
# Change the orientation of the lattice and re-export the geometry
lattice.orientation = 'x'
geometry.export_to_xml()
# Run OpenMC in plotting mode
plot.to_ipython_image()
[11]:

When we change the orientation to ‘x’, you can see that the first universe in each ring starts to the right along the x-axis. As before, the universes are defined in a clockwise fashion around each ring. To see the proper indices for a hexagonal lattice in this orientation, we can again call show_indices
but pass an extra orientation argument:
[12]:
print(lattice.show_indices(4, orientation='x'))
(0,12) (0,13) (0,14) (0,15)
(0,11) (1, 8) (1, 9) (1,10) (0,16)
(0,10) (1, 7) (2, 4) (2, 5) (1,11) (0,17)
(0, 9) (1, 6) (2, 3) (3, 0) (2, 0) (1, 0) (0, 0)
(0, 8) (1, 5) (2, 2) (2, 1) (1, 1) (0, 1)
(0, 7) (1, 4) (1, 3) (1, 2) (0, 2)
(0, 6) (0, 5) (0, 4) (0, 3)
Hexagonal prisms¶
OpenMC also contains a convenience function that can create a hexagonal prism representing the interior region of six surfaces defining a hexagon. This can be useful as a bounding surface of a hexagonal lattice. For example, if we wanted the outer boundary of our geometry to be hexagonal, we could change the region
of the main cell:
[13]:
main_cell.region = openmc.model.hexagonal_prism(
edge_length=4*lattice.pitch[0],
orientation='x',
boundary_type='vacuum'
)
geometry.export_to_xml()
# Run OpenMC in plotting mode
plot.color_by = 'cell'
plot.to_ipython_image()
[13]:

Modeling TRISO Particles¶
OpenMC includes a few convenience functions for generationing TRISO particle locations and placing them in a lattice. To be clear, this capability is not a stochastic geometry capability like that included in MCNP. It’s also important to note that OpenMC does not use delta tracking, which would normally speed up calculations in geometries with tons of surfaces and cells. However, the computational burden can be eased by placing TRISO particles in a lattice.
[1]:
%matplotlib inline
from math import pi
import numpy as np
import matplotlib.pyplot as plt
import openmc
import openmc.model
Let’s first start by creating materials that will be used in our TRISO particles and the background material.
[2]:
fuel = openmc.Material(name='Fuel')
fuel.set_density('g/cm3', 10.5)
fuel.add_nuclide('U235', 4.6716e-02)
fuel.add_nuclide('U238', 2.8697e-01)
fuel.add_nuclide('O16', 5.0000e-01)
fuel.add_element('C', 1.6667e-01)
buff = openmc.Material(name='Buffer')
buff.set_density('g/cm3', 1.0)
buff.add_element('C', 1.0)
buff.add_s_alpha_beta('c_Graphite')
PyC1 = openmc.Material(name='PyC1')
PyC1.set_density('g/cm3', 1.9)
PyC1.add_element('C', 1.0)
PyC1.add_s_alpha_beta('c_Graphite')
PyC2 = openmc.Material(name='PyC2')
PyC2.set_density('g/cm3', 1.87)
PyC2.add_element('C', 1.0)
PyC2.add_s_alpha_beta('c_Graphite')
SiC = openmc.Material(name='SiC')
SiC.set_density('g/cm3', 3.2)
SiC.add_element('C', 0.5)
SiC.add_element('Si', 0.5)
graphite = openmc.Material()
graphite.set_density('g/cm3', 1.1995)
graphite.add_element('C', 1.0)
graphite.add_s_alpha_beta('c_Graphite')
To actually create individual TRISO particles, we first need to create a universe that will be used within each particle. The reason we use the same universe for each TRISO particle is to reduce the total number of cells/surfaces needed which can substantially improve performance over using unique cells/surfaces in each.
[3]:
# Create TRISO universe
spheres = [openmc.Sphere(r=1e-4*r)
for r in [215., 315., 350., 385.]]
cells = [openmc.Cell(fill=fuel, region=-spheres[0]),
openmc.Cell(fill=buff, region=+spheres[0] & -spheres[1]),
openmc.Cell(fill=PyC1, region=+spheres[1] & -spheres[2]),
openmc.Cell(fill=SiC, region=+spheres[2] & -spheres[3]),
openmc.Cell(fill=PyC2, region=+spheres[3])]
triso_univ = openmc.Universe(cells=cells)
Next, we need a region to pack the TRISO particles in. We will use a 1 cm x 1 cm x 1 cm box centered at the origin.
[4]:
min_x = openmc.XPlane(x0=-0.5, boundary_type='reflective')
max_x = openmc.XPlane(x0=0.5, boundary_type='reflective')
min_y = openmc.YPlane(y0=-0.5, boundary_type='reflective')
max_y = openmc.YPlane(y0=0.5, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-0.5, boundary_type='reflective')
max_z = openmc.ZPlane(z0=0.5, boundary_type='reflective')
region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
Now we need to randomly select locations for the TRISO particles. In this example, we will select locations at random within the box with a packing fraction of 30%. Note that pack_spheres
can handle up to the theoretical maximum of 60% (it will just be slow).
[5]:
outer_radius = 425.*1e-4
centers = openmc.model.pack_spheres(radius=outer_radius, region=region, pf=0.3)
Now that we have the locations of the TRISO particles determined and a universe that can be used for each particle, we can create the TRISO particles.
[6]:
trisos = [openmc.model.TRISO(outer_radius, triso_univ, center) for center in centers]
Each TRISO object actually is a Cell, in fact; we can look at the properties of the TRISO just as we would a cell:
[7]:
print(trisos[0])
Cell
ID = 6
Name =
Fill = 1
Region = -11
Rotation = None
Translation = [-0.33455672 0.31790187 0.24135378]
Let’s confirm that all our TRISO particles are within the box.
[8]:
centers = np.vstack([triso.center for triso in trisos])
print(centers.min(axis=0))
print(centers.max(axis=0))
[-0.45718713 -0.45730405 -0.45725048]
[0.45705454 0.45743843 0.45741142]
We can also look at what the actual packing fraction turned out to be:
[9]:
len(trisos)*4/3*pi*outer_radius**3
[9]:
0.2996893513959326
Now that we have our TRISO particles created, we need to place them in a lattice to provide optimal tracking performance in OpenMC. We can use the box we created above to place the lattice in. Actually creating a lattice containing TRISO particles can be done with the model.create_triso_lattice()
function. This function requires that we give it a list of TRISO particles, the lower-left coordinates of the lattice, the pitch of each lattice cell, the overall shape of the lattice (number of
cells in each direction), and a background material.
[10]:
box = openmc.Cell(region=region)
lower_left, upper_right = box.region.bounding_box
shape = (3, 3, 3)
pitch = (upper_right - lower_left)/shape
lattice = openmc.model.create_triso_lattice(
trisos, lower_left, pitch, shape, graphite)
Now we can set the fill of our box cell to be the lattice:
[11]:
box.fill = lattice
Finally, let’s take a look at our geometry by putting the box in a universe and plotting it. We’re going to use the Fortran-side plotter since it’s much faster.
[12]:
universe = openmc.Universe(cells=[box])
geometry = openmc.Geometry(universe)
geometry.export_to_xml()
materials = list(geometry.get_all_materials().values())
openmc.Materials(materials).export_to_xml()
settings = openmc.Settings()
settings.run_mode = 'plot'
settings.export_to_xml()
plot = openmc.Plot.from_geometry(geometry)
plot.to_ipython_image()
[12]:

If we plot the universe by material rather than by cell, we can see that the entire background is just graphite.
[13]:
plot.color_by = 'material'
plot.colors = {graphite: 'gray'}
plot.to_ipython_image()
[13]:

Modeling a CANDU Bundle¶
In this example, we will create a typical CANDU bundle with rings of fuel pins. At present, OpenMC does not have a specialized lattice for this type of fuel arrangement, so we must resort to manual creation of the array of fuel pins.
[1]:
%matplotlib inline
from math import pi, sin, cos
import numpy as np
import openmc
Let’s begin by creating the materials that will be used in our model.
[2]:
fuel = openmc.Material(name='fuel')
fuel.add_element('U', 1.0)
fuel.add_element('O', 2.0)
fuel.set_density('g/cm3', 10.0)
clad = openmc.Material(name='zircaloy')
clad.add_element('Zr', 1.0)
clad.set_density('g/cm3', 6.0)
heavy_water = openmc.Material(name='heavy water')
heavy_water.add_nuclide('H2', 2.0)
heavy_water.add_nuclide('O16', 1.0)
heavy_water.add_s_alpha_beta('c_D_in_D2O')
heavy_water.set_density('g/cm3', 1.1)
With our materials created, we’ll now define key dimensions in our model. These dimensions are taken from the example in section 11.1.3 of the Serpent manual.
[3]:
# Outer radius of fuel and clad
r_fuel = 0.6122
r_clad = 0.6540
# Pressure tube and calendria radii
pressure_tube_ir = 5.16890
pressure_tube_or = 5.60320
calendria_ir = 6.44780
calendria_or = 6.58750
# Radius to center of each ring of fuel pins
ring_radii = np.array([0.0, 1.4885, 2.8755, 4.3305])
To begin creating the bundle, we’ll first create annular regions completely filled with heavy water and add in the fuel pins later. The radii that we’ve specified above correspond to the center of each ring. We actually need to create cylindrical surfaces at radii that are half-way between the centers.
[4]:
# These are the surfaces that will divide each of the rings
radial_surf = [openmc.ZCylinder(r=r) for r in
(ring_radii[:-1] + ring_radii[1:])/2]
water_cells = []
for i in range(ring_radii.size):
# Create annular region
if i == 0:
water_region = -radial_surf[i]
elif i == ring_radii.size - 1:
water_region = +radial_surf[i-1]
else:
water_region = +radial_surf[i-1] & -radial_surf[i]
water_cells.append(openmc.Cell(fill=heavy_water, region=water_region))
Let’s see what our geometry looks like so far. In order to plot the geometry, we create a universe that contains the annular water cells and then use the Universe.plot()
method. While we’re at it, we’ll set some keyword arguments that can be reused for later plots.
[5]:
plot_args = {'width': (2*calendria_or, 2*calendria_or)}
bundle_universe = openmc.Universe(cells=water_cells)
bundle_universe.plot(**plot_args)
[5]:
<matplotlib.image.AxesImage at 0x154b1fe8f438>

Now we need to create a universe that contains a fuel pin. Note that we don’t actually need to put water outside of the cladding in this universe because it will be truncated by a higher universe.
[6]:
surf_fuel = openmc.ZCylinder(r=r_fuel)
fuel_cell = openmc.Cell(fill=fuel, region=-surf_fuel)
clad_cell = openmc.Cell(fill=clad, region=+surf_fuel)
pin_universe = openmc.Universe(cells=(fuel_cell, clad_cell))
[7]:
pin_universe.plot(**plot_args)
[7]:
<matplotlib.image.AxesImage at 0x154b1de334a8>

The code below works through each ring to create a cell containing the fuel pin universe. As each fuel pin is created, we modify the region of the water cell to include everything outside the fuel pin.
[8]:
num_pins = [1, 6, 12, 18]
angles = [0, 0, 15, 0]
for i, (r, n, a) in enumerate(zip(ring_radii, num_pins, angles)):
for j in range(n):
# Determine location of center of pin
theta = (a + j/n*360.) * pi/180.
x = r*cos(theta)
y = r*sin(theta)
pin_boundary = openmc.ZCylinder(x0=x, y0=y, r=r_clad)
water_cells[i].region &= +pin_boundary
# Create each fuel pin -- note that we explicitly assign an ID so
# that we can identify the pin later when looking at tallies
pin = openmc.Cell(fill=pin_universe, region=-pin_boundary)
pin.translation = (x, y, 0)
pin.id = (i + 1)*100 + j
bundle_universe.add_cell(pin)
[9]:
bundle_universe.plot(**plot_args)
[9]:
<matplotlib.image.AxesImage at 0x154b1ddaadd8>

Looking pretty good! Finally, we create cells for the pressure tube and calendria and then put our bundle in the middle of the pressure tube.
[10]:
pt_inner = openmc.ZCylinder(r=pressure_tube_ir)
pt_outer = openmc.ZCylinder(r=pressure_tube_or)
calendria_inner = openmc.ZCylinder(r=calendria_ir)
calendria_outer = openmc.ZCylinder(r=calendria_or, boundary_type='vacuum')
bundle = openmc.Cell(fill=bundle_universe, region=-pt_inner)
pressure_tube = openmc.Cell(fill=clad, region=+pt_inner & -pt_outer)
v1 = openmc.Cell(region=+pt_outer & -calendria_inner)
calendria = openmc.Cell(fill=clad, region=+calendria_inner & -calendria_outer)
root_universe = openmc.Universe(cells=[bundle, pressure_tube, v1, calendria])
Let’s look at the final product. We’ll export our geometry and materials and then use plot_inline()
to get a nice-looking plot.
[11]:
geometry = openmc.Geometry(root_universe)
geometry.export_to_xml()
materials = openmc.Materials(geometry.get_all_materials().values())
materials.export_to_xml()
[12]:
plot = openmc.Plot.from_geometry(geometry)
plot.color_by = 'material'
plot.colors = {
fuel: 'black',
clad: 'silver',
heavy_water: 'blue'
}
plot.to_ipython_image()
[12]:

Interpreting Results¶
One of the difficulties of a geometry like this is identifying tally results when there was no lattice involved. To address this, we specifically gave an ID to each fuel pin of the form 100*ring + azimuthal position. Consequently, we can use a distribcell tally and then look at our DataFrame
which will show these cell IDs.
[13]:
settings = openmc.Settings()
settings.particles = 1000
settings.batches = 20
settings.inactive = 10
settings.source = openmc.Source(space=openmc.stats.Point())
settings.export_to_xml()
[14]:
fuel_tally = openmc.Tally()
fuel_tally.filters = [openmc.DistribcellFilter(fuel_cell)]
fuel_tally.scores = ['flux']
tallies = openmc.Tallies([fuel_tally])
tallies.export_to_xml()
[15]:
openmc.run(output=False)
The return code of 0
indicates that OpenMC ran successfully. Now let’s load the statepoint into a openmc.StatePoint
object and use the Tally.get_pandas_dataframe(...)
method to see our results.
[16]:
sp = openmc.StatePoint('statepoint.{}.h5'.format(settings.batches))
[17]:
output_tally = sp.get_tally()
output_tally.get_pandas_dataframe()
[17]:
level 1 | level 2 | level 3 | distribcell | nuclide | score | mean | std. dev. | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
univ | cell | univ | cell | univ | cell | ||||||
id | id | id | id | id | id | ||||||
0 | 3 | 44 | 1 | 100 | 2 | 5 | 0 | total | flux | 0.207805 | 0.007037 |
1 | 3 | 44 | 1 | 200 | 2 | 5 | 1 | total | flux | 0.197197 | 0.005272 |
2 | 3 | 44 | 1 | 201 | 2 | 5 | 2 | total | flux | 0.190310 | 0.007816 |
3 | 3 | 44 | 1 | 202 | 2 | 5 | 3 | total | flux | 0.194736 | 0.006469 |
4 | 3 | 44 | 1 | 203 | 2 | 5 | 4 | total | flux | 0.191097 | 0.006431 |
5 | 3 | 44 | 1 | 204 | 2 | 5 | 5 | total | flux | 0.189910 | 0.004891 |
6 | 3 | 44 | 1 | 205 | 2 | 5 | 6 | total | flux | 0.182203 | 0.003851 |
7 | 3 | 44 | 1 | 300 | 2 | 5 | 7 | total | flux | 0.165922 | 0.005815 |
8 | 3 | 44 | 1 | 301 | 2 | 5 | 8 | total | flux | 0.168933 | 0.008300 |
9 | 3 | 44 | 1 | 302 | 2 | 5 | 9 | total | flux | 0.159587 | 0.003085 |
10 | 3 | 44 | 1 | 303 | 2 | 5 | 10 | total | flux | 0.159158 | 0.005910 |
11 | 3 | 44 | 1 | 304 | 2 | 5 | 11 | total | flux | 0.148537 | 0.005308 |
12 | 3 | 44 | 1 | 305 | 2 | 5 | 12 | total | flux | 0.150945 | 0.006654 |
13 | 3 | 44 | 1 | 306 | 2 | 5 | 13 | total | flux | 0.154237 | 0.003665 |
14 | 3 | 44 | 1 | 307 | 2 | 5 | 14 | total | flux | 0.165888 | 0.004733 |
15 | 3 | 44 | 1 | 308 | 2 | 5 | 15 | total | flux | 0.156777 | 0.006540 |
16 | 3 | 44 | 1 | 309 | 2 | 5 | 16 | total | flux | 0.165277 | 0.005935 |
17 | 3 | 44 | 1 | 310 | 2 | 5 | 17 | total | flux | 0.156528 | 0.005732 |
18 | 3 | 44 | 1 | 311 | 2 | 5 | 18 | total | flux | 0.159610 | 0.004584 |
19 | 3 | 44 | 1 | 400 | 2 | 5 | 19 | total | flux | 0.096597 | 0.004466 |
20 | 3 | 44 | 1 | 401 | 2 | 5 | 20 | total | flux | 0.118214 | 0.005451 |
21 | 3 | 44 | 1 | 402 | 2 | 5 | 21 | total | flux | 0.106167 | 0.004722 |
22 | 3 | 44 | 1 | 403 | 2 | 5 | 22 | total | flux | 0.110814 | 0.004208 |
23 | 3 | 44 | 1 | 404 | 2 | 5 | 23 | total | flux | 0.112319 | 0.005079 |
24 | 3 | 44 | 1 | 405 | 2 | 5 | 24 | total | flux | 0.110232 | 0.004153 |
25 | 3 | 44 | 1 | 406 | 2 | 5 | 25 | total | flux | 0.099967 | 0.005085 |
26 | 3 | 44 | 1 | 407 | 2 | 5 | 26 | total | flux | 0.095444 | 0.003615 |
27 | 3 | 44 | 1 | 408 | 2 | 5 | 27 | total | flux | 0.092620 | 0.003997 |
28 | 3 | 44 | 1 | 409 | 2 | 5 | 28 | total | flux | 0.095517 | 0.004022 |
29 | 3 | 44 | 1 | 410 | 2 | 5 | 29 | total | flux | 0.113737 | 0.009530 |
30 | 3 | 44 | 1 | 411 | 2 | 5 | 30 | total | flux | 0.108368 | 0.007241 |
31 | 3 | 44 | 1 | 412 | 2 | 5 | 31 | total | flux | 0.106990 | 0.005716 |
32 | 3 | 44 | 1 | 413 | 2 | 5 | 32 | total | flux | 0.112050 | 0.005002 |
33 | 3 | 44 | 1 | 414 | 2 | 5 | 33 | total | flux | 0.115054 | 0.006239 |
34 | 3 | 44 | 1 | 415 | 2 | 5 | 34 | total | flux | 0.114394 | 0.004919 |
35 | 3 | 44 | 1 | 416 | 2 | 5 | 35 | total | flux | 0.114352 | 0.005322 |
36 | 3 | 44 | 1 | 417 | 2 | 5 | 36 | total | flux | 0.110890 | 0.005051 |
We can see that in the ‘level 2’ column, the ‘cell id’ tells us how each row corresponds to a ring and azimuthal position.
Using CAD-Based Geometries¶
In this notebook we’ll be exploring how to use CAD-based geometries in OpenMC via the DagMC toolkit. The models we’ll be using in this notebook have already been created using Trelis and faceted into a surface mesh represented as .h5m
files in the Mesh Oriented DatABase format. We’ll be retrieving these files using the function below.
[1]:
import urllib.request
fuel_pin_url = 'https://tinyurl.com/y3ugwz6w' # 1.2 MB
teapot_url = 'https://tinyurl.com/y4mcmc3u' # 29 MB
def download(url):
"""
Helper function for retrieving dagmc models
"""
u = urllib.request.urlopen(url)
if u.status != 200:
raise RuntimeError("Failed to download file.")
# save file as dagmc.h5m
with open("dagmc.h5m", 'wb') as f:
f.write(u.read())
This notebook is intended to demonstrate how DagMC problems are run in OpenMC. For more information on how DagMC models are created, please refer to the DagMC User’s Guide.
[2]:
%matplotlib inline
from IPython.display import Image
import openmc
To start, we’ll be using a simple U235 fuel pin surrounded by a water moderator, so let’s create those materials.
[3]:
# materials
u235 = openmc.Material(name="fuel")
u235.add_nuclide('U235', 1.0, 'ao')
u235.set_density('g/cc', 11)
u235.id = 40
water = openmc.Material(name="water")
water.add_nuclide('H1', 2.0, 'ao')
water.add_nuclide('O16', 1.0, 'ao')
water.set_density('g/cc', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')
water.id = 41
mats = openmc.Materials([u235, water])
mats.export_to_xml()
Now let’s get our DAGMC geometry. We’ll be using prefabricated models in this notebook. For information on how to create your own DAGMC models, you can refer to the instructions here.
Let’s download the DAGMC model. These models come in the form of triangle surface meshes stored using the the Mesh Oriented datABase (MOAB) in an HDF5 file with the extension .h5m
. An example of a coarse triangle mesh looks like:
[4]:
Image("./images/cylinder_mesh.png", width=350)
First we’ll need to grab some pre-made DagMC models.
[5]:
download(fuel_pin_url)
OpenMC expects that the model has the name “dagmc.h5m” so we’ll name the file that and indicate to OpenMC that a DAGMC geometry is being used by setting the settings.dagmc
attribute to True
.
[6]:
settings = openmc.Settings()
settings.dagmc = True
settings.batches = 10
settings.inactive = 2
settings.particles = 5000
settings.export_to_xml()
Unlike conventional geometries in OpenMC, we really have no way of knowing what our model looks like at this point. Thankfully DagMC geometries can be plotted just like any other OpenMC geometry to give us an idea of what we’re now working with.
Note that material assignments have already been applied to this model. Materials can be assigned either using ids or names of materials in the materials.xml
file. It is recommended that material names are used for assignment for readability.
[7]:
p = openmc.Plot()
p.width = (25.0, 25.0)
p.pixels = (400, 400)
p.color_by = 'material'
p.colors = {u235: 'yellow', water: 'blue'}
openmc.plot_inline(p)

Now that we’ve had a chance to examine the model a bit, we can finish applying our settings and add a source.
[8]:
settings.source = openmc.Source(space=openmc.stats.Box([-4., -4., -4.],
[ 4., 4., 4.]))
settings.export_to_xml()
Tallies work in the same way when using DAGMC geometries too. We’ll add a tally on the fuel cell here.
[9]:
tally = openmc.Tally()
tally.scores = ['total']
tally.filters = [openmc.CellFilter(1)]
tallies = openmc.Tallies([tally])
tallies.export_to_xml()
Note: Applying tally filters in DagMC models requires prior knowledge of the model. Here, we know that the fuel cell’s volume ID in the CAD sofware is 1. To identify cells without use of CAD software, load them into the OpenMC plotter where cell, material, and volume IDs can be identified for native both OpenMC and DagMC geometries.
Now we’re ready to run the simulation just like any other OpenMC run.
[10]:
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 2c0b16e73d5d81a2f849f4e2bfae5eb5319f2417
Date/Time | 2019-06-18 08:54:17
OpenMP Threads | 2
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading DAGMC geometry...
Loading file dagmc.h5m
Initializing the GeomQueryTool...
Using faceting tolerance: 0.0001
Building OBB Tree...
Reading U235 from /home/shriwise/opt/openmc/xs/nndc_hdf5/U235.h5
Reading H1 from /home/shriwise/opt/openmc/xs/nndc_hdf5/H1.h5
Reading O16 from /home/shriwise/opt/openmc/xs/nndc_hdf5/O16.h5
Reading c_H_in_H2O from /home/shriwise/opt/openmc/xs/nndc_hdf5/c_H_in_H2O.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.15789
2/1 1.05169
3/1 1.00736
4/1 0.97863 0.99300 +/- 0.01436
5/1 0.95316 0.97972 +/- 0.01566
6/1 0.95079 0.97248 +/- 0.01322
7/1 0.96879 0.97175 +/- 0.01027
8/1 0.94253 0.96688 +/- 0.00970
9/1 0.97406 0.96790 +/- 0.00826
10/1 0.97362 0.96862 +/- 0.00719
Creating state point statepoint.10.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 2.4575e-01 seconds
Reading cross sections = 1.3129e-01 seconds
Total time in simulation = 1.6897e+00 seconds
Time in transport only = 1.6829e+00 seconds
Time in inactive batches = 3.1605e-01 seconds
Time in active batches = 1.3737e+00 seconds
Time synchronizing fission bank = 2.8739e-03 seconds
Sampling source sites = 2.5550e-03 seconds
SEND/RECV source sites = 2.8512e-04 seconds
Time accumulating tallies = 7.8450e-06 seconds
Total time for finalization = 1.7404e-04 seconds
Total time elapsed = 1.9588e+00 seconds
Calculation Rate (inactive) = 31640.8 particles/second
Calculation Rate (active) = 29119.1 particles/second
============================> RESULTS <============================
k-effective (Collision) = 0.97020 +/- 0.00750
k-effective (Track-length) = 0.96862 +/- 0.00719
k-effective (Absorption) = 0.95742 +/- 0.00791
Combined k-effective = 0.96203 +/- 0.00898
Leakage Fraction = 0.57677 +/- 0.00317
More Complicated Geometry¶
Neat! But this pincell is something we could’ve done with CSG. Let’s take a look at something more complex. We’ll download a pre-built model of the Utah teapot and use it here.
[11]:
download(teapot_url)
[12]:
Image("./images/teapot.jpg", width=600)
Our teapot is made out of iron, so we’ll want to create that material and make sure it is in our materials.xml
file.
[13]:
iron = openmc.Material(name="iron")
iron.add_nuclide("Fe54", 0.0564555822608)
iron.add_nuclide("Fe56", 0.919015287728)
iron.add_nuclide("Fe57", 0.0216036861685)
iron.add_nuclide("Fe58", 0.00292544384231)
iron.set_density("g/cm3", 7.874)
mats = openmc.Materials([iron, water])
mats.export_to_xml()
To make sure we’ve updated the file correctly, let’s make a plot of the teapot.
[14]:
p = openmc.Plot()
p.basis = 'xz'
p.origin = (0.0, 0.0, 0.0)
p.width = (30.0, 20.0)
p.pixels = (450, 300)
p.color_by = 'material'
p.colors = {iron: 'gray', water: 'blue'}
openmc.plot_inline(p)

Here we start to see some of the advantages CAD geometries provide. This particular file was pulled from the GrabCAD and pushed through the DAGMC workflow without modification (other than the addition of material assignments). It would take a considerable amount of time to create a model like this using CSG!
[15]:
p.width = (18.0, 6.0)
p.basis = 'xz'
p.origin = (10.0, 0.0, 5.0)
p.pixels = (600, 200)
p.color_by = 'material'
openmc.plot_inline(p)

Now let’s brew some tea! … using a very hot neutron source. We’ll use some well-placed point sources distributed throughout the model.
[16]:
settings = openmc.Settings()
settings.dagmc = True
settings.batches = 10
settings.particles = 5000
settings.run_mode = "fixed source"
src_locations = ((-4.0, 0.0, -2.0),
( 4.0, 0.0, -2.0),
( 4.0, 0.0, -6.0),
(-4.0, 0.0, -6.0),
(10.0, 0.0, -4.0),
(-8.0, 0.0, -4.0))
# we'll use the same energy for each source
src_e = openmc.stats.Discrete(x=[12.0,], p=[1.0,])
# create source for each location
sources = []
for loc in src_locations:
src_pnt = openmc.stats.Point(xyz=loc)
src = openmc.Source(space=src_pnt, energy=src_e)
sources.append(src)
src_str = 1.0 / len(sources)
for source in sources:
source.strength = src_str
settings.source = sources
settings.export_to_xml()
…and setup a couple mesh tallies. One for the kettle, and one for the water inside.
[17]:
mesh = openmc.RegularMesh()
mesh.dimension = (120, 1, 40)
mesh.lower_left = (-20.0, 0.0, -10.0)
mesh.upper_right = (20.0, 1.0, 4.0)
mesh_filter = openmc.MeshFilter(mesh)
pot_filter = openmc.CellFilter([1])
pot_tally = openmc.Tally()
pot_tally.filters = [mesh_filter, pot_filter]
pot_tally.scores = ['flux']
water_filter = openmc.CellFilter([5])
water_tally = openmc.Tally()
water_tally.filters = [mesh_filter, water_filter]
water_tally.scores = ['flux']
tallies = openmc.Tallies([pot_tally, water_tally])
tallies.export_to_xml()
[18]:
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 2c0b16e73d5d81a2f849f4e2bfae5eb5319f2417
Date/Time | 2019-06-18 08:54:35
OpenMP Threads | 2
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading DAGMC geometry...
Loading file dagmc.h5m
Initializing the GeomQueryTool...
Using faceting tolerance: 0.001
Building OBB Tree...
Reading Fe54 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe54.h5
Reading Fe56 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe56.h5
Reading Fe57 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe57.h5
Reading Fe58 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe58.h5
Reading H1 from /home/shriwise/opt/openmc/xs/nndc_hdf5/H1.h5
Reading O16 from /home/shriwise/opt/openmc/xs/nndc_hdf5/O16.h5
Reading c_H_in_H2O from /home/shriwise/opt/openmc/xs/nndc_hdf5/c_H_in_H2O.h5
Maximum neutron transport energy: 20000000.000000 eV for Fe58
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
===============> FIXED SOURCE TRANSPORT SIMULATION <===============
Simulating batch 1
Simulating batch 2
Simulating batch 3
Simulating batch 4
Simulating batch 5
Simulating batch 6
Simulating batch 7
Simulating batch 8
Simulating batch 9
Simulating batch 10
Creating state point statepoint.10.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 4.9659e+00 seconds
Reading cross sections = 6.3783e-01 seconds
Total time in simulation = 1.5112e+01 seconds
Time in transport only = 1.4102e+01 seconds
Time in active batches = 1.5112e+01 seconds
Time accumulating tallies = 2.8790e-04 seconds
Total time for finalization = 3.2375e-02 seconds
Total time elapsed = 2.0224e+01 seconds
Calculation Rate (active) = 3308.59 particles/second
============================> RESULTS <============================
Leakage Fraction = 0.62594 +/- 0.00117
Note that the performance is significantly lower than our pincell model due to the increased complexity of the model, but it allows us to examine tally results like these:
[19]:
sp = openmc.StatePoint("statepoint.10.h5")
water_tally = sp.get_tally(scores=['flux'], id=water_tally.id)
water_flux = water_tally.mean
water_flux.shape = (40, 120)
water_flux = water_flux[::-1, :]
pot_tally = sp.get_tally(scores=['flux'], id=pot_tally.id)
pot_flux = pot_tally.mean
pot_flux.shape = (40, 120)
pot_flux = pot_flux[::-1, :]
del sp
[20]:
from matplotlib import pyplot as plt
fig = plt.figure(figsize=(18, 16))
sub_plot1 = plt.subplot(121, title="Kettle Flux")
sub_plot1.imshow(pot_flux)
sub_plot2 = plt.subplot(122, title="Water Flux")
sub_plot2.imshow(water_flux)
[20]:
<matplotlib.image.AxesImage at 0x7f90d12b8198>

Multigroup Cross Section Generation¶
Multigroup Cross Section Generation Part I: Introduction¶
This IPython Notebook introduces the use of the openmc.mgxs
module to calculate multi-group cross sections for an infinite homogeneous medium. In particular, this Notebook introduces the the following features:
- General equations for scalar-flux averaged multi-group cross sections
- Creation of multi-group cross sections for an infinite homogeneous medium
- Use of tally arithmetic to manipulate multi-group cross sections
Introduction to Multi-Group Cross Sections (MGXS)¶
Many Monte Carlo particle transport codes, including OpenMC, use continuous-energy nuclear cross section data. However, most deterministic neutron transport codes use multi-group cross sections defined over discretized energy bins or energy groups. An example of U-235’s continuous-energy fission cross section along with a 16-group cross section computed for a light water reactor spectrum is displayed below.
[1]:
from IPython.display import Image
Image(filename='images/mgxs.png', width=350)
A variety of tools employing different methodologies have been developed over the years to compute multi-group cross sections for certain applications, including NJOY (LANL), MC\(^2\)-3 (ANL), and Serpent (VTT). The openmc.mgxs
Python module is designed to leverage OpenMC’s tally system to calculate multi-group cross sections with arbitrary energy discretizations for fine-mesh heterogeneous deterministic neutron transport applications.
Before proceeding to illustrate how one may use the openmc.mgxs
module, it is worthwhile to define the general equations used to calculate multi-group cross sections. This is only intended as a brief overview of the methodology used by openmc.mgxs
- we refer the interested reader to the large body of literature on the subject for a more comprehensive understanding of this complex topic.
Introductory Notation¶
The continuous real-valued microscopic cross section may be denoted \(\sigma_{n,x}(\mathbf{r}, E)\) for position vector \(\mathbf{r}\), energy \(E\), nuclide \(n\) and interaction type \(x\). Similarly, the scalar neutron flux may be denoted by \(\Phi(\mathbf{r},E)\) for position \(\mathbf{r}\) and energy \(E\). Note: Although nuclear cross sections are dependent on the temperature \(T\) of the interacting medium, the temperature variable is neglected here for brevity.
Spatial and Energy Discretization¶
The energy domain for critical systems such as thermal reactors spans more than 10 orders of magnitude of neutron energies from 10\(^{-5}\) - 10\(^7\) eV. The multi-group approximation discretization divides this energy range into one or more energy groups. In particular, for \(G\) total groups, we denote an energy group index \(g\) such that \(g \in \{1, 2, ..., G\}\). The energy group indices are defined such that the smaller group the higher the energy, and vice versa. The integration over neutron energies across a discrete energy group is commonly referred to as energy condensation.
Multi-group cross sections are computed for discretized spatial zones in the geometry of interest. The spatial zones may be defined on a structured and regular fuel assembly or pin cell mesh, an arbitrary unstructured mesh or the constructive solid geometry used by OpenMC. For a geometry with \(K\) distinct spatial zones, we designate each spatial zone an index \(k\) such that \(k \in \{1, 2, ..., K\}\). The volume of each spatial zone is denoted by \(V_{k}\). The integration over discrete spatial zones is commonly referred to as spatial homogenization.
General Scalar-Flux Weighted MGXS¶
The multi-group cross sections computed by openmc.mgxs
are defined as a scalar flux-weighted average of the microscopic cross sections across each discrete energy group. This formulation is employed in order to preserve the reaction rates within each energy group and spatial zone. In particular, spatial homogenization and energy condensation are used to compute the general multi-group cross section \(\sigma_{n,x,k,g}\) as follows:
This scalar flux-weighted average microscopic cross section is computed by openmc.mgxs
for most multi-group cross sections, including total, absorption, and fission reaction types. These double integrals are stochastically computed with OpenMC’s tally system - in particular, filters on the energy range and spatial zone (material, cell or universe) define the bounds of integration for both numerator and denominator.
Multi-Group Scattering Matrices¶
The general multi-group cross section \(\sigma_{n,x,k,g}\) is a vector of \(G\) values for each energy group \(g\). The equation presented above only discretizes the energy of the incoming neutron and neglects the outgoing energy of the neutron (if any). Hence, this formulation must be extended to account for the outgoing energy of neutrons in the discretized scattering matrix cross section used by deterministic neutron transport codes.
We denote the incoming and outgoing neutron energy groups as \(g\) and \(g'\) for the microscopic scattering matrix cross section \(\sigma_{n,s}(\mathbf{r},E)\). As before, spatial homogenization and energy condensation are used to find the multi-group scattering matrix cross section \(\sigma_{n,s,k,g \to g'}\) as follows:
This scalar flux-weighted multi-group microscopic scattering matrix is computed using OpenMC tallies with both energy in and energy out filters.
Multi-Group Fission Spectrum¶
The energy spectrum of neutrons emitted from fission is denoted by \(\chi_{n}(\mathbf{r},E' \rightarrow E'')\) for incoming and outgoing energies \(E'\) and \(E''\), respectively. Unlike the multi-group cross sections \(\sigma_{n,x,k,g}\) considered up to this point, the fission spectrum is a probability distribution and must sum to unity. The outgoing energy is typically much less dependent on the incoming energy for fission than for scattering interactions. As a result, it is common practice to integrate over the incoming neutron energy when computing the multi-group fission spectrum. The fission spectrum may be simplified as \(\chi_{n}(\mathbf{r},E)\) with outgoing energy \(E\).
Unlike the multi-group cross sections defined up to this point, the multi-group fission spectrum is weighted by the fission production rate rather than the scalar flux. This formulation is intended to preserve the total fission production rate in the multi-group deterministic calculation. In order to mathematically define the multi-group fission spectrum, we denote the microscopic fission cross section as \(\sigma_{n,f}(\mathbf{r},E)\) and the average number of neutrons emitted from fission interactions with nuclide \(n\) as \(\nu_{n}(\mathbf{r},E)\). The multi-group fission spectrum \(\chi_{n,k,g}\) is then the probability of fission neutrons emitted into energy group \(g\).
Similar to before, spatial homogenization and energy condensation are used to find the multi-group fission spectrum \(\chi_{n,k,g}\) as follows:
The fission production-weighted multi-group fission spectrum is computed using OpenMC tallies with both energy in and energy out filters.
This concludes our brief overview on the methodology to compute multi-group cross sections. The following sections detail more concretely how users may employ the openmc.mgxs
module to power simulation workflows requiring multi-group cross sections for downstream deterministic calculations.
Generate Input Files¶
[2]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import openmc
import openmc.mgxs as mgxs
We being by creating a material for the homogeneous medium.
[3]:
# Instantiate a Material and register the Nuclides
inf_medium = openmc.Material(name='moderator')
inf_medium.set_density('g/cc', 5.)
inf_medium.add_nuclide('H1', 0.028999667)
inf_medium.add_nuclide('O16', 0.01450188)
inf_medium.add_nuclide('U235', 0.000114142)
inf_medium.add_nuclide('U238', 0.006886019)
inf_medium.add_nuclide('Zr90', 0.002116053)
With our material, we can now create a Materials
object that can be exported to an actual XML file.
[4]:
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([inf_medium])
materials_file.export_to_xml()
Now let’s move on to the geometry. This problem will be a simple square cell with reflective boundary conditions to simulate an infinite homogeneous medium. The first step is to create the outer bounding surfaces of the problem.
[5]:
# Instantiate boundary Planes
min_x = openmc.XPlane(boundary_type='reflective', x0=-0.63)
max_x = openmc.XPlane(boundary_type='reflective', x0=0.63)
min_y = openmc.YPlane(boundary_type='reflective', y0=-0.63)
max_y = openmc.YPlane(boundary_type='reflective', y0=0.63)
With the surfaces defined, we can now create a cell that is defined by intersections of half-spaces created by the surfaces.
[6]:
# Instantiate a Cell
cell = openmc.Cell(cell_id=1, name='cell')
# Register bounding Surfaces with the Cell
cell.region = +min_x & -max_x & +min_y & -max_y
# Fill the Cell with the Material
cell.fill = inf_medium
OpenMC requires that there is a “root” universe. Let us create a root universe and add our square cell to it.
[7]:
# Create root universe
root_universe = openmc.Universe(name='root universe', cells=[cell])
We now must create a geometry that is assigned a root universe and export it to XML.
[8]:
# Create Geometry and set root Universe
openmc_geometry = openmc.Geometry(root_universe)
# Export to "geometry.xml"
openmc_geometry.export_to_xml()
Next, we must define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 2500 particles.
[9]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 2500
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': True}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -0.63, 0.63, 0.63, 0.63]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Now we are ready to generate multi-group cross sections! First, let’s define a 2-group structure using the built-in EnergyGroups
class.
[10]:
# Instantiate a 2-group EnergyGroups object
groups = mgxs.EnergyGroups()
groups.group_edges = np.array([0., 0.625, 20.0e6])
We can now use the EnergyGroups
object, along with our previously created materials and geometry, to instantiate some MGXS
objects from the openmc.mgxs
module. In particular, the following are subclasses of the generic and abstract MGXS
class:
TotalXS
TransportXS
AbsorptionXS
CaptureXS
FissionXS
KappaFissionXS
ScatterXS
ScatterMatrixXS
Chi
ChiPrompt
InverseVelocity
PromptNuFissionXS
Of course, we are aware that the fission cross section (FissionXS
) can sometimes be paired with the fission neutron multiplication to become \(\nu\sigma_f\). This can be accomodated in to the FissionXS
class by setting the nu
parameter to True
as shown below.
Additionally, scattering reactions (like (n,2n)) can also be defined to take in to account the neutron multiplication to become \(\nu\sigma_s\). This can be accomodated in the the transport (TransportXS
), scattering (ScatterXS
), and scattering-matrix (ScatterMatrixXS
) cross sections types by setting the nu
parameter to True
as shown below.
These classes provide us with an interface to generate the tally inputs as well as perform post-processing of OpenMC’s tally data to compute the respective multi-group cross sections. In this case, let’s create the multi-group total, absorption and scattering cross sections with our 2-group structure.
[11]:
# Instantiate a few different sections
total = mgxs.TotalXS(domain=cell, groups=groups)
absorption = mgxs.AbsorptionXS(domain=cell, groups=groups)
scattering = mgxs.ScatterXS(domain=cell, groups=groups)
# Note that if we wanted to incorporate neutron multiplication in the
# scattering cross section we would write the previous line as:
# scattering = mgxs.ScatterXS(domain=cell, groups=groups, nu=True)
Each multi-group cross section object stores its tallies in a Python dictionary called tallies
. We can inspect the tallies in the dictionary for our Absorption
object as follows.
[12]:
absorption.tallies
[12]:
OrderedDict([('flux', Tally
ID = 1
Name =
Filters = CellFilter, EnergyFilter
Nuclides = total
Scores = ['flux']
Estimator = tracklength), ('absorption', Tally
ID = 2
Name =
Filters = CellFilter, EnergyFilter
Nuclides = total
Scores = ['absorption']
Estimator = tracklength)])
The Absorption
object includes tracklength tallies for the ‘absorption’ and ‘flux’ scores in the 2-group structure in cell 1. Now that each MGXS
object contains the tallies that it needs, we must add these tallies to a Tallies
object to generate the “tallies.xml” input file for OpenMC.
[13]:
# Instantiate an empty Tallies object
tallies_file = openmc.Tallies()
# Add total tallies to the tallies file
tallies_file += total.tallies.values()
# Add absorption tallies to the tallies file
tallies_file += absorption.tallies.values()
# Add scattering tallies to the tallies file
tallies_file += scattering.tallies.values()
# Export to "tallies.xml"
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=3.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=4.
warn(msg, IDWarning)
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[14]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2017 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.9.0
Git SHA1 | 9b7cebf7bc34d60e0f1750c3d6cb103df11e8dc4
Date/Time | 2017-12-04 20:56:46
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Building neighboring cells lists for each surface...
Reading H1 from /home/romano/openmc/scripts/nndc_hdf5/H1.h5
Reading O16 from /home/romano/openmc/scripts/nndc_hdf5/O16.h5
Reading U235 from /home/romano/openmc/scripts/nndc_hdf5/U235.h5
Reading U238 from /home/romano/openmc/scripts/nndc_hdf5/U238.h5
Reading Zr90 from /home/romano/openmc/scripts/nndc_hdf5/Zr90.h5
Maximum neutron transport energy: 2.00000E+07 eV for H1
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.11184
2/1 1.15820
3/1 1.18468
4/1 1.17492
5/1 1.19645
6/1 1.18436
7/1 1.14070
8/1 1.15150
9/1 1.19202
10/1 1.17677
11/1 1.20272
12/1 1.21366 1.20819 +/- 0.00547
13/1 1.15906 1.19181 +/- 0.01668
14/1 1.14687 1.18058 +/- 0.01629
15/1 1.14570 1.17360 +/- 0.01442
16/1 1.13480 1.16713 +/- 0.01343
17/1 1.17680 1.16852 +/- 0.01144
18/1 1.16866 1.16853 +/- 0.00990
19/1 1.19253 1.17120 +/- 0.00913
20/1 1.18124 1.17220 +/- 0.00823
21/1 1.19206 1.17401 +/- 0.00766
22/1 1.17681 1.17424 +/- 0.00700
23/1 1.17634 1.17440 +/- 0.00644
24/1 1.13659 1.17170 +/- 0.00654
25/1 1.17144 1.17169 +/- 0.00609
26/1 1.20649 1.17386 +/- 0.00610
27/1 1.11238 1.17024 +/- 0.00678
28/1 1.18911 1.17129 +/- 0.00647
29/1 1.14681 1.17000 +/- 0.00626
30/1 1.12152 1.16758 +/- 0.00641
31/1 1.12729 1.16566 +/- 0.00639
32/1 1.15399 1.16513 +/- 0.00612
33/1 1.13547 1.16384 +/- 0.00599
34/1 1.17723 1.16440 +/- 0.00576
35/1 1.09296 1.16154 +/- 0.00622
36/1 1.19621 1.16287 +/- 0.00612
37/1 1.12560 1.16149 +/- 0.00605
38/1 1.17872 1.16211 +/- 0.00586
39/1 1.17721 1.16263 +/- 0.00568
40/1 1.13724 1.16178 +/- 0.00555
41/1 1.18526 1.16254 +/- 0.00542
42/1 1.13779 1.16177 +/- 0.00531
43/1 1.15066 1.16143 +/- 0.00516
44/1 1.12174 1.16026 +/- 0.00514
45/1 1.17478 1.16068 +/- 0.00501
46/1 1.14146 1.16014 +/- 0.00489
47/1 1.20464 1.16135 +/- 0.00491
48/1 1.15119 1.16108 +/- 0.00479
49/1 1.17938 1.16155 +/- 0.00468
50/1 1.15798 1.16146 +/- 0.00457
Creating state point statepoint.50.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 4.0504E-01 seconds
Reading cross sections = 3.6457E-01 seconds
Total time in simulation = 6.3478E+00 seconds
Time in transport only = 6.0079E+00 seconds
Time in inactive batches = 8.1713E-01 seconds
Time in active batches = 5.5307E+00 seconds
Time synchronizing fission bank = 5.4640E-03 seconds
Sampling source sites = 4.0981E-03 seconds
SEND/RECV source sites = 1.2606E-03 seconds
Time accumulating tallies = 1.2030E-04 seconds
Total time for finalization = 9.6554E-04 seconds
Total time elapsed = 6.7713E+00 seconds
Calculation Rate (inactive) = 30594.8 neutrons/second
Calculation Rate (active) = 18080.8 neutrons/second
============================> RESULTS <============================
k-effective (Collision) = 1.15984 +/- 0.00411
k-effective (Track-length) = 1.16146 +/- 0.00457
k-effective (Absorption) = 1.16177 +/- 0.00380
Combined k-effective = 1.16105 +/- 0.00364
Leakage Fraction = 0.00000 +/- 0.00000
[14]:
0
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a StatePoint
object.
[15]:
# Load the last statepoint file
sp = openmc.StatePoint('statepoint.50.h5')
In addition to the statepoint file, our simulation also created a summary file which encapsulates information about the materials and geometry. By default, a Summary
object is automatically linked when a StatePoint
is loaded. This is necessary for the openmc.mgxs
module to properly process the tally data.
The statepoint is now ready to be analyzed by our multi-group cross sections. We simply have to load the tallies from the StatePoint
into each object as follows and our MGXS
objects will compute the cross sections for us under-the-hood.
[16]:
# Load the tallies from the statepoint into each MGXS object
total.load_from_statepoint(sp)
absorption.load_from_statepoint(sp)
scattering.load_from_statepoint(sp)
Voila! Our multi-group cross sections are now ready to rock ‘n roll!
Extracting and Storing MGXS Data¶
Let’s first inspect our total cross section by printing it to the screen.
[17]:
total.print_xs()
Multi-Group XS
Reaction Type = total
Domain Type = cell
Domain ID = 1
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 6.81e-01 +/- 2.69e-01%
Group 2 [0.0 - 0.625 eV]: 1.40e+00 +/- 5.93e-01%
Since the openmc.mgxs
module uses tally arithmetic under-the-hood, the cross section is stored as a “derived” Tally
object. This means that it can be queried and manipulated using all of the same methods supported for the Tally
class in the OpenMC Python API. For example, we can construct a Pandas DataFrame
of the multi-group cross section data.
[18]:
df = scattering.get_pandas_dataframe()
df.head(10)
[18]:
cell | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|
1 | 1 | 1 | total | 0.667787 | 0.001802 |
0 | 1 | 2 | total | 1.292013 | 0.007642 |
Each multi-group cross section object can be easily exported to a variety of file formats, including CSV, Excel, and LaTeX for storage or data processing.
[19]:
absorption.export_xs_data(filename='absorption-xs', format='excel')
The following code snippet shows how to export all three MGXS
to the same HDF5 binary data store.
[20]:
total.build_hdf5_store(filename='mgxs', append=True)
absorption.build_hdf5_store(filename='mgxs', append=True)
scattering.build_hdf5_store(filename='mgxs', append=True)
Comparing MGXS with Tally Arithmetic¶
Finally, we illustrate how one can leverage OpenMC’s tally arithmetic data processing feature with MGXS
objects. The openmc.mgxs
module uses tally arithmetic to compute multi-group cross sections with automated uncertainty propagation. Each MGXS
object includes an xs_tally
attribute which is a “derived” Tally
based on the tallies needed to compute the cross section type of interest. These derived tallies can be used in subsequent tally
arithmetic operations. For example, we can use tally artithmetic to confirm that the TotalXS
is equal to the sum of the AbsorptionXS
and ScatterXS
objects.
[21]:
# Use tally arithmetic to compute the difference between the total, absorption and scattering
difference = total.xs_tally - absorption.xs_tally - scattering.xs_tally
# The difference is a derived tally which can generate Pandas DataFrames for inspection
difference.get_pandas_dataframe()
[21]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | total | (((total / flux) - (absorption / flux)) - (sca... | -1.110223e-15 | 0.011292 |
1 | 1 | 0.625 | 2.000000e+07 | total | (((total / flux) - (absorption / flux)) - (sca... | 7.771561e-16 | 0.002570 |
Similarly, we can use tally arithmetic to compute the ratio of AbsorptionXS
and ScatterXS
to the TotalXS
.
[22]:
# Use tally arithmetic to compute the absorption-to-total MGXS ratio
absorption_to_total = absorption.xs_tally / total.xs_tally
# The absorption-to-total ratio is a derived tally which can generate Pandas DataFrames for inspection
absorption_to_total.get_pandas_dataframe()
[22]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | total | ((absorption / flux) / (total / flux)) | 0.076115 | 0.000649 |
1 | 1 | 0.625 | 2.000000e+07 | total | ((absorption / flux) / (total / flux)) | 0.019263 | 0.000095 |
[23]:
# Use tally arithmetic to compute the scattering-to-total MGXS ratio
scattering_to_total = scattering.xs_tally / total.xs_tally
# The scattering-to-total ratio is a derived tally which can generate Pandas DataFrames for inspection
scattering_to_total.get_pandas_dataframe()
[23]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | total | ((scatter / flux) / (total / flux)) | 0.923885 | 0.007736 |
1 | 1 | 0.625 | 2.000000e+07 | total | ((scatter / flux) / (total / flux)) | 0.980737 | 0.003737 |
Lastly, we sum the derived scatter-to-total and absorption-to-total ratios to confirm that they sum to unity.
[24]:
# Use tally arithmetic to ensure that the absorption- and scattering-to-total MGXS ratios sum to unity
sum_ratio = absorption_to_total + scattering_to_total
# The sum ratio is a derived tally which can generate Pandas DataFrames for inspection
sum_ratio.get_pandas_dataframe()
[24]:
cell | energy low [eV] | energy high [eV] | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|---|
0 | 1 | 0.000 | 6.250000e-01 | total | (((absorption / flux) / (total / flux)) + ((sc... | 1.0 | 0.007763 |
1 | 1 | 0.625 | 2.000000e+07 | total | (((absorption / flux) / (total / flux)) + ((sc... | 1.0 | 0.003739 |
Multigroup Cross Section Generation Part II: Advanced Features¶
This IPython Notebook illustrates the use of the openmc.mgxs
module to calculate multi-group cross sections for a heterogeneous fuel pin cell geometry. In particular, this Notebook illustrates the following features:
- Creation of multi-group cross sections on a heterogeneous geometry
- Calculation of cross sections on a nuclide-by-nuclide basis
- The use of `tally precision triggers <../io_formats/settings.rst#trigger-element>`__ with multi-group cross sections
- Built-in features for energy condensation in downstream data processing
- The use of the ``openmc.data`` module to plot continuous-energy vs. multi-group cross sections
- Validation of multi-group cross sections with `OpenMOC <https://mit-crpg.github.io/OpenMOC/>`__
Note: This Notebook was created using OpenMOC to verify the multi-group cross-sections generated by OpenMC. You must install OpenMOC on your system in order to run this Notebook in its entirety. In addition, this Notebook illustrates the use of Pandas DataFrames
to containerize multi-group cross section data.
Generate Input Files¶
[1]:
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-dark')
import openmoc
import openmc
import openmc.mgxs as mgxs
import openmc.data
from openmc.openmoc_compatible import get_openmoc_geometry
%matplotlib inline
First we need to define materials that will be used in the problem. We’ll create three distinct materials for water, clad and fuel.
[2]:
# 1.6% enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our materials, we can now create a Materials
object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials collection
materials_file = openmc.Materials([fuel, water, zircaloy])
# Export to "materials.xml"
materials_file.export_to_xml()
Now let’s move on to the geometry. Our problem will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces – in this case two cylinders and six reflective planes.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.45720)
# Create box to surround the geometry
box = openmc.model.rectangular_prism(1.26, 1.26, boundary_type='reflective')
With the surfaces defined, we can now create cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
pin_cell_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
pin_cell_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
pin_cell_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius & box
pin_cell_universe.add_cell(moderator_cell)
We now must create a geometry with the pin cell universe and export it to XML.
[6]:
# Create Geometry and set root Universe
openmc_geometry = openmc.Geometry(pin_cell_universe)
# Export to "geometry.xml"
openmc_geometry.export_to_xml()
Next, we must define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 10,000 particles.
[7]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 10000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': True}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -0.63, 0.63, 0.63, 0.63]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Activate tally precision triggers
settings_file.trigger_active = True
settings_file.trigger_max_batches = settings_file.batches * 4
# Export to "settings.xml"
settings_file.export_to_xml()
Now we are finally ready to make use of the openmc.mgxs
module to generate multi-group cross sections! First, let’s define “coarse” 2-group and “fine” 8-group structures using the built-in EnergyGroups
class.
[8]:
# Instantiate a "coarse" 2-group EnergyGroups object
coarse_groups = mgxs.EnergyGroups([0., 0.625, 20.0e6])
# Instantiate a "fine" 8-group EnergyGroups object
fine_groups = mgxs.EnergyGroups([0., 0.058, 0.14, 0.28,
0.625, 4.0, 5.53e3, 821.0e3, 20.0e6])
Now we will instantiate a variety of MGXS
objects needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we define transport, fission, nu-fission, nu-scatter and chi cross sections for each of the three cells in the fuel pin with the 8-group structure as our energy groups.
[9]:
# Extract all Cells filled by Materials
openmc_cells = openmc_geometry.get_all_material_cells().values()
# Create dictionary to store multi-group cross sections for all cells
xs_library = {}
# Instantiate 8-group cross sections for each cell
for cell in openmc_cells:
xs_library[cell.id] = {}
xs_library[cell.id]['transport'] = mgxs.TransportXS(groups=fine_groups)
xs_library[cell.id]['fission'] = mgxs.FissionXS(groups=fine_groups)
xs_library[cell.id]['nu-fission'] = mgxs.FissionXS(groups=fine_groups, nu=True)
xs_library[cell.id]['nu-scatter'] = mgxs.ScatterMatrixXS(groups=fine_groups, nu=True)
xs_library[cell.id]['chi'] = mgxs.Chi(groups=fine_groups)
Next, we showcase the use of OpenMC’s tally precision trigger feature in conjunction with the openmc.mgxs
module. In particular, we will assign a tally trigger of 1E-2 on the standard deviation for each of the tallies used to compute multi-group cross sections.
[10]:
# Create a tally trigger for +/- 0.01 on each tally used to compute the multi-group cross sections
tally_trigger = openmc.Trigger('std_dev', 1e-2)
# Add the tally trigger to each of the multi-group cross section tallies
for cell in openmc_cells:
for mgxs_type in xs_library[cell.id]:
xs_library[cell.id][mgxs_type].tally_trigger = tally_trigger
Now, we must loop over all cells to set the cross section domains to the various cells - fuel, clad and moderator - included in the geometry. In addition, we will set each cross section to tally cross sections on a per-nuclide basis through the use of the MGXS
class’ boolean by_nuclide
instance attribute.
[11]:
# Instantiate an empty Tallies object
tallies_file = openmc.Tallies()
# Iterate over all cells and cross section types
for cell in openmc_cells:
for rxn_type in xs_library[cell.id]:
# Set the cross sections domain to the cell
xs_library[cell.id][rxn_type].domain = cell
# Tally cross sections by nuclide
xs_library[cell.id][rxn_type].by_nuclide = True
# Add OpenMC tallies to the tallies file for XML generation
for tally in xs_library[cell.id][rxn_type].tallies.values():
tallies_file.append(tally, merge=True)
# Export to "tallies.xml"
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=53.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=4.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=41.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=15.
warn(msg, IDWarning)
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[12]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-19 07:08:16
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.20332
2/1 1.22209
3/1 1.24322
4/1 1.21622
5/1 1.25850
6/1 1.22581
7/1 1.21118
8/1 1.23377
9/1 1.24254
10/1 1.21241
11/1 1.21042
12/1 1.23539 1.22290 +/- 0.01249
13/1 1.22436 1.22339 +/- 0.00723
14/1 1.22888 1.22476 +/- 0.00529
15/1 1.22553 1.22491 +/- 0.00410
16/1 1.24194 1.22775 +/- 0.00439
17/1 1.24755 1.23058 +/- 0.00466
18/1 1.21117 1.22815 +/- 0.00471
19/1 1.22530 1.22784 +/- 0.00417
20/1 1.20762 1.22582 +/- 0.00424
21/1 1.20377 1.22381 +/- 0.00433
22/1 1.24305 1.22541 +/- 0.00426
23/1 1.22434 1.22533 +/- 0.00392
24/1 1.22937 1.22562 +/- 0.00364
25/1 1.22458 1.22555 +/- 0.00339
26/1 1.18978 1.22332 +/- 0.00388
27/1 1.20582 1.22229 +/- 0.00379
28/1 1.22719 1.22256 +/- 0.00358
29/1 1.21307 1.22206 +/- 0.00343
30/1 1.20915 1.22141 +/- 0.00331
31/1 1.22799 1.22173 +/- 0.00317
32/1 1.21251 1.22131 +/- 0.00305
33/1 1.20540 1.22062 +/- 0.00299
34/1 1.20052 1.21978 +/- 0.00299
35/1 1.24552 1.22081 +/- 0.00304
36/1 1.21685 1.22066 +/- 0.00293
37/1 1.22395 1.22078 +/- 0.00282
38/1 1.22379 1.22089 +/- 0.00272
39/1 1.20951 1.22049 +/- 0.00265
40/1 1.25199 1.22154 +/- 0.00277
41/1 1.23243 1.22190 +/- 0.00270
42/1 1.20973 1.22152 +/- 0.00264
43/1 1.24682 1.22228 +/- 0.00268
44/1 1.20694 1.22183 +/- 0.00263
45/1 1.22196 1.22183 +/- 0.00256
46/1 1.20687 1.22142 +/- 0.00252
47/1 1.22023 1.22139 +/- 0.00245
48/1 1.22204 1.22140 +/- 0.00239
49/1 1.22077 1.22139 +/- 0.00232
50/1 1.23166 1.22164 +/- 0.00228
Triggers unsatisfied, max unc./thresh. is 1.17623 for flux in tally 53
The estimated number of batches is 66
Creating state point statepoint.050.h5...
51/1 1.20071 1.22113 +/- 0.00228
Triggers unsatisfied, max unc./thresh. is 1.26577 for flux in tally 53
The estimated number of batches is 76
52/1 1.21423 1.22097 +/- 0.00223
Triggers unsatisfied, max unc./thresh. is 1.24 for flux in tally 53
The estimated number of batches is 75
53/1 1.25595 1.22178 +/- 0.00233
Triggers unsatisfied, max unc./thresh. is 1.2112 for flux in tally 53
The estimated number of batches is 74
54/1 1.21806 1.22170 +/- 0.00227
Triggers unsatisfied, max unc./thresh. is 1.18484 for flux in tally 53
The estimated number of batches is 72
55/1 1.22911 1.22186 +/- 0.00223
Triggers unsatisfied, max unc./thresh. is 1.1596 for flux in tally 53
The estimated number of batches is 71
56/1 1.23054 1.22205 +/- 0.00219
Triggers unsatisfied, max unc./thresh. is 1.13453 for flux in tally 53
The estimated number of batches is 70
57/1 1.19384 1.22145 +/- 0.00222
Triggers unsatisfied, max unc./thresh. is 1.11914 for flux in tally 53
The estimated number of batches is 69
58/1 1.20625 1.22114 +/- 0.00220
Triggers unsatisfied, max unc./thresh. is 1.11471 for flux in tally 53
The estimated number of batches is 70
59/1 1.21977 1.22111 +/- 0.00216
Triggers unsatisfied, max unc./thresh. is 1.10334 for flux in tally 53
The estimated number of batches is 70
60/1 1.20813 1.22085 +/- 0.00213
Triggers unsatisfied, max unc./thresh. is 1.09813 for flux in tally 53
The estimated number of batches is 71
61/1 1.22077 1.22085 +/- 0.00209
Triggers unsatisfied, max unc./thresh. is 1.10221 for flux in tally 53
The estimated number of batches is 72
62/1 1.21956 1.22082 +/- 0.00205
Triggers unsatisfied, max unc./thresh. is 1.11395 for flux in tally 53
The estimated number of batches is 75
63/1 1.22360 1.22087 +/- 0.00201
Triggers unsatisfied, max unc./thresh. is 1.09283 for flux in tally 53
The estimated number of batches is 74
64/1 1.23955 1.22122 +/- 0.00200
Triggers unsatisfied, max unc./thresh. is 1.07416 for flux in tally 53
The estimated number of batches is 73
65/1 1.21143 1.22104 +/- 0.00197
Triggers unsatisfied, max unc./thresh. is 1.06461 for flux in tally 53
The estimated number of batches is 73
66/1 1.21791 1.22099 +/- 0.00194
Triggers unsatisfied, max unc./thresh. is 1.13207 for flux in tally 53
The estimated number of batches is 82
67/1 1.24897 1.22148 +/- 0.00196
Triggers unsatisfied, max unc./thresh. is 1.11277 for flux in tally 53
The estimated number of batches is 81
68/1 1.22221 1.22149 +/- 0.00193
Triggers unsatisfied, max unc./thresh. is 1.09514 for flux in tally 53
The estimated number of batches is 80
69/1 1.25627 1.22208 +/- 0.00199
Triggers unsatisfied, max unc./thresh. is 1.07653 for flux in tally 53
The estimated number of batches is 79
70/1 1.21493 1.22196 +/- 0.00196
Triggers unsatisfied, max unc./thresh. is 1.12831 for flux in tally 53
The estimated number of batches is 87
71/1 1.23406 1.22216 +/- 0.00193
Triggers unsatisfied, max unc./thresh. is 1.11005 for flux in tally 53
The estimated number of batches is 86
72/1 1.23842 1.22242 +/- 0.00192
Triggers unsatisfied, max unc./thresh. is 1.09352 for flux in tally 53
The estimated number of batches is 85
73/1 1.24542 1.22279 +/- 0.00193
Triggers unsatisfied, max unc./thresh. is 1.08766 for flux in tally 53
The estimated number of batches is 85
74/1 1.21314 1.22263 +/- 0.00190
Triggers unsatisfied, max unc./thresh. is 1.07419 for flux in tally 53
The estimated number of batches is 84
75/1 1.26484 1.22328 +/- 0.00198
Triggers unsatisfied, max unc./thresh. is 1.06788 for flux in tally 53
The estimated number of batches is 85
76/1 1.22243 1.22327 +/- 0.00195
Triggers unsatisfied, max unc./thresh. is 1.05164 for flux in tally 53
The estimated number of batches is 83
77/1 1.21865 1.22320 +/- 0.00192
Triggers unsatisfied, max unc./thresh. is 1.04022 for flux in tally 53
The estimated number of batches is 83
78/1 1.23500 1.22338 +/- 0.00190
Triggers unsatisfied, max unc./thresh. is 1.0275 for flux in tally 53
The estimated number of batches is 82
79/1 1.22125 1.22334 +/- 0.00187
Triggers unsatisfied, max unc./thresh. is 1.0283 for flux in tally 53
The estimated number of batches is 83
80/1 1.23793 1.22355 +/- 0.00186
Triggers unsatisfied, max unc./thresh. is 1.01363 for flux in tally 53
The estimated number of batches is 82
81/1 1.24238 1.22382 +/- 0.00185
Triggers unsatisfied, max unc./thresh. is 1.01172 for flux in tally 53
The estimated number of batches is 83
82/1 1.23493 1.22397 +/- 0.00183
Triggers satisfied for batch 82
Creating state point statepoint.082.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 9.5644e-01 seconds
Reading cross sections = 9.0579e-01 seconds
Total time in simulation = 9.9887e+01 seconds
Time in transport only = 9.9333e+01 seconds
Time in inactive batches = 5.4841e+00 seconds
Time in active batches = 9.4403e+01 seconds
Time synchronizing fission bank = 7.3998e-02 seconds
Sampling source sites = 5.9021e-02 seconds
SEND/RECV source sites = 1.4787e-02 seconds
Time accumulating tallies = 1.2234e-03 seconds
Total time for finalization = 2.8416e-02 seconds
Total time elapsed = 1.0094e+02 seconds
Calculation Rate (inactive) = 18234.5 particles/second
Calculation Rate (active) = 7626.89 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.22348 +/- 0.00169
k-effective (Track-length) = 1.22397 +/- 0.00183
k-effective (Absorption) = 1.22467 +/- 0.00117
Combined k-effective = 1.22448 +/- 0.00108
Leakage Fraction = 0.00000 +/- 0.00000
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a StatePoint
object.
[13]:
# Load the last statepoint file
sp = openmc.StatePoint('statepoint.082.h5')
The statepoint is now ready to be analyzed by our multi-group cross sections. We simply have to load the tallies from the StatePoint
into each object as follows and our MGXS
objects will compute the cross sections for us under-the-hood.
[14]:
# Iterate over all cells and cross section types
for cell in openmc_cells:
for rxn_type in xs_library[cell.id]:
xs_library[cell.id][rxn_type].load_from_statepoint(sp)
That’s it! Our multi-group cross sections are now ready for the big spotlight. This time we have cross sections in three distinct spatial zones - fuel, clad and moderator - on a per-nuclide basis.
Extracting and Storing MGXS Data¶
Let’s first inspect one of our cross sections by printing it to the screen as a microscopic cross section in units of barns.
[15]:
nufission = xs_library[fuel_cell.id]['nu-fission']
nufission.print_xs(xs_type='micro', nuclides=['U235', 'U238'])
Multi-Group XS
Reaction Type = nu-fission
Domain Type = cell
Domain ID = 1
Nuclide = U235
Cross Sections [barns]:
Group 1 [821000.0 - 20000000.0eV]: 3.30e+00 +/- 2.14e-01%
Group 2 [5530.0 - 821000.0 eV]: 3.96e+00 +/- 1.33e-01%
Group 3 [4.0 - 5530.0 eV]: 5.50e+01 +/- 2.29e-01%
Group 4 [0.625 - 4.0 eV]: 8.85e+01 +/- 3.10e-01%
Group 5 [0.28 - 0.625 eV]: 2.90e+02 +/- 3.94e-01%
Group 6 [0.14 - 0.28 eV]: 4.49e+02 +/- 4.12e-01%
Group 7 [0.058 - 0.14 eV]: 6.87e+02 +/- 3.01e-01%
Group 8 [0.0 - 0.058 eV]: 1.44e+03 +/- 2.79e-01%
Nuclide = U238
Cross Sections [barns]:
Group 1 [821000.0 - 20000000.0eV]: 1.06e+00 +/- 2.53e-01%
Group 2 [5530.0 - 821000.0 eV]: 1.21e-03 +/- 2.60e-01%
Group 3 [4.0 - 5530.0 eV]: 5.73e-04 +/- 2.93e+00%
Group 4 [0.625 - 4.0 eV]: 6.54e-06 +/- 2.72e-01%
Group 5 [0.28 - 0.625 eV]: 1.07e-05 +/- 3.83e-01%
Group 6 [0.14 - 0.28 eV]: 1.55e-05 +/- 4.13e-01%
Group 7 [0.058 - 0.14 eV]: 2.30e-05 +/- 3.01e-01%
Group 8 [0.0 - 0.058 eV]: 4.24e-05 +/- 2.79e-01%
Our multi-group cross sections are capable of summing across all nuclides to provide us with macroscopic cross sections as well.
[16]:
nufission = xs_library[fuel_cell.id]['nu-fission']
nufission.print_xs(xs_type='macro', nuclides='sum')
Multi-Group XS
Reaction Type = nu-fission
Domain Type = cell
Domain ID = 1
Cross Sections [cm^-1]:
Group 1 [821000.0 - 20000000.0eV]: 2.52e-02 +/- 2.41e-01%
Group 2 [5530.0 - 821000.0 eV]: 1.51e-03 +/- 1.31e-01%
Group 3 [4.0 - 5530.0 eV]: 2.07e-02 +/- 2.29e-01%
Group 4 [0.625 - 4.0 eV]: 3.32e-02 +/- 3.10e-01%
Group 5 [0.28 - 0.625 eV]: 1.09e-01 +/- 3.94e-01%
Group 6 [0.14 - 0.28 eV]: 1.69e-01 +/- 4.12e-01%
Group 7 [0.058 - 0.14 eV]: 2.58e-01 +/- 3.01e-01%
Group 8 [0.0 - 0.058 eV]: 5.40e-01 +/- 2.79e-01%
Although a printed report is nice, it is not scalable or flexible. Let’s extract the microscopic cross section data for the moderator as a Pandas DataFrame
.
[17]:
nuscatter = xs_library[moderator_cell.id]['nu-scatter']
df = nuscatter.get_pandas_dataframe(xs_type='micro')
df.head(10)
[17]:
cell | group in | group out | nuclide | mean | std. dev. | |
---|---|---|---|---|---|---|
126 | 3 | 1 | 1 | H1 | 0.233991 | 0.003752 |
127 | 3 | 1 | 1 | O16 | 1.569288 | 0.006360 |
124 | 3 | 1 | 2 | H1 | 1.587279 | 0.003098 |
125 | 3 | 1 | 2 | O16 | 0.285599 | 0.001422 |
122 | 3 | 1 | 3 | H1 | 0.010482 | 0.000220 |
123 | 3 | 1 | 3 | O16 | 0.000000 | 0.000000 |
120 | 3 | 1 | 4 | H1 | 0.000009 | 0.000006 |
121 | 3 | 1 | 4 | O16 | 0.000000 | 0.000000 |
118 | 3 | 1 | 5 | H1 | 0.000005 | 0.000005 |
119 | 3 | 1 | 5 | O16 | 0.000000 | 0.000000 |
Next, we illustate how one can easily take multi-group cross sections and condense them down to a coarser energy group structure. The MGXS
class includes a get_condensed_xs(...)
method which takes an EnergyGroups
parameter with a coarse(r) group structure and returns a new MGXS
condensed to the coarse groups. We illustrate this process below using the 2-group structure created earlier.
[18]:
# Extract the 8-group transport cross section for the fuel
fine_xs = xs_library[fuel_cell.id]['transport']
# Condense to the 2-group structure
condensed_xs = fine_xs.get_condensed_xs(coarse_groups)
Group condensation is as simple as that! We now have a new coarse 2-group TransportXS
in addition to our original 8-group TransportXS
. Let’s inspect the 2-group TransportXS
by printing it to the screen and extracting a Pandas DataFrame
as we have already learned how to do.
[19]:
condensed_xs.print_xs()
Multi-Group XS
Reaction Type = transport
Domain Type = cell
Domain ID = 1
Nuclide = U235
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 7.79e-03 +/- 2.12e-01%
Group 2 [0.0 - 0.625 eV]: 1.82e-01 +/- 1.92e-01%
Nuclide = U238
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 2.17e-01 +/- 1.12e-01%
Group 2 [0.0 - 0.625 eV]: 2.53e-01 +/- 1.89e-01%
Nuclide = O16
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 1.45e-01 +/- 1.12e-01%
Group 2 [0.0 - 0.625 eV]: 1.74e-01 +/- 2.03e-01%
[20]:
df = condensed_xs.get_pandas_dataframe(xs_type='micro')
df
[20]:
cell | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|
3 | 1 | 1 | U235 | 20.763062 | 0.044093 |
4 | 1 | 1 | U238 | 9.579086 | 0.010757 |
5 | 1 | 1 | O16 | 3.157274 | 0.003531 |
0 | 1 | 2 | U235 | 485.349036 | 0.930937 |
1 | 1 | 2 | U238 | 11.199167 | 0.021167 |
2 | 1 | 2 | O16 | 3.788383 | 0.007676 |
Verification with OpenMOC¶
Now, let’s verify our cross sections using OpenMOC. First, we construct an equivalent OpenMOC geometry.
[21]:
# Create an OpenMOC Geometry from the OpenMC Geometry
openmoc_geometry = get_openmoc_geometry(sp.summary.geometry)
Next, we we can inject the multi-group cross sections into the equivalent fuel pin cell OpenMOC geometry.
[22]:
# Get all OpenMOC cells in the gometry
openmoc_cells = openmoc_geometry.getRootUniverse().getAllCells()
# Inject multi-group cross sections into OpenMOC Materials
for cell_id, cell in openmoc_cells.items():
# Ignore the root cell
if cell.getName() == 'root cell':
continue
# Get a reference to the Material filling this Cell
openmoc_material = cell.getFillMaterial()
# Set the number of energy groups for the Material
openmoc_material.setNumEnergyGroups(fine_groups.num_groups)
# Extract the appropriate cross section objects for this cell
transport = xs_library[cell_id]['transport']
nufission = xs_library[cell_id]['nu-fission']
nuscatter = xs_library[cell_id]['nu-scatter']
chi = xs_library[cell_id]['chi']
# Inject NumPy arrays of cross section data into the Material
# NOTE: Sum across nuclides to get macro cross sections needed by OpenMOC
openmoc_material.setSigmaT(transport.get_xs(nuclides='sum').flatten())
openmoc_material.setNuSigmaF(nufission.get_xs(nuclides='sum').flatten())
openmoc_material.setSigmaS(nuscatter.get_xs(nuclides='sum').flatten())
openmoc_material.setChi(chi.get_xs(nuclides='sum').flatten())
We are now ready to run OpenMOC to verify our cross-sections from OpenMC.
[23]:
# Generate tracks for OpenMOC
track_generator = openmoc.TrackGenerator(openmoc_geometry, num_azim=128, azim_spacing=0.1)
track_generator.generateTracks()
# Run OpenMOC
solver = openmoc.CPUSolver(track_generator)
solver.computeEigenvalue()
[ NORMAL ] Initializing a default angular quadrature...
[ NORMAL ] Initializing 2D tracks...
[ NORMAL ] Initializing 2D tracks reflections...
[ NORMAL ] Initializing 2D tracks array...
[ NORMAL ] Ray tracing for 2D track segmentation...
[ NORMAL ] Progress Segmenting 2D tracks: 0.09 %
[ NORMAL ] Progress Segmenting 2D tracks: 10.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 19.94 %
[ NORMAL ] Progress Segmenting 2D tracks: 29.87 %
[ NORMAL ] Progress Segmenting 2D tracks: 39.80 %
[ NORMAL ] Progress Segmenting 2D tracks: 49.72 %
[ NORMAL ] Progress Segmenting 2D tracks: 59.65 %
[ NORMAL ] Progress Segmenting 2D tracks: 69.58 %
[ NORMAL ] Progress Segmenting 2D tracks: 79.50 %
[ NORMAL ] Progress Segmenting 2D tracks: 89.43 %
[ NORMAL ] Progress Segmenting 2D tracks: 100.00 %
[ NORMAL ] Initializing FSR lookup vectors
[ NORMAL ] Total number of FSRs 3
[ RESULT ] Total Track Generation & Segmentation Time...........2.5566E-02 sec
[ NORMAL ] Initializing MOC eigenvalue solver...
[ NORMAL ] Initializing solver arrays...
[ NORMAL ] Centering segments around FSR centroid...
[ NORMAL ] Max boundary angular flux storage per domain = 0.42 MB
[ NORMAL ] Max scalar flux storage per domain = 0.00 MB
[ NORMAL ] Max source storage per domain = 0.00 MB
[ NORMAL ] Number of azimuthal angles = 128
[ NORMAL ] Azimuthal ray spacing = 0.100000
[ NORMAL ] Number of polar angles = 6
[ NORMAL ] Source type = Flat
[ NORMAL ] MOC transport undamped
[ NORMAL ] CMFD acceleration: OFF
[ NORMAL ] Using 1 threads
[ NORMAL ] Computing the eigenvalue...
[ NORMAL ] Iteration 0: k_eff = 0.423133 res = 5.671E-09 delta-k (pcm) =
[ NORMAL ] ... -57686 D.R. = 0.00
[ NORMAL ] Iteration 1: k_eff = 0.475953 res = 2.442E-08 delta-k (pcm) =
[ NORMAL ] ... 5282 D.R. = 4.31
[ NORMAL ] Iteration 2: k_eff = 0.491468 res = 4.764E-08 delta-k (pcm) =
[ NORMAL ] ... 1551 D.R. = 1.95
[ NORMAL ] Iteration 3: k_eff = 0.487446 res = 2.253E-08 delta-k (pcm) =
[ NORMAL ] ... -402 D.R. = 0.47
[ NORMAL ] Iteration 4: k_eff = 0.483930 res = 6.957E-09 delta-k (pcm) =
[ NORMAL ] ... -351 D.R. = 0.31
[ NORMAL ] Iteration 5: k_eff = 0.477280 res = 3.902E-08 delta-k (pcm) =
[ NORMAL ] ... -665 D.R. = 5.61
[ NORMAL ] Iteration 6: k_eff = 0.468938 res = 3.161E-08 delta-k (pcm) =
[ NORMAL ] ... -834 D.R. = 0.81
[ NORMAL ] Iteration 7: k_eff = 0.460319 res = 2.480E-08 delta-k (pcm) =
[ NORMAL ] ... -861 D.R. = 0.78
[ NORMAL ] Iteration 8: k_eff = 0.450591 res = 9.377E-09 delta-k (pcm) =
[ NORMAL ] ... -972 D.R. = 0.38
[ NORMAL ] Iteration 9: k_eff = 0.441377 res = 3.085E-08 delta-k (pcm) =
[ NORMAL ] ... -921 D.R. = 3.29
[ NORMAL ] Iteration 10: k_eff = 0.431990 res = 1.028E-08 delta-k (pcm) =
[ NORMAL ] ... -938 D.R. = 0.33
[ NORMAL ] Iteration 11: k_eff = 0.422932 res = 1.180E-08 delta-k (pcm) =
[ NORMAL ] ... -905 D.R. = 1.15
[ NORMAL ] Iteration 12: k_eff = 0.414487 res = 1.633E-08 delta-k (pcm) =
[ NORMAL ] ... -844 D.R. = 1.38
[ NORMAL ] Iteration 13: k_eff = 0.406708 res = 1.754E-08 delta-k (pcm) =
[ NORMAL ] ... -777 D.R. = 1.07
[ NORMAL ] Iteration 14: k_eff = 0.399378 res = 5.021E-08 delta-k (pcm) =
[ NORMAL ] ... -732 D.R. = 2.86
[ NORMAL ] Iteration 15: k_eff = 0.393067 res = 9.074E-09 delta-k (pcm) =
[ NORMAL ] ... -631 D.R. = 0.18
[ NORMAL ] Iteration 16: k_eff = 0.387427 res = 4.840E-09 delta-k (pcm) =
[ NORMAL ] ... -564 D.R. = 0.53
[ NORMAL ] Iteration 17: k_eff = 0.382668 res = 2.299E-08 delta-k (pcm) =
[ NORMAL ] ... -475 D.R. = 4.75
[ NORMAL ] Iteration 18: k_eff = 0.378741 res = 1.573E-08 delta-k (pcm) =
[ NORMAL ] ... -392 D.R. = 0.68
[ NORMAL ] Iteration 19: k_eff = 0.375642 res = 7.017E-08 delta-k (pcm) =
[ NORMAL ] ... -309 D.R. = 4.46
[ NORMAL ] Iteration 20: k_eff = 0.373489 res = 4.053E-08 delta-k (pcm) =
[ NORMAL ] ... -215 D.R. = 0.58
[ NORMAL ] Iteration 21: k_eff = 0.372357 res = 4.235E-08 delta-k (pcm) =
[ NORMAL ] ... -113 D.R. = 1.04
[ NORMAL ] Iteration 22: k_eff = 0.371974 res = 6.352E-08 delta-k (pcm) =
[ NORMAL ] ... -38 D.R. = 1.50
[ NORMAL ] Iteration 23: k_eff = 0.372581 res = 3.267E-08 delta-k (pcm) =
[ NORMAL ] ... 60 D.R. = 0.51
[ NORMAL ] Iteration 24: k_eff = 0.374056 res = 1.573E-08 delta-k (pcm) =
[ NORMAL ] ... 147 D.R. = 0.48
[ NORMAL ] Iteration 25: k_eff = 0.376384 res = 3.630E-08 delta-k (pcm) =
[ NORMAL ] ... 232 D.R. = 2.31
[ NORMAL ] Iteration 26: k_eff = 0.379563 res = 2.420E-09 delta-k (pcm) =
[ NORMAL ] ... 317 D.R. = 0.07
[ NORMAL ] Iteration 27: k_eff = 0.383583 res = 3.146E-08 delta-k (pcm) =
[ NORMAL ] ... 401 D.R. = 13.00
[ NORMAL ] Iteration 28: k_eff = 0.388380 res = 1.089E-08 delta-k (pcm) =
[ NORMAL ] ... 479 D.R. = 0.35
[ NORMAL ] Iteration 29: k_eff = 0.393938 res = 6.049E-08 delta-k (pcm) =
[ NORMAL ] ... 555 D.R. = 5.56
[ NORMAL ] Iteration 30: k_eff = 0.400234 res = 3.267E-08 delta-k (pcm) =
[ NORMAL ] ... 629 D.R. = 0.54
[ NORMAL ] Iteration 31: k_eff = 0.407235 res = 2.420E-08 delta-k (pcm) =
[ NORMAL ] ... 700 D.R. = 0.74
[ NORMAL ] Iteration 32: k_eff = 0.414884 res = 1.815E-08 delta-k (pcm) =
[ NORMAL ] ... 764 D.R. = 0.75
[ NORMAL ] Iteration 33: k_eff = 0.423172 res = 7.259E-09 delta-k (pcm) =
[ NORMAL ] ... 828 D.R. = 0.40
[ NORMAL ] Iteration 34: k_eff = 0.432051 res = 6.049E-08 delta-k (pcm) =
[ NORMAL ] ... 887 D.R. = 8.33
[ NORMAL ] Iteration 35: k_eff = 0.441471 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 942 D.R. = 0.96
[ NORMAL ] Iteration 36: k_eff = 0.451430 res = 2.662E-08 delta-k (pcm) =
[ NORMAL ] ... 995 D.R. = 0.46
[ NORMAL ] Iteration 37: k_eff = 0.461853 res = 8.227E-08 delta-k (pcm) =
[ NORMAL ] ... 1042 D.R. = 3.09
[ NORMAL ] Iteration 38: k_eff = 0.472730 res = 5.928E-08 delta-k (pcm) =
[ NORMAL ] ... 1087 D.R. = 0.72
[ NORMAL ] Iteration 39: k_eff = 0.484006 res = 2.299E-08 delta-k (pcm) =
[ NORMAL ] ... 1127 D.R. = 0.39
[ NORMAL ] Iteration 40: k_eff = 0.495653 res = 2.299E-08 delta-k (pcm) =
[ NORMAL ] ... 1164 D.R. = 1.00
[ NORMAL ] Iteration 41: k_eff = 0.507634 res = 7.017E-08 delta-k (pcm) =
[ NORMAL ] ... 1198 D.R. = 3.05
[ NORMAL ] Iteration 42: k_eff = 0.519914 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 1227 D.R. = 0.28
[ NORMAL ] Iteration 43: k_eff = 0.532458 res = 1.089E-08 delta-k (pcm) =
[ NORMAL ] ... 1254 D.R. = 0.56
[ NORMAL ] Iteration 44: k_eff = 0.545234 res = 6.291E-08 delta-k (pcm) =
[ NORMAL ] ... 1277 D.R. = 5.78
[ NORMAL ] Iteration 45: k_eff = 0.558210 res = 3.509E-08 delta-k (pcm) =
[ NORMAL ] ... 1297 D.R. = 0.56
[ NORMAL ] Iteration 46: k_eff = 0.571353 res = 3.025E-08 delta-k (pcm) =
[ NORMAL ] ... 1314 D.R. = 0.86
[ NORMAL ] Iteration 47: k_eff = 0.584635 res = 7.259E-09 delta-k (pcm) =
[ NORMAL ] ... 1328 D.R. = 0.24
[ NORMAL ] Iteration 48: k_eff = 0.598027 res = 4.961E-08 delta-k (pcm) =
[ NORMAL ] ... 1339 D.R. = 6.83
[ NORMAL ] Iteration 49: k_eff = 0.611500 res = 9.014E-08 delta-k (pcm) =
[ NORMAL ] ... 1347 D.R. = 1.82
[ NORMAL ] Iteration 50: k_eff = 0.625029 res = 5.203E-08 delta-k (pcm) =
[ NORMAL ] ... 1352 D.R. = 0.58
[ NORMAL ] Iteration 51: k_eff = 0.638590 res = 1.512E-08 delta-k (pcm) =
[ NORMAL ] ... 1356 D.R. = 0.29
[ NORMAL ] Iteration 52: k_eff = 0.652158 res = 2.359E-08 delta-k (pcm) =
[ NORMAL ] ... 1356 D.R. = 1.56
[ NORMAL ] Iteration 53: k_eff = 0.665710 res = 4.598E-08 delta-k (pcm) =
[ NORMAL ] ... 1355 D.R. = 1.95
[ NORMAL ] Iteration 54: k_eff = 0.679228 res = 2.783E-08 delta-k (pcm) =
[ NORMAL ] ... 1351 D.R. = 0.61
[ NORMAL ] Iteration 55: k_eff = 0.692689 res = 2.117E-08 delta-k (pcm) =
[ NORMAL ] ... 1346 D.R. = 0.76
[ NORMAL ] Iteration 56: k_eff = 0.706075 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 1338 D.R. = 0.91
[ NORMAL ] Iteration 57: k_eff = 0.719370 res = 5.505E-08 delta-k (pcm) =
[ NORMAL ] ... 1329 D.R. = 2.84
[ NORMAL ] Iteration 58: k_eff = 0.732556 res = 2.238E-08 delta-k (pcm) =
[ NORMAL ] ... 1318 D.R. = 0.41
[ NORMAL ] Iteration 59: k_eff = 0.745619 res = 7.864E-09 delta-k (pcm) =
[ NORMAL ] ... 1306 D.R. = 0.35
[ NORMAL ] Iteration 60: k_eff = 0.758546 res = 4.477E-08 delta-k (pcm) =
[ NORMAL ] ... 1292 D.R. = 5.69
[ NORMAL ] Iteration 61: k_eff = 0.771323 res = 4.658E-08 delta-k (pcm) =
[ NORMAL ] ... 1277 D.R. = 1.04
[ NORMAL ] Iteration 62: k_eff = 0.783939 res = 9.679E-09 delta-k (pcm) =
[ NORMAL ] ... 1261 D.R. = 0.21
[ NORMAL ] Iteration 63: k_eff = 0.796383 res = 4.235E-09 delta-k (pcm) =
[ NORMAL ] ... 1244 D.R. = 0.44
[ NORMAL ] Iteration 64: k_eff = 0.808646 res = 9.074E-09 delta-k (pcm) =
[ NORMAL ] ... 1226 D.R. = 2.14
[ NORMAL ] Iteration 65: k_eff = 0.820719 res = 6.412E-08 delta-k (pcm) =
[ NORMAL ] ... 1207 D.R. = 7.07
[ NORMAL ] Iteration 66: k_eff = 0.832594 res = 2.420E-09 delta-k (pcm) =
[ NORMAL ] ... 1187 D.R. = 0.04
[ NORMAL ] Iteration 67: k_eff = 0.844264 res = 2.299E-08 delta-k (pcm) =
[ NORMAL ] ... 1167 D.R. = 9.50
[ NORMAL ] Iteration 68: k_eff = 0.855724 res = 5.928E-08 delta-k (pcm) =
[ NORMAL ] ... 1146 D.R. = 2.58
[ NORMAL ] Iteration 69: k_eff = 0.866968 res = 6.049E-08 delta-k (pcm) =
[ NORMAL ] ... 1124 D.R. = 1.02
[ NORMAL ] Iteration 70: k_eff = 0.877992 res = 2.722E-08 delta-k (pcm) =
[ NORMAL ] ... 1102 D.R. = 0.45
[ NORMAL ] Iteration 71: k_eff = 0.888792 res = 1.633E-08 delta-k (pcm) =
[ NORMAL ] ... 1079 D.R. = 0.60
[ NORMAL ] Iteration 72: k_eff = 0.899364 res = 2.117E-08 delta-k (pcm) =
[ NORMAL ] ... 1057 D.R. = 1.30
[ NORMAL ] Iteration 73: k_eff = 0.909708 res = 3.327E-08 delta-k (pcm) =
[ NORMAL ] ... 1034 D.R. = 1.57
[ NORMAL ] Iteration 74: k_eff = 0.919819 res = 4.477E-08 delta-k (pcm) =
[ NORMAL ] ... 1011 D.R. = 1.35
[ NORMAL ] Iteration 75: k_eff = 0.929699 res = 3.569E-08 delta-k (pcm) =
[ NORMAL ] ... 987 D.R. = 0.80
[ NORMAL ] Iteration 76: k_eff = 0.939346 res = 4.840E-09 delta-k (pcm) =
[ NORMAL ] ... 964 D.R. = 0.14
[ NORMAL ] Iteration 77: k_eff = 0.948758 res = 4.840E-09 delta-k (pcm) =
[ NORMAL ] ... 941 D.R. = 1.00
[ NORMAL ] Iteration 78: k_eff = 0.957938 res = 6.049E-10 delta-k (pcm) =
[ NORMAL ] ... 917 D.R. = 0.12
[ NORMAL ] Iteration 79: k_eff = 0.966885 res = 2.057E-08 delta-k (pcm) =
[ NORMAL ] ... 894 D.R. = 34.00
[ NORMAL ] Iteration 80: k_eff = 0.975601 res = 4.235E-08 delta-k (pcm) =
[ NORMAL ] ... 871 D.R. = 2.06
[ NORMAL ] Iteration 81: k_eff = 0.984087 res = 5.868E-08 delta-k (pcm) =
[ NORMAL ] ... 848 D.R. = 1.39
[ NORMAL ] Iteration 82: k_eff = 0.992344 res = 7.259E-09 delta-k (pcm) =
[ NORMAL ] ... 825 D.R. = 0.12
[ NORMAL ] Iteration 83: k_eff = 1.000375 res = 1.512E-08 delta-k (pcm) =
[ NORMAL ] ... 803 D.R. = 2.08
[ NORMAL ] Iteration 84: k_eff = 1.008182 res = 7.864E-09 delta-k (pcm) =
[ NORMAL ] ... 780 D.R. = 0.52
[ NORMAL ] Iteration 85: k_eff = 1.015768 res = 7.259E-09 delta-k (pcm) =
[ NORMAL ] ... 758 D.R. = 0.92
[ NORMAL ] Iteration 86: k_eff = 1.023136 res = 3.025E-09 delta-k (pcm) =
[ NORMAL ] ... 736 D.R. = 0.42
[ NORMAL ] Iteration 87: k_eff = 1.030288 res = 1.210E-08 delta-k (pcm) =
[ NORMAL ] ... 715 D.R. = 4.00
[ NORMAL ] Iteration 88: k_eff = 1.037228 res = 3.690E-08 delta-k (pcm) =
[ NORMAL ] ... 693 D.R. = 3.05
[ NORMAL ] Iteration 89: k_eff = 1.043960 res = 5.203E-08 delta-k (pcm) =
[ NORMAL ] ... 673 D.R. = 1.41
[ NORMAL ] Iteration 90: k_eff = 1.050486 res = 6.231E-08 delta-k (pcm) =
[ NORMAL ] ... 652 D.R. = 1.20
[ NORMAL ] Iteration 91: k_eff = 1.056812 res = 6.775E-08 delta-k (pcm) =
[ NORMAL ] ... 632 D.R. = 1.09
[ NORMAL ] Iteration 92: k_eff = 1.062939 res = 2.964E-08 delta-k (pcm) =
[ NORMAL ] ... 612 D.R. = 0.44
[ NORMAL ] Iteration 93: k_eff = 1.068872 res = 5.505E-08 delta-k (pcm) =
[ NORMAL ] ... 593 D.R. = 1.86
[ NORMAL ] Iteration 94: k_eff = 1.074616 res = 4.235E-09 delta-k (pcm) =
[ NORMAL ] ... 574 D.R. = 0.08
[ NORMAL ] Iteration 95: k_eff = 1.080173 res = 2.541E-08 delta-k (pcm) =
[ NORMAL ] ... 555 D.R. = 6.00
[ NORMAL ] Iteration 96: k_eff = 1.085550 res = 1.996E-08 delta-k (pcm) =
[ NORMAL ] ... 537 D.R. = 0.79
[ NORMAL ] Iteration 97: k_eff = 1.090748 res = 3.388E-08 delta-k (pcm) =
[ NORMAL ] ... 519 D.R. = 1.70
[ NORMAL ] Iteration 98: k_eff = 1.095774 res = 3.085E-08 delta-k (pcm) =
[ NORMAL ] ... 502 D.R. = 0.91
[ NORMAL ] Iteration 99: k_eff = 1.100629 res = 3.267E-08 delta-k (pcm) =
[ NORMAL ] ... 485 D.R. = 1.06
[ NORMAL ] Iteration 100: k_eff = 1.105320 res = 3.025E-09 delta-k (pcm)
[ NORMAL ] ... = 469 D.R. = 0.09
[ NORMAL ] Iteration 101: k_eff = 1.109851 res = 6.654E-09 delta-k (pcm)
[ NORMAL ] ... = 453 D.R. = 2.20
[ NORMAL ] Iteration 102: k_eff = 1.114224 res = 3.569E-08 delta-k (pcm)
[ NORMAL ] ... = 437 D.R. = 5.36
[ NORMAL ] Iteration 103: k_eff = 1.118444 res = 5.203E-08 delta-k (pcm)
[ NORMAL ] ... = 421 D.R. = 1.46
[ NORMAL ] Iteration 104: k_eff = 1.122516 res = 1.452E-08 delta-k (pcm)
[ NORMAL ] ... = 407 D.R. = 0.28
[ NORMAL ] Iteration 105: k_eff = 1.126445 res = 5.445E-09 delta-k (pcm)
[ NORMAL ] ... = 392 D.R. = 0.38
[ NORMAL ] Iteration 106: k_eff = 1.130232 res = 2.783E-08 delta-k (pcm)
[ NORMAL ] ... = 378 D.R. = 5.11
[ NORMAL ] Iteration 107: k_eff = 1.133884 res = 1.996E-08 delta-k (pcm)
[ NORMAL ] ... = 365 D.R. = 0.72
[ NORMAL ] Iteration 108: k_eff = 1.137403 res = 4.235E-08 delta-k (pcm)
[ NORMAL ] ... = 351 D.R. = 2.12
[ NORMAL ] Iteration 109: k_eff = 1.140793 res = 3.751E-08 delta-k (pcm)
[ NORMAL ] ... = 339 D.R. = 0.89
[ NORMAL ] Iteration 110: k_eff = 1.144059 res = 4.174E-08 delta-k (pcm)
[ NORMAL ] ... = 326 D.R. = 1.11
[ NORMAL ] Iteration 111: k_eff = 1.147203 res = 4.416E-08 delta-k (pcm)
[ NORMAL ] ... = 314 D.R. = 1.06
[ NORMAL ] Iteration 112: k_eff = 1.150231 res = 3.025E-08 delta-k (pcm)
[ NORMAL ] ... = 302 D.R. = 0.68
[ NORMAL ] Iteration 113: k_eff = 1.153146 res = 5.384E-08 delta-k (pcm)
[ NORMAL ] ... = 291 D.R. = 1.78
[ NORMAL ] Iteration 114: k_eff = 1.155950 res = 3.569E-08 delta-k (pcm)
[ NORMAL ] ... = 280 D.R. = 0.66
[ NORMAL ] Iteration 115: k_eff = 1.158649 res = 5.142E-08 delta-k (pcm)
[ NORMAL ] ... = 269 D.R. = 1.44
[ NORMAL ] Iteration 116: k_eff = 1.161244 res = 2.843E-08 delta-k (pcm)
[ NORMAL ] ... = 259 D.R. = 0.55
[ NORMAL ] Iteration 117: k_eff = 1.163739 res = 3.267E-08 delta-k (pcm)
[ NORMAL ] ... = 249 D.R. = 1.15
[ NORMAL ] Iteration 118: k_eff = 1.166139 res = 5.505E-08 delta-k (pcm)
[ NORMAL ] ... = 239 D.R. = 1.69
[ NORMAL ] Iteration 119: k_eff = 1.168445 res = 4.719E-08 delta-k (pcm)
[ NORMAL ] ... = 230 D.R. = 0.86
[ NORMAL ] Iteration 120: k_eff = 1.170662 res = 6.170E-08 delta-k (pcm)
[ NORMAL ] ... = 221 D.R. = 1.31
[ NORMAL ] Iteration 121: k_eff = 1.172791 res = 5.384E-08 delta-k (pcm)
[ NORMAL ] ... = 212 D.R. = 0.87
[ NORMAL ] Iteration 122: k_eff = 1.174837 res = 1.331E-08 delta-k (pcm)
[ NORMAL ] ... = 204 D.R. = 0.25
[ NORMAL ] Iteration 123: k_eff = 1.176801 res = 1.391E-08 delta-k (pcm)
[ NORMAL ] ... = 196 D.R. = 1.05
[ NORMAL ] Iteration 124: k_eff = 1.178688 res = 1.089E-08 delta-k (pcm)
[ NORMAL ] ... = 188 D.R. = 0.78
[ NORMAL ] Iteration 125: k_eff = 1.180500 res = 3.448E-08 delta-k (pcm)
[ NORMAL ] ... = 181 D.R. = 3.17
[ NORMAL ] Iteration 126: k_eff = 1.182238 res = 1.041E-07 delta-k (pcm)
[ NORMAL ] ... = 173 D.R. = 3.02
[ NORMAL ] Iteration 127: k_eff = 1.183907 res = 3.690E-08 delta-k (pcm)
[ NORMAL ] ... = 166 D.R. = 0.35
[ NORMAL ] Iteration 128: k_eff = 1.185508 res = 2.722E-08 delta-k (pcm)
[ NORMAL ] ... = 160 D.R. = 0.74
[ NORMAL ] Iteration 129: k_eff = 1.187044 res = 9.074E-09 delta-k (pcm)
[ NORMAL ] ... = 153 D.R. = 0.33
[ NORMAL ] Iteration 130: k_eff = 1.188518 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 147 D.R. = 3.20
[ NORMAL ] Iteration 131: k_eff = 1.189930 res = 2.783E-08 delta-k (pcm)
[ NORMAL ] ... = 141 D.R. = 0.96
[ NORMAL ] Iteration 132: k_eff = 1.191285 res = 2.541E-08 delta-k (pcm)
[ NORMAL ] ... = 135 D.R. = 0.91
[ NORMAL ] Iteration 133: k_eff = 1.192584 res = 4.537E-08 delta-k (pcm)
[ NORMAL ] ... = 129 D.R. = 1.79
[ NORMAL ] Iteration 134: k_eff = 1.193829 res = 5.505E-08 delta-k (pcm)
[ NORMAL ] ... = 124 D.R. = 1.21
[ NORMAL ] Iteration 135: k_eff = 1.195022 res = 6.412E-08 delta-k (pcm)
[ NORMAL ] ... = 119 D.R. = 1.16
[ NORMAL ] Iteration 136: k_eff = 1.196166 res = 6.049E-10 delta-k (pcm)
[ NORMAL ] ... = 114 D.R. = 0.01
[ NORMAL ] Iteration 137: k_eff = 1.197262 res = 4.416E-08 delta-k (pcm)
[ NORMAL ] ... = 109 D.R. = 73.00
[ NORMAL ] Iteration 138: k_eff = 1.198311 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 104 D.R. = 0.88
[ NORMAL ] Iteration 139: k_eff = 1.199316 res = 2.420E-09 delta-k (pcm)
[ NORMAL ] ... = 100 D.R. = 0.06
[ NORMAL ] Iteration 140: k_eff = 1.200279 res = 4.053E-08 delta-k (pcm)
[ NORMAL ] ... = 96 D.R. = 16.75
[ NORMAL ] Iteration 141: k_eff = 1.201201 res = 1.028E-08 delta-k (pcm)
[ NORMAL ] ... = 92 D.R. = 0.25
[ NORMAL ] Iteration 142: k_eff = 1.202083 res = 3.509E-08 delta-k (pcm)
[ NORMAL ] ... = 88 D.R. = 3.41
[ NORMAL ] Iteration 143: k_eff = 1.202927 res = 1.815E-08 delta-k (pcm)
[ NORMAL ] ... = 84 D.R. = 0.52
[ NORMAL ] Iteration 144: k_eff = 1.203736 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 80 D.R. = 1.07
[ NORMAL ] Iteration 145: k_eff = 1.204510 res = 6.836E-08 delta-k (pcm)
[ NORMAL ] ... = 77 D.R. = 3.53
[ NORMAL ] Iteration 146: k_eff = 1.205251 res = 5.324E-08 delta-k (pcm)
[ NORMAL ] ... = 74 D.R. = 0.78
[ NORMAL ] Iteration 147: k_eff = 1.205959 res = 2.299E-08 delta-k (pcm)
[ NORMAL ] ... = 70 D.R. = 0.43
[ NORMAL ] Iteration 148: k_eff = 1.206637 res = 1.815E-08 delta-k (pcm)
[ NORMAL ] ... = 67 D.R. = 0.79
[ NORMAL ] Iteration 149: k_eff = 1.207285 res = 8.469E-09 delta-k (pcm)
[ NORMAL ] ... = 64 D.R. = 0.47
[ NORMAL ] Iteration 150: k_eff = 1.207905 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 61 D.R. = 2.29
[ NORMAL ] Iteration 151: k_eff = 1.208498 res = 9.074E-09 delta-k (pcm)
[ NORMAL ] ... = 59 D.R. = 0.47
[ NORMAL ] Iteration 152: k_eff = 1.209065 res = 2.178E-08 delta-k (pcm)
[ NORMAL ] ... = 56 D.R. = 2.40
[ NORMAL ] Iteration 153: k_eff = 1.209607 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 54 D.R. = 3.11
[ NORMAL ] Iteration 154: k_eff = 1.210125 res = 9.074E-08 delta-k (pcm)
[ NORMAL ] ... = 51 D.R. = 1.34
[ NORMAL ] Iteration 155: k_eff = 1.210621 res = 5.445E-09 delta-k (pcm)
[ NORMAL ] ... = 49 D.R. = 0.06
[ NORMAL ] Iteration 156: k_eff = 1.211094 res = 6.049E-09 delta-k (pcm)
[ NORMAL ] ... = 47 D.R. = 1.11
[ NORMAL ] Iteration 157: k_eff = 1.211546 res = 6.049E-09 delta-k (pcm)
[ NORMAL ] ... = 45 D.R. = 1.00
[ NORMAL ] Iteration 158: k_eff = 1.211978 res = 4.658E-08 delta-k (pcm)
[ NORMAL ] ... = 43 D.R. = 7.70
[ NORMAL ] Iteration 159: k_eff = 1.212391 res = 1.815E-09 delta-k (pcm)
[ NORMAL ] ... = 41 D.R. = 0.04
[ NORMAL ] Iteration 160: k_eff = 1.212786 res = 1.210E-08 delta-k (pcm)
[ NORMAL ] ... = 39 D.R. = 6.67
[ NORMAL ] Iteration 161: k_eff = 1.213162 res = 6.049E-10 delta-k (pcm)
[ NORMAL ] ... = 37 D.R. = 0.05
[ NORMAL ] Iteration 162: k_eff = 1.213522 res = 1.996E-08 delta-k (pcm)
[ NORMAL ] ... = 35 D.R. = 33.00
[ NORMAL ] Iteration 163: k_eff = 1.213866 res = 4.658E-08 delta-k (pcm)
[ NORMAL ] ... = 34 D.R. = 2.33
[ NORMAL ] Iteration 164: k_eff = 1.214194 res = 1.996E-08 delta-k (pcm)
[ NORMAL ] ... = 32 D.R. = 0.43
[ NORMAL ] Iteration 165: k_eff = 1.214507 res = 1.210E-09 delta-k (pcm)
[ NORMAL ] ... = 31 D.R. = 0.06
[ NORMAL ] Iteration 166: k_eff = 1.214806 res = 1.875E-08 delta-k (pcm)
[ NORMAL ] ... = 29 D.R. = 15.50
[ NORMAL ] Iteration 167: k_eff = 1.215092 res = 4.961E-08 delta-k (pcm)
[ NORMAL ] ... = 28 D.R. = 2.65
[ NORMAL ] Iteration 168: k_eff = 1.215365 res = 6.049E-08 delta-k (pcm)
[ NORMAL ] ... = 27 D.R. = 1.22
[ NORMAL ] Iteration 169: k_eff = 1.215625 res = 2.964E-08 delta-k (pcm)
[ NORMAL ] ... = 26 D.R. = 0.49
[ NORMAL ] Iteration 170: k_eff = 1.215874 res = 5.505E-08 delta-k (pcm)
[ NORMAL ] ... = 24 D.R. = 1.86
[ NORMAL ] Iteration 171: k_eff = 1.216110 res = 2.117E-08 delta-k (pcm)
[ NORMAL ] ... = 23 D.R. = 0.38
[ NORMAL ] Iteration 172: k_eff = 1.216337 res = 4.174E-08 delta-k (pcm)
[ NORMAL ] ... = 22 D.R. = 1.97
[ NORMAL ] Iteration 173: k_eff = 1.216552 res = 3.509E-08 delta-k (pcm)
[ NORMAL ] ... = 21 D.R. = 0.84
[ NORMAL ] Iteration 174: k_eff = 1.216759 res = 2.722E-08 delta-k (pcm)
[ NORMAL ] ... = 20 D.R. = 0.78
[ NORMAL ] Iteration 175: k_eff = 1.216954 res = 2.601E-08 delta-k (pcm)
[ NORMAL ] ... = 19 D.R. = 0.96
[ NORMAL ] Iteration 176: k_eff = 1.217142 res = 4.295E-08 delta-k (pcm)
[ NORMAL ] ... = 18 D.R. = 1.65
[ NORMAL ] Iteration 177: k_eff = 1.217320 res = 4.598E-08 delta-k (pcm)
[ NORMAL ] ... = 17 D.R. = 1.07
[ NORMAL ] Iteration 178: k_eff = 1.217491 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 17 D.R. = 1.26
[ NORMAL ] Iteration 179: k_eff = 1.217654 res = 2.480E-08 delta-k (pcm)
[ NORMAL ] ... = 16 D.R. = 0.43
[ NORMAL ] Iteration 180: k_eff = 1.217809 res = 6.049E-09 delta-k (pcm)
[ NORMAL ] ... = 15 D.R. = 0.24
[ NORMAL ] Iteration 181: k_eff = 1.217956 res = 6.412E-08 delta-k (pcm)
[ NORMAL ] ... = 14 D.R. = 10.60
[ NORMAL ] Iteration 182: k_eff = 1.218098 res = 7.138E-08 delta-k (pcm)
[ NORMAL ] ... = 14 D.R. = 1.11
[ NORMAL ] Iteration 183: k_eff = 1.218232 res = 1.633E-08 delta-k (pcm)
[ NORMAL ] ... = 13 D.R. = 0.23
[ NORMAL ] Iteration 184: k_eff = 1.218360 res = 2.541E-08 delta-k (pcm)
[ NORMAL ] ... = 12 D.R. = 1.56
[ NORMAL ] Iteration 185: k_eff = 1.218482 res = 1.028E-08 delta-k (pcm)
[ NORMAL ] ... = 12 D.R. = 0.40
[ NORMAL ] Iteration 186: k_eff = 1.218599 res = 7.864E-09 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 0.76
[ NORMAL ] Iteration 187: k_eff = 1.218709 res = 4.053E-08 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 5.15
[ NORMAL ] Iteration 188: k_eff = 1.218815 res = 2.299E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 0.57
[ NORMAL ] Iteration 189: k_eff = 1.218916 res = 5.445E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 2.37
[ NORMAL ] Iteration 190: k_eff = 1.219011 res = 3.327E-08 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = 0.61
[ NORMAL ] Iteration 191: k_eff = 1.219103 res = 4.840E-09 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = 0.15
[ NORMAL ] Iteration 192: k_eff = 1.219190 res = 1.210E-08 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 2.50
[ NORMAL ] Iteration 193: k_eff = 1.219273 res = 1.573E-08 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 1.30
[ NORMAL ] Iteration 194: k_eff = 1.219352 res = 1.331E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 0.85
[ NORMAL ] Iteration 195: k_eff = 1.219428 res = 2.783E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 2.09
[ NORMAL ] Iteration 196: k_eff = 1.219499 res = 2.722E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 0.98
[ NORMAL ] Iteration 197: k_eff = 1.219567 res = 2.057E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.76
[ NORMAL ] Iteration 198: k_eff = 1.219633 res = 1.331E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.65
[ NORMAL ] Iteration 199: k_eff = 1.219695 res = 3.932E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 2.95
[ NORMAL ] Iteration 200: k_eff = 1.219753 res = 1.996E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.51
[ NORMAL ] Iteration 201: k_eff = 1.219810 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 2.42
[ NORMAL ] Iteration 202: k_eff = 1.219863 res = 1.270E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.26
[ NORMAL ] Iteration 203: k_eff = 1.219914 res = 2.662E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 2.10
[ NORMAL ] Iteration 204: k_eff = 1.219962 res = 5.445E-09 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.20
[ NORMAL ] Iteration 205: k_eff = 1.220009 res = 3.327E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 6.11
[ NORMAL ] Iteration 206: k_eff = 1.220052 res = 4.658E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 1.40
[ NORMAL ] Iteration 207: k_eff = 1.220094 res = 3.025E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.65
[ NORMAL ] Iteration 208: k_eff = 1.220134 res = 2.117E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.70
[ NORMAL ] Iteration 209: k_eff = 1.220172 res = 1.875E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.89
[ NORMAL ] Iteration 210: k_eff = 1.220208 res = 1.028E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.55
[ NORMAL ] Iteration 211: k_eff = 1.220243 res = 5.263E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 5.12
[ NORMAL ] Iteration 212: k_eff = 1.220275 res = 2.480E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.47
[ NORMAL ] Iteration 213: k_eff = 1.220306 res = 4.598E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 1.85
[ NORMAL ] Iteration 214: k_eff = 1.220336 res = 3.630E-09 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.08
[ NORMAL ] Iteration 215: k_eff = 1.220364 res = 1.391E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 3.83
[ NORMAL ] Iteration 216: k_eff = 1.220391 res = 6.049E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 4.35
[ NORMAL ] Iteration 217: k_eff = 1.220416 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.32
[ NORMAL ] Iteration 218: k_eff = 1.220441 res = 6.049E-10 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.03
[ NORMAL ] Iteration 219: k_eff = 1.220464 res = 1.270E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 21.00
[ NORMAL ] Iteration 220: k_eff = 1.220486 res = 1.452E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 1.14
[ NORMAL ] Iteration 221: k_eff = 1.220507 res = 2.299E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 1.58
[ NORMAL ] Iteration 222: k_eff = 1.220527 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.42
[ NORMAL ] Iteration 223: k_eff = 1.220545 res = 7.078E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 7.31
[ NORMAL ] Iteration 224: k_eff = 1.220563 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.14
[ NORMAL ] Iteration 225: k_eff = 1.220580 res = 3.146E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 3.25
[ NORMAL ] Iteration 226: k_eff = 1.220596 res = 1.633E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.52
[ NORMAL ] Iteration 227: k_eff = 1.220612 res = 3.569E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 2.19
[ NORMAL ] Iteration 228: k_eff = 1.220627 res = 2.722E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.76
[ NORMAL ] Iteration 229: k_eff = 1.220641 res = 1.875E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.69
[ NORMAL ] Iteration 230: k_eff = 1.220655 res = 3.448E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.84
[ NORMAL ] Iteration 231: k_eff = 1.220667 res = 8.046E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 2.33
[ NORMAL ] Iteration 232: k_eff = 1.220679 res = 3.751E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.47
[ NORMAL ] Iteration 233: k_eff = 1.220690 res = 5.686E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.52
[ NORMAL ] Iteration 234: k_eff = 1.220701 res = 1.270E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.22
[ NORMAL ] Iteration 235: k_eff = 1.220711 res = 6.715E-08 delta-k (pcm)
[ NORMAL ] ... = 0 D.R. = 5.29
We report the eigenvalues computed by OpenMC and OpenMOC here together to summarize our results.
[24]:
# Print report of keff and bias with OpenMC
openmoc_keff = solver.getKeff()
openmc_keff = sp.k_combined.n
bias = (openmoc_keff - openmc_keff) * 1e5
print('openmc keff = {0:1.6f}'.format(openmc_keff))
print('openmoc keff = {0:1.6f}'.format(openmoc_keff))
print('bias [pcm]: {0:1.1f}'.format(bias))
openmc keff = 1.224484
openmoc keff = 1.220711
bias [pcm]: -377.3
As a sanity check, let’s run a simulation with the coarse 2-group cross sections to ensure that they also produce a reasonable result.
[25]:
openmoc_geometry = get_openmoc_geometry(sp.summary.geometry)
openmoc_cells = openmoc_geometry.getRootUniverse().getAllCells()
# Inject multi-group cross sections into OpenMOC Materials
for cell_id, cell in openmoc_cells.items():
# Ignore the root cell
if cell.getName() == 'root cell':
continue
openmoc_material = cell.getFillMaterial()
openmoc_material.setNumEnergyGroups(coarse_groups.num_groups)
# Extract the appropriate cross section objects for this cell
transport = xs_library[cell_id]['transport']
nufission = xs_library[cell_id]['nu-fission']
nuscatter = xs_library[cell_id]['nu-scatter']
chi = xs_library[cell_id]['chi']
# Perform group condensation
transport = transport.get_condensed_xs(coarse_groups)
nufission = nufission.get_condensed_xs(coarse_groups)
nuscatter = nuscatter.get_condensed_xs(coarse_groups)
chi = chi.get_condensed_xs(coarse_groups)
# Inject NumPy arrays of cross section data into the Material
openmoc_material.setSigmaT(transport.get_xs(nuclides='sum').flatten())
openmoc_material.setNuSigmaF(nufission.get_xs(nuclides='sum').flatten())
openmoc_material.setSigmaS(nuscatter.get_xs(nuclides='sum').flatten())
openmoc_material.setChi(chi.get_xs(nuclides='sum').flatten())
[26]:
# Generate tracks for OpenMOC
track_generator = openmoc.TrackGenerator(openmoc_geometry, num_azim=128, azim_spacing=0.1)
track_generator.generateTracks()
# Run OpenMOC
solver = openmoc.CPUSolver(track_generator)
solver.computeEigenvalue()
[ NORMAL ] Initializing a default angular quadrature...
[ NORMAL ] Initializing 2D tracks...
[ NORMAL ] Initializing 2D tracks reflections...
[ NORMAL ] Initializing 2D tracks array...
[ NORMAL ] Ray tracing for 2D track segmentation...
[ NORMAL ] Progress Segmenting 2D tracks: 0.09 %
[ NORMAL ] Progress Segmenting 2D tracks: 10.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 19.94 %
[ NORMAL ] Progress Segmenting 2D tracks: 29.87 %
[ NORMAL ] Progress Segmenting 2D tracks: 39.80 %
[ NORMAL ] Progress Segmenting 2D tracks: 49.72 %
[ NORMAL ] Progress Segmenting 2D tracks: 59.65 %
[ NORMAL ] Progress Segmenting 2D tracks: 69.58 %
[ NORMAL ] Progress Segmenting 2D tracks: 79.50 %
[ NORMAL ] Progress Segmenting 2D tracks: 89.43 %
[ NORMAL ] Progress Segmenting 2D tracks: 100.00 %
[ NORMAL ] Initializing FSR lookup vectors
[ NORMAL ] Total number of FSRs 3
[ RESULT ] Total Track Generation & Segmentation Time...........3.9517E-02 sec
[ NORMAL ] Initializing MOC eigenvalue solver...
[ NORMAL ] Initializing solver arrays...
[ NORMAL ] Centering segments around FSR centroid...
[ NORMAL ] Max boundary angular flux storage per domain = 0.10 MB
[ NORMAL ] Max scalar flux storage per domain = 0.00 MB
[ NORMAL ] Max source storage per domain = 0.00 MB
[ NORMAL ] Number of azimuthal angles = 128
[ NORMAL ] Azimuthal ray spacing = 0.100000
[ NORMAL ] Number of polar angles = 6
[ NORMAL ] Source type = Flat
[ NORMAL ] MOC transport undamped
[ NORMAL ] CMFD acceleration: OFF
[ NORMAL ] Using 1 threads
[ NORMAL ] Computing the eigenvalue...
[ NORMAL ] Iteration 0: k_eff = 0.366880 res = 4.840E-08 delta-k (pcm) =
[ NORMAL ] ... -63312 D.R. = 0.00
[ NORMAL ] Iteration 1: k_eff = 0.391184 res = 9.679E-09 delta-k (pcm) =
[ NORMAL ] ... 2430 D.R. = 0.20
[ NORMAL ] Iteration 2: k_eff = 0.392990 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 180 D.R. = 6.00
[ NORMAL ] Iteration 3: k_eff = 0.381099 res = 9.195E-08 delta-k (pcm) =
[ NORMAL ] ... -1189 D.R. = 1.58
[ NORMAL ] Iteration 4: k_eff = 0.375018 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... -608 D.R. = 0.00
[ NORMAL ] Iteration 5: k_eff = 0.369593 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... -542 D.R. = inf
[ NORMAL ] Iteration 6: k_eff = 0.365543 res = 1.065E-07 delta-k (pcm) =
[ NORMAL ] ... -405 D.R. = 5.50
[ NORMAL ] Iteration 7: k_eff = 0.363054 res = 1.065E-07 delta-k (pcm) =
[ NORMAL ] ... -248 D.R. = 1.00
[ NORMAL ] Iteration 8: k_eff = 0.361473 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... -158 D.R. = 0.18
[ NORMAL ] Iteration 9: k_eff = 0.361280 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... -19 D.R. = 3.00
[ NORMAL ] Iteration 10: k_eff = 0.362003 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 72 D.R. = 0.33
[ NORMAL ] Iteration 11: k_eff = 0.363718 res = 4.840E-08 delta-k (pcm) =
[ NORMAL ] ... 171 D.R. = 2.50
[ NORMAL ] Iteration 12: k_eff = 0.366338 res = 1.258E-07 delta-k (pcm) =
[ NORMAL ] ... 262 D.R. = 2.60
[ NORMAL ] Iteration 13: k_eff = 0.369804 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 346 D.R. = 0.77
[ NORMAL ] Iteration 14: k_eff = 0.373989 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 418 D.R. = 0.40
[ NORMAL ] Iteration 15: k_eff = 0.378923 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 493 D.R. = 0.00
[ NORMAL ] Iteration 16: k_eff = 0.384479 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 555 D.R. = inf
[ NORMAL ] Iteration 17: k_eff = 0.390637 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 615 D.R. = 1.00
[ NORMAL ] Iteration 18: k_eff = 0.397338 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 670 D.R. = 0.00
[ NORMAL ] Iteration 19: k_eff = 0.404533 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 719 D.R. = inf
[ NORMAL ] Iteration 20: k_eff = 0.412184 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 765 D.R. = 1.67
[ NORMAL ] Iteration 21: k_eff = 0.420253 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 806 D.R. = 0.80
[ NORMAL ] Iteration 22: k_eff = 0.428686 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 843 D.R. = 0.75
[ NORMAL ] Iteration 23: k_eff = 0.437462 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 877 D.R. = 1.67
[ NORMAL ] Iteration 24: k_eff = 0.446538 res = 1.161E-07 delta-k (pcm) =
[ NORMAL ] ... 907 D.R. = 1.20
[ NORMAL ] Iteration 25: k_eff = 0.455883 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 934 D.R. = 0.17
[ NORMAL ] Iteration 26: k_eff = 0.465469 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 958 D.R. = 1.00
[ NORMAL ] Iteration 27: k_eff = 0.475265 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 979 D.R. = 2.00
[ NORMAL ] Iteration 28: k_eff = 0.485246 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 998 D.R. = 1.00
[ NORMAL ] Iteration 29: k_eff = 0.495385 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 1013 D.R. = 0.50
[ NORMAL ] Iteration 30: k_eff = 0.505661 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 1027 D.R. = 4.00
[ NORMAL ] Iteration 31: k_eff = 0.516051 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1038 D.R. = 1.25
[ NORMAL ] Iteration 32: k_eff = 0.526534 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1048 D.R. = 1.00
[ NORMAL ] Iteration 33: k_eff = 0.537092 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1055 D.R. = 1.00
[ NORMAL ] Iteration 34: k_eff = 0.547706 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 1061 D.R. = 0.60
[ NORMAL ] Iteration 35: k_eff = 0.558361 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 1065 D.R. = 0.33
[ NORMAL ] Iteration 36: k_eff = 0.569040 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 1067 D.R. = 3.00
[ NORMAL ] Iteration 37: k_eff = 0.579730 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1068 D.R. = 1.67
[ NORMAL ] Iteration 38: k_eff = 0.590416 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 1068 D.R. = 0.00
[ NORMAL ] Iteration 39: k_eff = 0.601087 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1067 D.R. = inf
[ NORMAL ] Iteration 40: k_eff = 0.611731 res = 1.549E-07 delta-k (pcm) =
[ NORMAL ] ... 1064 D.R. = 1.60
[ NORMAL ] Iteration 41: k_eff = 0.622338 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 1060 D.R. = 0.50
[ NORMAL ] Iteration 42: k_eff = 0.632897 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 1055 D.R. = 1.00
[ NORMAL ] Iteration 43: k_eff = 0.643400 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 1050 D.R. = 0.75
[ NORMAL ] Iteration 44: k_eff = 0.653837 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 1043 D.R. = 0.00
[ NORMAL ] Iteration 45: k_eff = 0.664203 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 1036 D.R. = inf
[ NORMAL ] Iteration 46: k_eff = 0.674488 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1028 D.R. = 1.25
[ NORMAL ] Iteration 47: k_eff = 0.684688 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 1020 D.R. = 1.00
[ NORMAL ] Iteration 48: k_eff = 0.694796 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 1010 D.R. = 0.40
[ NORMAL ] Iteration 49: k_eff = 0.704807 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 1001 D.R. = 1.00
[ NORMAL ] Iteration 50: k_eff = 0.714715 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 990 D.R. = 0.50
[ NORMAL ] Iteration 51: k_eff = 0.724517 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 980 D.R. = 1.00
[ NORMAL ] Iteration 52: k_eff = 0.734209 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 969 D.R. = 1.00
[ NORMAL ] Iteration 53: k_eff = 0.743787 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 957 D.R. = 0.00
[ NORMAL ] Iteration 54: k_eff = 0.753247 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 946 D.R. = inf
[ NORMAL ] Iteration 55: k_eff = 0.762588 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 934 D.R. = 1.50
[ NORMAL ] Iteration 56: k_eff = 0.771806 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 921 D.R. = 0.33
[ NORMAL ] Iteration 57: k_eff = 0.780901 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 909 D.R. = 2.00
[ NORMAL ] Iteration 58: k_eff = 0.789868 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 896 D.R. = 0.50
[ NORMAL ] Iteration 59: k_eff = 0.798708 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 884 D.R. = 1.00
[ NORMAL ] Iteration 60: k_eff = 0.807419 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 871 D.R. = 4.00
[ NORMAL ] Iteration 61: k_eff = 0.816000 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 858 D.R. = 0.50
[ NORMAL ] Iteration 62: k_eff = 0.824450 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 844 D.R. = 1.00
[ NORMAL ] Iteration 63: k_eff = 0.832768 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 831 D.R. = 2.00
[ NORMAL ] Iteration 64: k_eff = 0.840954 res = 1.742E-07 delta-k (pcm) =
[ NORMAL ] ... 818 D.R. = 2.25
[ NORMAL ] Iteration 65: k_eff = 0.849008 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 805 D.R. = 0.44
[ NORMAL ] Iteration 66: k_eff = 0.856930 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 792 D.R. = 0.50
[ NORMAL ] Iteration 67: k_eff = 0.864720 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 778 D.R. = 1.00
[ NORMAL ] Iteration 68: k_eff = 0.872378 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 765 D.R. = 1.00
[ NORMAL ] Iteration 69: k_eff = 0.879905 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 752 D.R. = 0.50
[ NORMAL ] Iteration 70: k_eff = 0.887301 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 739 D.R. = 2.00
[ NORMAL ] Iteration 71: k_eff = 0.894566 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 726 D.R. = 1.50
[ NORMAL ] Iteration 72: k_eff = 0.901702 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 713 D.R. = 0.33
[ NORMAL ] Iteration 73: k_eff = 0.908710 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 700 D.R. = 2.00
[ NORMAL ] Iteration 74: k_eff = 0.915590 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 687 D.R. = 1.50
[ NORMAL ] Iteration 75: k_eff = 0.922342 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 675 D.R. = 0.00
[ NORMAL ] Iteration 76: k_eff = 0.928971 res = 1.161E-07 delta-k (pcm) =
[ NORMAL ] ... 662 D.R. = inf
[ NORMAL ] Iteration 77: k_eff = 0.935474 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 650 D.R. = 0.17
[ NORMAL ] Iteration 78: k_eff = 0.941853 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 637 D.R. = 1.00
[ NORMAL ] Iteration 79: k_eff = 0.948112 res = 9.679E-08 delta-k (pcm) =
[ NORMAL ] ... 625 D.R. = 5.00
[ NORMAL ] Iteration 80: k_eff = 0.954249 res = 1.065E-07 delta-k (pcm) =
[ NORMAL ] ... 613 D.R. = 1.10
[ NORMAL ] Iteration 81: k_eff = 0.960267 res = 9.679E-09 delta-k (pcm) =
[ NORMAL ] ... 601 D.R. = 0.09
[ NORMAL ] Iteration 82: k_eff = 0.966168 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 590 D.R. = 8.00
[ NORMAL ] Iteration 83: k_eff = 0.971953 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 578 D.R. = 0.75
[ NORMAL ] Iteration 84: k_eff = 0.977623 res = 1.936E-08 delta-k (pcm) =
[ NORMAL ] ... 566 D.R. = 0.33
[ NORMAL ] Iteration 85: k_eff = 0.983179 res = 2.904E-08 delta-k (pcm) =
[ NORMAL ] ... 555 D.R. = 1.50
[ NORMAL ] Iteration 86: k_eff = 0.988624 res = 0.000E+00 delta-k (pcm) =
[ NORMAL ] ... 544 D.R. = 0.00
[ NORMAL ] Iteration 87: k_eff = 0.993958 res = 6.775E-08 delta-k (pcm) =
[ NORMAL ] ... 533 D.R. = inf
[ NORMAL ] Iteration 88: k_eff = 0.999184 res = 9.679E-09 delta-k (pcm) =
[ NORMAL ] ... 522 D.R. = 0.14
[ NORMAL ] Iteration 89: k_eff = 1.004304 res = 4.840E-08 delta-k (pcm) =
[ NORMAL ] ... 511 D.R. = 5.00
[ NORMAL ] Iteration 90: k_eff = 1.009318 res = 3.872E-08 delta-k (pcm) =
[ NORMAL ] ... 501 D.R. = 0.80
[ NORMAL ] Iteration 91: k_eff = 1.014228 res = 4.840E-08 delta-k (pcm) =
[ NORMAL ] ... 491 D.R. = 1.25
[ NORMAL ] Iteration 92: k_eff = 1.019037 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 480 D.R. = 1.20
[ NORMAL ] Iteration 93: k_eff = 1.023744 res = 2.904E-08 delta-k (pcm) =
[ NORMAL ] ... 470 D.R. = 0.50
[ NORMAL ] Iteration 94: k_eff = 1.028353 res = 6.775E-08 delta-k (pcm) =
[ NORMAL ] ... 460 D.R. = 2.33
[ NORMAL ] Iteration 95: k_eff = 1.032865 res = 7.743E-08 delta-k (pcm) =
[ NORMAL ] ... 451 D.R. = 1.14
[ NORMAL ] Iteration 96: k_eff = 1.037281 res = 1.355E-07 delta-k (pcm) =
[ NORMAL ] ... 441 D.R. = 1.75
[ NORMAL ] Iteration 97: k_eff = 1.041604 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 432 D.R. = 0.43
[ NORMAL ] Iteration 98: k_eff = 1.045834 res = 4.840E-08 delta-k (pcm) =
[ NORMAL ] ... 422 D.R. = 0.83
[ NORMAL ] Iteration 99: k_eff = 1.049974 res = 5.807E-08 delta-k (pcm) =
[ NORMAL ] ... 413 D.R. = 1.20
[ NORMAL ] Iteration 100: k_eff = 1.054024 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 405 D.R. = 1.00
[ NORMAL ] Iteration 101: k_eff = 1.057987 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 396 D.R. = 0.83
[ NORMAL ] Iteration 102: k_eff = 1.061864 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 387 D.R. = 0.20
[ NORMAL ] Iteration 103: k_eff = 1.065657 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 379 D.R. = 3.00
[ NORMAL ] Iteration 104: k_eff = 1.069367 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 370 D.R. = 1.33
[ NORMAL ] Iteration 105: k_eff = 1.072996 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 362 D.R. = 1.00
[ NORMAL ] Iteration 106: k_eff = 1.076545 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 354 D.R. = 0.50
[ NORMAL ] Iteration 107: k_eff = 1.080016 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 347 D.R. = 3.50
[ NORMAL ] Iteration 108: k_eff = 1.083411 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 339 D.R. = 1.71
[ NORMAL ] Iteration 109: k_eff = 1.086731 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 331 D.R. = 0.50
[ NORMAL ] Iteration 110: k_eff = 1.089976 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 324 D.R. = 0.33
[ NORMAL ] Iteration 111: k_eff = 1.093150 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 317 D.R. = 4.00
[ NORMAL ] Iteration 112: k_eff = 1.096253 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 310 D.R. = 1.00
[ NORMAL ] Iteration 113: k_eff = 1.099286 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 303 D.R. = 0.50
[ NORMAL ] Iteration 114: k_eff = 1.102251 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 296 D.R. = 1.25
[ NORMAL ] Iteration 115: k_eff = 1.105150 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 289 D.R. = 0.20
[ NORMAL ] Iteration 116: k_eff = 1.107983 res = 1.452E-07 delta-k (pcm)
[ NORMAL ] ... = 283 D.R. = 15.00
[ NORMAL ] Iteration 117: k_eff = 1.110753 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 276 D.R. = 0.33
[ NORMAL ] Iteration 118: k_eff = 1.113459 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 270 D.R. = 1.80
[ NORMAL ] Iteration 119: k_eff = 1.116105 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 264 D.R. = 1.22
[ NORMAL ] Iteration 120: k_eff = 1.118690 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 258 D.R. = 0.36
[ NORMAL ] Iteration 121: k_eff = 1.121216 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 252 D.R. = 0.25
[ NORMAL ] Iteration 122: k_eff = 1.123685 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 246 D.R. = 1.00
[ NORMAL ] Iteration 123: k_eff = 1.126097 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 241 D.R. = 4.00
[ NORMAL ] Iteration 124: k_eff = 1.128454 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 235 D.R. = 0.00
[ NORMAL ] Iteration 125: k_eff = 1.130758 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 230 D.R. = inf
[ NORMAL ] Iteration 126: k_eff = 1.133008 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 224 D.R. = 5.50
[ NORMAL ] Iteration 127: k_eff = 1.135207 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 219 D.R. = 0.45
[ NORMAL ] Iteration 128: k_eff = 1.137354 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 214 D.R. = 0.40
[ NORMAL ] Iteration 129: k_eff = 1.139453 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 209 D.R. = 0.00
[ NORMAL ] Iteration 130: k_eff = 1.141503 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 204 D.R. = inf
[ NORMAL ] Iteration 131: k_eff = 1.143505 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 200 D.R. = 1.50
[ NORMAL ] Iteration 132: k_eff = 1.145461 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 195 D.R. = 2.00
[ NORMAL ] Iteration 133: k_eff = 1.147372 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 191 D.R. = 1.50
[ NORMAL ] Iteration 134: k_eff = 1.149238 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 186 D.R. = 1.22
[ NORMAL ] Iteration 135: k_eff = 1.151061 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 182 D.R. = 0.18
[ NORMAL ] Iteration 136: k_eff = 1.152842 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 178 D.R. = 1.00
[ NORMAL ] Iteration 137: k_eff = 1.154581 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 173 D.R. = 2.00
[ NORMAL ] Iteration 138: k_eff = 1.156279 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 169 D.R. = 2.50
[ NORMAL ] Iteration 139: k_eff = 1.157938 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 165 D.R. = 0.80
[ NORMAL ] Iteration 140: k_eff = 1.159557 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 161 D.R. = 1.50
[ NORMAL ] Iteration 141: k_eff = 1.161139 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 158 D.R. = 0.33
[ NORMAL ] Iteration 142: k_eff = 1.162684 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 154 D.R. = 1.00
[ NORMAL ] Iteration 143: k_eff = 1.164193 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 150 D.R. = 2.75
[ NORMAL ] Iteration 144: k_eff = 1.165666 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 147 D.R. = 0.09
[ NORMAL ] Iteration 145: k_eff = 1.167105 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 143 D.R. = 2.00
[ NORMAL ] Iteration 146: k_eff = 1.168509 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 140 D.R. = 4.00
[ NORMAL ] Iteration 147: k_eff = 1.169881 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 137 D.R. = 1.25
[ NORMAL ] Iteration 148: k_eff = 1.171220 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 133 D.R. = 0.10
[ NORMAL ] Iteration 149: k_eff = 1.172528 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 130 D.R. = 6.00
[ NORMAL ] Iteration 150: k_eff = 1.173804 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 127 D.R. = 1.67
[ NORMAL ] Iteration 151: k_eff = 1.175051 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 124 D.R. = 0.80
[ NORMAL ] Iteration 152: k_eff = 1.176268 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 121 D.R. = 1.00
[ NORMAL ] Iteration 153: k_eff = 1.177456 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 118 D.R. = 1.00
[ NORMAL ] Iteration 154: k_eff = 1.178616 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 115 D.R. = 0.63
[ NORMAL ] Iteration 155: k_eff = 1.179749 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 113 D.R. = 1.20
[ NORMAL ] Iteration 156: k_eff = 1.180855 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 110 D.R. = 0.83
[ NORMAL ] Iteration 157: k_eff = 1.181935 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 107 D.R. = 0.20
[ NORMAL ] Iteration 158: k_eff = 1.182988 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 105 D.R. = 3.00
[ NORMAL ] Iteration 159: k_eff = 1.184017 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 102 D.R. = 0.67
[ NORMAL ] Iteration 160: k_eff = 1.185021 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 100 D.R. = 1.50
[ NORMAL ] Iteration 161: k_eff = 1.186002 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 98 D.R. = 0.67
[ NORMAL ] Iteration 162: k_eff = 1.186959 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 95 D.R. = 2.50
[ NORMAL ] Iteration 163: k_eff = 1.187893 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 93 D.R. = 1.40
[ NORMAL ] Iteration 164: k_eff = 1.188805 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 91 D.R. = 1.29
[ NORMAL ] Iteration 165: k_eff = 1.189695 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 89 D.R. = 0.22
[ NORMAL ] Iteration 166: k_eff = 1.190564 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 86 D.R. = 2.00
[ NORMAL ] Iteration 167: k_eff = 1.191413 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 84 D.R. = 2.00
[ NORMAL ] Iteration 168: k_eff = 1.192241 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 82 D.R. = 0.00
[ NORMAL ] Iteration 169: k_eff = 1.193049 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 80 D.R. = inf
[ NORMAL ] Iteration 170: k_eff = 1.193838 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 78 D.R. = 4.50
[ NORMAL ] Iteration 171: k_eff = 1.194607 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 76 D.R. = 0.56
[ NORMAL ] Iteration 172: k_eff = 1.195359 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 75 D.R. = 1.20
[ NORMAL ] Iteration 173: k_eff = 1.196092 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 73 D.R. = 0.67
[ NORMAL ] Iteration 174: k_eff = 1.196808 res = 1.355E-07 delta-k (pcm)
[ NORMAL ] ... = 71 D.R. = 3.50
[ NORMAL ] Iteration 175: k_eff = 1.197507 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 69 D.R. = 0.86
[ NORMAL ] Iteration 176: k_eff = 1.198190 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 68 D.R. = 0.33
[ NORMAL ] Iteration 177: k_eff = 1.198855 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 66 D.R. = 0.75
[ NORMAL ] Iteration 178: k_eff = 1.199505 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 64 D.R. = 0.67
[ NORMAL ] Iteration 179: k_eff = 1.200139 res = 1.258E-07 delta-k (pcm)
[ NORMAL ] ... = 63 D.R. = 6.50
[ NORMAL ] Iteration 180: k_eff = 1.200757 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 61 D.R. = 0.62
[ NORMAL ] Iteration 181: k_eff = 1.201361 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 60 D.R. = 0.25
[ NORMAL ] Iteration 182: k_eff = 1.201951 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 58 D.R. = 1.50
[ NORMAL ] Iteration 183: k_eff = 1.202526 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 57 D.R. = 0.33
[ NORMAL ] Iteration 184: k_eff = 1.203088 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 56 D.R. = 5.00
[ NORMAL ] Iteration 185: k_eff = 1.203636 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 54 D.R. = 0.00
[ NORMAL ] Iteration 186: k_eff = 1.204171 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 53 D.R. = inf
[ NORMAL ] Iteration 187: k_eff = 1.204692 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 52 D.R. = 2.33
[ NORMAL ] Iteration 188: k_eff = 1.205202 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 50 D.R. = 0.57
[ NORMAL ] Iteration 189: k_eff = 1.205698 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 49 D.R. = 0.50
[ NORMAL ] Iteration 190: k_eff = 1.206183 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 48 D.R. = 0.50
[ NORMAL ] Iteration 191: k_eff = 1.206656 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 47 D.R. = 6.00
[ NORMAL ] Iteration 192: k_eff = 1.207118 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 46 D.R. = 0.00
[ NORMAL ] Iteration 193: k_eff = 1.207570 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 45 D.R. = inf
[ NORMAL ] Iteration 194: k_eff = 1.208010 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 44 D.R. = 0.33
[ NORMAL ] Iteration 195: k_eff = 1.208439 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 42 D.R. = 1.33
[ NORMAL ] Iteration 196: k_eff = 1.208858 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 41 D.R. = 0.50
[ NORMAL ] Iteration 197: k_eff = 1.209267 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 40 D.R. = 1.50
[ NORMAL ] Iteration 198: k_eff = 1.209665 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 39 D.R. = 1.33
[ NORMAL ] Iteration 199: k_eff = 1.210055 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 38 D.R. = 1.50
[ NORMAL ] Iteration 200: k_eff = 1.210435 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 37 D.R. = 0.17
[ NORMAL ] Iteration 201: k_eff = 1.210805 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 37 D.R. = 9.00
[ NORMAL ] Iteration 202: k_eff = 1.211167 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 36 D.R. = 0.11
[ NORMAL ] Iteration 203: k_eff = 1.211520 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 35 D.R. = 5.00
[ NORMAL ] Iteration 204: k_eff = 1.211864 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 34 D.R. = 0.60
[ NORMAL ] Iteration 205: k_eff = 1.212200 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 33 D.R. = 0.67
[ NORMAL ] Iteration 206: k_eff = 1.212528 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 32 D.R. = 2.50
[ NORMAL ] Iteration 207: k_eff = 1.212848 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 32 D.R. = 1.60
[ NORMAL ] Iteration 208: k_eff = 1.213160 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 31 D.R. = 0.25
[ NORMAL ] Iteration 209: k_eff = 1.213466 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 30 D.R. = 5.50
[ NORMAL ] Iteration 210: k_eff = 1.213763 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 29 D.R. = 0.55
[ NORMAL ] Iteration 211: k_eff = 1.214053 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 29 D.R. = 1.67
[ NORMAL ] Iteration 212: k_eff = 1.214337 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 28 D.R. = 0.30
[ NORMAL ] Iteration 213: k_eff = 1.214614 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 27 D.R. = 2.33
[ NORMAL ] Iteration 214: k_eff = 1.214883 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 26 D.R. = 1.29
[ NORMAL ] Iteration 215: k_eff = 1.215145 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 26 D.R. = 0.56
[ NORMAL ] Iteration 216: k_eff = 1.215402 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 25 D.R. = 0.40
[ NORMAL ] Iteration 217: k_eff = 1.215653 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 25 D.R. = 3.00
[ NORMAL ] Iteration 218: k_eff = 1.215897 res = 1.258E-07 delta-k (pcm)
[ NORMAL ] ... = 24 D.R. = 2.17
[ NORMAL ] Iteration 219: k_eff = 1.216136 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 23 D.R. = 0.77
[ NORMAL ] Iteration 220: k_eff = 1.216368 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 23 D.R. = 0.80
[ NORMAL ] Iteration 221: k_eff = 1.216595 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 22 D.R. = 0.25
[ NORMAL ] Iteration 222: k_eff = 1.216817 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 22 D.R. = 3.00
[ NORMAL ] Iteration 223: k_eff = 1.217033 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 21 D.R. = 0.33
[ NORMAL ] Iteration 224: k_eff = 1.217244 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 21 D.R. = 4.00
[ NORMAL ] Iteration 225: k_eff = 1.217450 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 20 D.R. = 0.75
[ NORMAL ] Iteration 226: k_eff = 1.217651 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 20 D.R. = 0.17
[ NORMAL ] Iteration 227: k_eff = 1.217847 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 19 D.R. = 4.00
[ NORMAL ] Iteration 228: k_eff = 1.218039 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 19 D.R. = 2.50
[ NORMAL ] Iteration 229: k_eff = 1.218225 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 18 D.R. = 0.20
[ NORMAL ] Iteration 230: k_eff = 1.218407 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 18 D.R. = 2.50
[ NORMAL ] Iteration 231: k_eff = 1.218585 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 17 D.R. = 1.00
[ NORMAL ] Iteration 232: k_eff = 1.218758 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 17 D.R. = 1.40
[ NORMAL ] Iteration 233: k_eff = 1.218927 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 16 D.R. = 0.71
[ NORMAL ] Iteration 234: k_eff = 1.219092 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 16 D.R. = 1.00
[ NORMAL ] Iteration 235: k_eff = 1.219253 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 16 D.R. = 1.40
[ NORMAL ] Iteration 236: k_eff = 1.219410 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 15 D.R. = 0.57
[ NORMAL ] Iteration 237: k_eff = 1.219563 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 15 D.R. = 0.50
[ NORMAL ] Iteration 238: k_eff = 1.219712 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 14 D.R. = 0.00
[ NORMAL ] Iteration 239: k_eff = 1.219858 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 14 D.R. = -nan
[ NORMAL ] Iteration 240: k_eff = 1.220000 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 14 D.R. = inf
[ NORMAL ] Iteration 241: k_eff = 1.220139 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 13 D.R. = 1.13
[ NORMAL ] Iteration 242: k_eff = 1.220274 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 13 D.R. = 1.11
[ NORMAL ] Iteration 243: k_eff = 1.220407 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 13 D.R. = 1.10
[ NORMAL ] Iteration 244: k_eff = 1.220536 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 12 D.R. = 0.64
[ NORMAL ] Iteration 245: k_eff = 1.220662 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 12 D.R. = 0.43
[ NORMAL ] Iteration 246: k_eff = 1.220784 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 12 D.R. = 0.67
[ NORMAL ] Iteration 247: k_eff = 1.220904 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 1.00
[ NORMAL ] Iteration 248: k_eff = 1.221021 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 3.50
[ NORMAL ] Iteration 249: k_eff = 1.221135 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 1.43
[ NORMAL ] Iteration 250: k_eff = 1.221246 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 11 D.R. = 0.80
[ NORMAL ] Iteration 251: k_eff = 1.221355 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 0.87
[ NORMAL ] Iteration 252: k_eff = 1.221461 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 0.43
[ NORMAL ] Iteration 253: k_eff = 1.221564 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 1.67
[ NORMAL ] Iteration 254: k_eff = 1.221665 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 10 D.R. = 0.40
[ NORMAL ] Iteration 255: k_eff = 1.221763 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = 0.00
[ NORMAL ] Iteration 256: k_eff = 1.221859 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = inf
[ NORMAL ] Iteration 257: k_eff = 1.221953 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = 1.00
[ NORMAL ] Iteration 258: k_eff = 1.222044 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 9 D.R. = 0.40
[ NORMAL ] Iteration 259: k_eff = 1.222133 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 1.50
[ NORMAL ] Iteration 260: k_eff = 1.222220 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 0.00
[ NORMAL ] Iteration 261: k_eff = 1.222305 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = inf
[ NORMAL ] Iteration 262: k_eff = 1.222387 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 1.17
[ NORMAL ] Iteration 263: k_eff = 1.222468 res = 1.839E-07 delta-k (pcm)
[ NORMAL ] ... = 8 D.R. = 2.71
[ NORMAL ] Iteration 264: k_eff = 1.222547 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 0.16
[ NORMAL ] Iteration 265: k_eff = 1.222624 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 1.00
[ NORMAL ] Iteration 266: k_eff = 1.222699 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 0.33
[ NORMAL ] Iteration 267: k_eff = 1.222772 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 6.00
[ NORMAL ] Iteration 268: k_eff = 1.222844 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 7 D.R. = 1.17
[ NORMAL ] Iteration 269: k_eff = 1.222913 res = 1.452E-07 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 2.14
[ NORMAL ] Iteration 270: k_eff = 1.222981 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.40
[ NORMAL ] Iteration 271: k_eff = 1.223048 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.17
[ NORMAL ] Iteration 272: k_eff = 1.223113 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 3.00
[ NORMAL ] Iteration 273: k_eff = 1.223176 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 1.33
[ NORMAL ] Iteration 274: k_eff = 1.223238 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 1.00
[ NORMAL ] Iteration 275: k_eff = 1.223298 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 1.00
[ NORMAL ] Iteration 276: k_eff = 1.223357 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 1.00
[ NORMAL ] Iteration 277: k_eff = 1.223414 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.50
[ NORMAL ] Iteration 278: k_eff = 1.223470 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 1.50
[ NORMAL ] Iteration 279: k_eff = 1.223524 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 1.67
[ NORMAL ] Iteration 280: k_eff = 1.223577 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.20
[ NORMAL ] Iteration 281: k_eff = 1.223629 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 5.00
[ NORMAL ] Iteration 282: k_eff = 1.223680 res = 1.258E-07 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 2.60
[ NORMAL ] Iteration 283: k_eff = 1.223729 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.77
[ NORMAL ] Iteration 284: k_eff = 1.223777 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.70
[ NORMAL ] Iteration 285: k_eff = 1.223824 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 1.00
[ NORMAL ] Iteration 286: k_eff = 1.223870 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 1.00
[ NORMAL ] Iteration 287: k_eff = 1.223915 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.29
[ NORMAL ] Iteration 288: k_eff = 1.223959 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.00
[ NORMAL ] Iteration 289: k_eff = 1.224001 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = inf
[ NORMAL ] Iteration 290: k_eff = 1.224043 res = 1.355E-07 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 2.80
[ NORMAL ] Iteration 291: k_eff = 1.224083 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.57
[ NORMAL ] Iteration 292: k_eff = 1.224123 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.37
[ NORMAL ] Iteration 293: k_eff = 1.224161 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 1.33
[ NORMAL ] Iteration 294: k_eff = 1.224199 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 2.00
[ NORMAL ] Iteration 295: k_eff = 1.224235 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.50
[ NORMAL ] Iteration 296: k_eff = 1.224271 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 1.75
[ NORMAL ] Iteration 297: k_eff = 1.224306 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 1.71
[ NORMAL ] Iteration 298: k_eff = 1.224340 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.08
[ NORMAL ] Iteration 299: k_eff = 1.224373 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 2.00
[ NORMAL ] Iteration 300: k_eff = 1.224406 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 5.50
[ NORMAL ] Iteration 301: k_eff = 1.224438 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.82
[ NORMAL ] Iteration 302: k_eff = 1.224468 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.78
[ NORMAL ] Iteration 303: k_eff = 1.224498 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.71
[ NORMAL ] Iteration 304: k_eff = 1.224528 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.40
[ NORMAL ] Iteration 305: k_eff = 1.224557 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 3.00
[ NORMAL ] Iteration 306: k_eff = 1.224585 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.83
[ NORMAL ] Iteration 307: k_eff = 1.224612 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.20
[ NORMAL ] Iteration 308: k_eff = 1.224639 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 3.00
[ NORMAL ] Iteration 309: k_eff = 1.224665 res = 9.679E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 3.33
[ NORMAL ] Iteration 310: k_eff = 1.224691 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.80
[ NORMAL ] Iteration 311: k_eff = 1.224716 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.50
[ NORMAL ] Iteration 312: k_eff = 1.224740 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.75
[ NORMAL ] Iteration 313: k_eff = 1.224764 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 4.00
[ NORMAL ] Iteration 314: k_eff = 1.224786 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.17
[ NORMAL ] Iteration 315: k_eff = 1.224809 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 1.50
[ NORMAL ] Iteration 316: k_eff = 1.224831 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 3.67
[ NORMAL ] Iteration 317: k_eff = 1.224852 res = 1.161E-07 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 1.09
[ NORMAL ] Iteration 318: k_eff = 1.224873 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.00
[ NORMAL ] Iteration 319: k_eff = 1.224893 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = inf
[ NORMAL ] Iteration 320: k_eff = 1.224913 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.63
[ NORMAL ] Iteration 321: k_eff = 1.224932 res = 6.775E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.40
[ NORMAL ] Iteration 322: k_eff = 1.224951 res = 8.711E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.29
[ NORMAL ] Iteration 323: k_eff = 1.224969 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.11
[ NORMAL ] Iteration 324: k_eff = 1.224987 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 8.00
[ NORMAL ] Iteration 325: k_eff = 1.225005 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.25
[ NORMAL ] Iteration 326: k_eff = 1.225022 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.50
[ NORMAL ] Iteration 327: k_eff = 1.225039 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 2.00
[ NORMAL ] Iteration 328: k_eff = 1.225055 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 3.00
[ NORMAL ] Iteration 329: k_eff = 1.225071 res = 1.065E-07 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.83
[ NORMAL ] Iteration 330: k_eff = 1.225086 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.27
[ NORMAL ] Iteration 331: k_eff = 1.225102 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.00
[ NORMAL ] Iteration 332: k_eff = 1.225116 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.00
[ NORMAL ] Iteration 333: k_eff = 1.225131 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 2.00
[ NORMAL ] Iteration 334: k_eff = 1.225145 res = 4.840E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.83
[ NORMAL ] Iteration 335: k_eff = 1.225159 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.20
[ NORMAL ] Iteration 336: k_eff = 1.225172 res = 5.807E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 6.00
[ NORMAL ] Iteration 337: k_eff = 1.225185 res = 1.355E-07 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 2.33
[ NORMAL ] Iteration 338: k_eff = 1.225198 res = 1.258E-07 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.93
[ NORMAL ] Iteration 339: k_eff = 1.225210 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.00
[ NORMAL ] Iteration 340: k_eff = 1.225222 res = 9.679E-09 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = inf
[ NORMAL ] Iteration 341: k_eff = 1.225233 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 4.00
[ NORMAL ] Iteration 342: k_eff = 1.225245 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.50
[ NORMAL ] Iteration 343: k_eff = 1.225256 res = 0.000E+00 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.00
[ NORMAL ] Iteration 344: k_eff = 1.225267 res = 7.743E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = inf
[ NORMAL ] Iteration 345: k_eff = 1.225278 res = 2.904E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.38
[ NORMAL ] Iteration 346: k_eff = 1.225288 res = 3.872E-08 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 1.33
[ NORMAL ] Iteration 347: k_eff = 1.225298 res = 1.936E-08 delta-k (pcm)
[ NORMAL ] ... = 0 D.R. = 0.50
[27]:
# Print report of keff and bias with OpenMC
openmoc_keff = solver.getKeff()
openmc_keff = sp.k_combined.n
bias = (openmoc_keff - openmc_keff) * 1e5
print('openmc keff = {0:1.6f}'.format(openmc_keff))
print('openmoc keff = {0:1.6f}'.format(openmoc_keff))
print('bias [pcm]: {0:1.1f}'.format(bias))
openmc keff = 1.224484
openmoc keff = 1.225298
bias [pcm]: 81.4
There is a non-trivial bias in both the 2-group and 8-group cases. In the case of a pin cell, one can show that these biases do not converge to <100 pcm with more particle histories. For heterogeneous geometries, additional measures must be taken to address the following three sources of bias:
- Appropriate transport-corrected cross sections
- Spatial discretization of OpenMOC’s mesh
- Constant-in-angle multi-group cross sections
Visualizing MGXS Data¶
It is often insightful to generate visual depictions of multi-group cross sections. There are many different types of plots which may be useful for multi-group cross section visualization, only a few of which will be shown here for enrichment and inspiration.
One particularly useful visualization is a comparison of the continuous-energy and multi-group cross sections for a particular nuclide and reaction type. We illustrate one option for generating such plots with the use of the openmc.plotter
module to plot continuous-energy cross sections from the openly available cross section library distributed by NNDC.
The MGXS data can also be plotted using the openmc.plot_xs command, however we will do this manually here to show how the openmc.Mgxs.get_xs method can be used to obtain data.
[28]:
# Create a figure of the U-235 continuous-energy fission cross section
fig = openmc.plot_xs('U235', ['fission'])
# Get the axis to use for plotting the MGXS
ax = fig.gca()
# Extract energy group bounds and MGXS values to plot
fission = xs_library[fuel_cell.id]['fission']
energy_groups = fission.energy_groups
x = energy_groups.group_edges
y = fission.get_xs(nuclides=['U235'], order_groups='decreasing', xs_type='micro')
y = np.squeeze(y)
# Fix low energy bound
x[0] = 1.e-5
# Extend the mgxs values array for matplotlib's step plot
y = np.insert(y, 0, y[0])
# Create a step plot for the MGXS
ax.plot(x, y, drawstyle='steps', color='r', linewidth=3)
ax.set_title('U-235 Fission Cross Section')
ax.legend(['Continuous', 'Multi-Group'])
ax.set_xlim((x.min(), x.max()))
[28]:
(1e-05, 20000000.0)

Another useful type of illustration is scattering matrix sparsity structures. First, we extract Pandas DataFrames
for the H-1 and O-16 scattering matrices.
[29]:
# Construct a Pandas DataFrame for the microscopic nu-scattering matrix
nuscatter = xs_library[moderator_cell.id]['nu-scatter']
df = nuscatter.get_pandas_dataframe(xs_type='micro')
# Slice DataFrame in two for each nuclide's mean values
h1 = df[df['nuclide'] == 'H1']['mean']
o16 = df[df['nuclide'] == 'O16']['mean']
# Cast DataFrames as NumPy arrays
h1 = h1.values
o16 = o16.values
# Reshape arrays to 2D matrix for plotting
h1.shape = (fine_groups.num_groups, fine_groups.num_groups)
o16.shape = (fine_groups.num_groups, fine_groups.num_groups)
Matplotlib’s imshow
routine can be used to plot the matrices to illustrate their sparsity structures.
[30]:
# Create plot of the H-1 scattering matrix
fig = plt.subplot(121)
fig.imshow(h1, interpolation='nearest', cmap='jet')
plt.title('H-1 Scattering Matrix')
plt.xlabel('Group Out')
plt.ylabel('Group In')
# Create plot of the O-16 scattering matrix
fig2 = plt.subplot(122)
fig2.imshow(o16, interpolation='nearest', cmap='jet')
plt.title('O-16 Scattering Matrix')
plt.xlabel('Group Out')
plt.ylabel('Group In')
# Show the plot on screen
plt.show()

Multigroup Cross Section Generation Part III: Libraries¶
This IPython Notebook illustrates the use of the ``openmc.mgxs.Library`` class. The Library
class is designed to automate the calculation of multi-group cross sections for use cases with one or more domains, cross section types, and/or nuclides. In particular, this Notebook illustrates the following features:
- Calculation of multi-group cross sections for a fuel assembly
- Automated creation, manipulation and storage of
MGXS
with ``openmc.mgxs.Library`` - Validation of multi-group cross sections with `OpenMOC <https://mit-crpg.github.io/OpenMOC/>`__
- Steady-state pin-by-pin fission rates comparison between OpenMC and OpenMOC
Note: This Notebook was created using OpenMOC to verify the multi-group cross-sections generated by OpenMC. You must install OpenMOC on your system to run this Notebook in its entirety. In addition, this Notebook illustrates the use of Pandas DataFrames
to containerize multi-group cross section data.
Generate Input Files¶
[1]:
import math
import pickle
from IPython.display import Image
import matplotlib.pyplot as plt
import numpy as np
import openmc
import openmc.mgxs
from openmc.openmoc_compatible import get_openmoc_geometry
import openmoc
import openmoc.process
from openmoc.materialize import load_openmc_mgxs_lib
%matplotlib inline
First we need to define materials that will be used in the problem. We’ll create three materials for the fuel, water, and cladding of the fuel pins.
[2]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
water.add_nuclide('B10', 8.0042e-6)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our three materials, we can now create a Materials
object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials object
materials_file = openmc.Materials([fuel, water, zircaloy])
# Export to "materials.xml"
materials_file.export_to_xml()
Now let’s move on to the geometry. This problem will be a square array of fuel pins and control rod guide tubes for which we can use OpenMC’s lattice/universe feature. The basic universe will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces for fuel and clad, as well as the outer bounding surfaces of the problem.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, r=0.45720)
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='reflective')
min_y = openmc.YPlane(y0=-10.71, boundary_type='reflective')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10., boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10., boundary_type='reflective')
With the surfaces defined, we can now construct a fuel pin cell from cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
fuel_pin_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
fuel_pin_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
fuel_pin_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
fuel_pin_universe.add_cell(moderator_cell)
Likewise, we can construct a control rod guide tube with the same surfaces.
[6]:
# Create a Universe to encapsulate a control rod guide tube
guide_tube_universe = openmc.Universe(name='Guide Tube')
# Create guide tube Cell
guide_tube_cell = openmc.Cell(name='Guide Tube Water')
guide_tube_cell.fill = water
guide_tube_cell.region = -fuel_outer_radius
guide_tube_universe.add_cell(guide_tube_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='Guide Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
guide_tube_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='Guide Tube Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
guide_tube_universe.add_cell(moderator_cell)
Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch.
[7]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel Assembly')
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2
Next, we create a NumPy array of fuel pin and guide tube universes for the lattice.
[8]:
# Create array indices for guide tube locations in lattice
template_x = np.array([5, 8, 11, 3, 13, 2, 5, 8, 11, 14, 2, 5, 8,
11, 14, 2, 5, 8, 11, 14, 3, 13, 5, 8, 11])
template_y = np.array([2, 2, 2, 3, 3, 5, 5, 5, 5, 5, 8, 8, 8, 8,
8, 11, 11, 11, 11, 11, 13, 13, 14, 14, 14])
# Initialize an empty 17x17 array of the lattice universes
universes = np.empty((17, 17), dtype=openmc.Universe)
# Fill the array with the fuel pin and guide tube universes
universes[:,:] = fuel_pin_universe
universes[template_x, template_y] = guide_tube_universe
# Store the array of universes in the lattice
assembly.universes = universes
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the assembly and then assign it to the root universe.
[9]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = assembly
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(universe_id=0, name='root universe')
root_universe.add_cell(root_cell)
We now must create a geometry that is assigned a root universe and export it to XML.
[10]:
# Create Geometry and set root Universe
geometry = openmc.Geometry(root_universe)
[11]:
# Export to "geometry.xml"
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 2500 particles.
[12]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 10000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': False}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Let us also create a plot to verify that our fuel assembly geometry was created successfully.
[13]:
# Instantiate a Plot
plot = openmc.Plot.from_geometry(geometry)
plot.pixels = (250, 250)
plot.color_by = 'material'
plot.to_ipython_image()
[13]:

As we can see from the plot, we have a nice array of fuel and guide tube pin cells with fuel, cladding, and water!
Create an MGXS Library¶
Now we are ready to generate multi-group cross sections! First, let’s define a 2-group structure using the built-in EnergyGroups
class.
[14]:
# Instantiate a 2-group EnergyGroups object
groups = openmc.mgxs.EnergyGroups()
groups.group_edges = np.array([0., 0.625, 20.0e6])
Next, we will instantiate an openmc.mgxs.Library
for the energy groups with the fuel assembly geometry.
[15]:
# Initialize a 2-group MGXS Library for OpenMOC
mgxs_lib = openmc.mgxs.Library(geometry)
mgxs_lib.energy_groups = groups
Now, we must specify to the Library
which types of cross sections to compute. In particular, the following are the multi-group cross section MGXS
subclasses that are mapped to string codes accepted by the Library
class:
TotalXS
("total"
)TransportXS
("transport"
or"nu-transport
withnu
set toTrue
)AbsorptionXS
("absorption"
)CaptureXS
("capture"
)FissionXS
("fission"
or"nu-fission"
withnu
set toTrue
)KappaFissionXS
("kappa-fission"
)ScatterXS
("scatter"
or"nu-scatter"
withnu
set toTrue
)ScatterMatrixXS
("scatter matrix"
or"nu-scatter matrix"
withnu
set toTrue
)Chi
("chi"
)ChiPrompt
("chi prompt"
)InverseVelocity
("inverse-velocity"
)PromptNuFissionXS
("prompt-nu-fission"
)DelayedNuFissionXS
("delayed-nu-fission"
)ChiDelayed
("chi-delayed"
)Beta
("beta"
)
In this case, let’s create the multi-group cross sections needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we will define "nu-transport"
, "nu-fission"
, '"fission"
, "nu-scatter matrix"
and "chi"
cross sections for our Library
.
Note: A variety of different approximate transport-corrected total multi-group cross sections (and corresponding scattering matrices) can be found in the literature. At the present time, the openmc.mgxs
module only supports the "P0"
transport correction. This correction can be turned on and off through the boolean Library.correction
property which may take values of "P0"
(default) or None
.
[16]:
# Specify multi-group cross section types to compute
mgxs_lib.mgxs_types = ['nu-transport', 'nu-fission', 'fission', 'nu-scatter matrix', 'chi']
Now we must specify the type of domain over which we would like the Library
to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the Library
supports "material"
, "cell"
, "universe"
, and "mesh"
domain types. We will use a "cell"
domain type here to compute cross sections in each of the cells in the fuel assembly geometry.
Note: By default, the Library
class will instantiate MGXS
objects for each and every domain (material, cell or universe) in the geometry of interest. However, one may specify a subset of these domains to the Library.domains
property. In our case, we wish to compute multi-group cross sections in each and every cell since they will be needed in our downstream OpenMOC calculation on the identical combinatorial geometry mesh.
[17]:
# Specify a "cell" domain type for the cross section tally filters
mgxs_lib.domain_type = 'cell'
# Specify the cell domains over which to compute multi-group cross sections
mgxs_lib.domains = geometry.get_all_material_cells().values()
We can easily instruct the Library
to compute multi-group cross sections on a nuclide-by-nuclide basis with the boolean Library.by_nuclide
property. By default, by_nuclide
is set to False
, but we will set it to True
here.
[18]:
# Compute cross sections on a nuclide-by-nuclide basis
mgxs_lib.by_nuclide = True
Lastly, we use the Library
to construct the tallies needed to compute all of the requested multi-group cross sections in each domain and nuclide.
[19]:
# Construct all tallies needed for the multi-group cross section library
mgxs_lib.build_library()
The tallies can now be export to a “tallies.xml” input file for OpenMC.
NOTE: At this point the Library
has constructed nearly 100 distinct Tally
objects. The overhead to tally in OpenMC scales as \(O(N)\) for \(N\) tallies, which can become a bottleneck for large tally datasets. To compensate for this, the Python API’s Tally
, Filter
and Tallies
classes allow for the smart merging of tallies when possible. The Library
class supports this runtime optimization with the use of the optional merge
paramter (False
by default)
for the Library.add_to_tallies_file(...)
method, as shown below.
[20]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
mgxs_lib.add_to_tallies_file(tallies_file, merge=True)
In addition, we instantiate a fission rate mesh tally to compare with OpenMOC.
[21]:
# Instantiate a tally Mesh
mesh = openmc.RegularMesh(mesh_id=1)
mesh.dimension = [17, 17]
mesh.lower_left = [-10.71, -10.71]
mesh.upper_right = [+10.71, +10.71]
# Instantiate tally Filter
mesh_filter = openmc.MeshFilter(mesh)
# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter]
tally.scores = ['fission', 'nu-fission']
# Add tally to collection
tallies_file.append(tally)
[22]:
# Export all tallies to a "tallies.xml" file
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=126.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=3.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=4.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=96.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=15.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=114.
warn(msg, IDWarning)
[23]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-19 07:12:55
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading B10 from /opt/data/hdf5/nndc_hdf5_v15/B10.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.03784
2/1 1.02297
3/1 1.02244
4/1 1.02344
5/1 1.02057
6/1 1.04077
7/1 1.00775
8/1 1.03892
9/1 1.01606
10/1 1.02209
11/1 1.03259
12/1 1.03331 1.03295 +/- 0.00036
13/1 1.02027 1.02872 +/- 0.00423
14/1 1.03901 1.03130 +/- 0.00395
15/1 1.02000 1.02904 +/- 0.00380
16/1 1.04469 1.03164 +/- 0.00405
17/1 1.01862 1.02978 +/- 0.00390
18/1 1.03265 1.03014 +/- 0.00340
19/1 1.00489 1.02734 +/- 0.00410
20/1 1.04533 1.02914 +/- 0.00409
21/1 1.01534 1.02788 +/- 0.00390
22/1 1.02204 1.02739 +/- 0.00360
23/1 1.02181 1.02696 +/- 0.00334
24/1 0.99207 1.02447 +/- 0.00397
25/1 1.03041 1.02487 +/- 0.00372
26/1 1.03652 1.02560 +/- 0.00355
27/1 1.03793 1.02632 +/- 0.00341
28/1 1.02099 1.02603 +/- 0.00323
29/1 1.01953 1.02568 +/- 0.00308
30/1 1.01690 1.02525 +/- 0.00295
31/1 1.01938 1.02497 +/- 0.00282
32/1 1.01800 1.02465 +/- 0.00271
33/1 1.01598 1.02427 +/- 0.00262
34/1 1.01735 1.02398 +/- 0.00252
35/1 1.01080 1.02346 +/- 0.00247
36/1 1.01267 1.02304 +/- 0.00241
37/1 1.01907 1.02289 +/- 0.00233
38/1 1.02333 1.02291 +/- 0.00224
39/1 1.01516 1.02264 +/- 0.00218
40/1 1.02797 1.02282 +/- 0.00211
41/1 1.03949 1.02336 +/- 0.00211
42/1 1.01456 1.02308 +/- 0.00207
43/1 1.02376 1.02310 +/- 0.00200
44/1 1.01917 1.02299 +/- 0.00195
45/1 1.01631 1.02280 +/- 0.00190
46/1 1.02381 1.02282 +/- 0.00185
47/1 1.04002 1.02329 +/- 0.00185
48/1 1.01059 1.02296 +/- 0.00184
49/1 1.02647 1.02305 +/- 0.00179
50/1 1.02451 1.02308 +/- 0.00175
Creating state point statepoint.50.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 5.7635e-01 seconds
Reading cross sections = 5.4002e-01 seconds
Total time in simulation = 7.0174e+01 seconds
Time in transport only = 6.9687e+01 seconds
Time in inactive batches = 7.1832e+00 seconds
Time in active batches = 6.2991e+01 seconds
Time synchronizing fission bank = 3.9991e-02 seconds
Sampling source sites = 3.4633e-02 seconds
SEND/RECV source sites = 5.2616e-03 seconds
Time accumulating tallies = 4.9801e-04 seconds
Total time for finalization = 1.3501e-05 seconds
Total time elapsed = 7.0791e+01 seconds
Calculation Rate (inactive) = 13921.3 particles/second
Calculation Rate (active) = 6350.11 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.02434 +/- 0.00173
k-effective (Track-length) = 1.02308 +/- 0.00175
k-effective (Absorption) = 1.02494 +/- 0.00175
Combined k-effective = 1.02408 +/- 0.00144
Leakage Fraction = 0.00000 +/- 0.00000
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a StatePoint
object.
[24]:
# Load the last statepoint file
sp = openmc.StatePoint('statepoint.50.h5')
The statepoint is now ready to be analyzed by the Library
. We simply have to load the tallies from the statepoint into the Library
and our MGXS
objects will compute the cross sections for us under-the-hood.
[25]:
# Initialize MGXS Library with OpenMC statepoint data
mgxs_lib.load_from_statepoint(sp)
Voila! Our multi-group cross sections are now ready to rock ‘n roll!
Extracting and Storing MGXS Data¶
The Library
supports a rich API to automate a variety of tasks, including multi-group cross section data retrieval and storage. We will highlight a few of these features here. First, the Library.get_mgxs(...)
method allows one to extract an MGXS
object from the Library
for a particular domain and cross section type. The following cell illustrates how one may extract the NuFissionXS
object for the fuel cell.
Note: The MGXS.get_mgxs(...)
method will accept either the domain or the integer domain ID of interest.
[26]:
# Retrieve the NuFissionXS object for the fuel cell from the library
fuel_mgxs = mgxs_lib.get_mgxs(fuel_cell, 'nu-fission')
The NuFissionXS
object supports all of the methods described previously in the openmc.mgxs
tutorials, such as Pandas DataFrames
: Note that since so few histories were simulated, we should expect a few division-by-error errors as some tallies have not yet scored any results.
[27]:
df = fuel_mgxs.get_pandas_dataframe()
df
[27]:
cell | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|
3 | 1 | 1 | U235 | 8.089079e-03 | 1.461462e-05 |
4 | 1 | 1 | U238 | 7.358661e-03 | 2.302063e-05 |
5 | 1 | 1 | O16 | 0.000000e+00 | 0.000000e+00 |
0 | 1 | 2 | U235 | 3.617174e-01 | 9.467633e-04 |
1 | 1 | 2 | U238 | 6.744743e-07 | 1.750450e-09 |
2 | 1 | 2 | O16 | 0.000000e+00 | 0.000000e+00 |
Similarly, we can use the MGXS.print_xs(...)
method to view a string representation of the multi-group cross section data.
[28]:
fuel_mgxs.print_xs()
Multi-Group XS
Reaction Type = nu-fission
Domain Type = cell
Domain ID = 1
Nuclide = U235
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 8.09e-03 +/- 1.81e-01%
Group 2 [0.0 - 0.625 eV]: 3.62e-01 +/- 2.62e-01%
Nuclide = U238
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 7.36e-03 +/- 3.13e-01%
Group 2 [0.0 - 0.625 eV]: 6.74e-07 +/- 2.60e-01%
Nuclide = O16
Cross Sections [cm^-1]:
Group 1 [0.625 - 20000000.0eV]: 0.00e+00 +/- 0.00e+00%
Group 2 [0.0 - 0.625 eV]: 0.00e+00 +/- 0.00e+00%
/home/romano/openmc/openmc/tallies.py:1269: RuntimeWarning: invalid value encountered in true_divide
data = self.std_dev[indices] / self.mean[indices]
One can export the entire Library
to HDF5 with the Library.build_hdf5_store(...)
method as follows:
[29]:
# Store the cross section data in an "mgxs/mgxs.h5" HDF5 binary file
mgxs_lib.build_hdf5_store(filename='mgxs.h5', directory='mgxs')
The HDF5 store will contain the numerical multi-group cross section data indexed by domain, nuclide and cross section type. Some data workflows may be optimized by storing and retrieving binary representations of the MGXS
objects in the Library
. This feature is supported through the Library.dump_to_file(...)
and Library.load_from_file(...)
routines which use Python’s `pickle
<https://docs.python.org/3/library/pickle.html>`__ module. This is illustrated as follows.
[30]:
# Store a Library and its MGXS objects in a pickled binary file "mgxs/mgxs.pkl"
mgxs_lib.dump_to_file(filename='mgxs', directory='mgxs')
[31]:
# Instantiate a new MGXS Library from the pickled binary file "mgxs/mgxs.pkl"
mgxs_lib = openmc.mgxs.Library.load_from_file(filename='mgxs', directory='mgxs')
The Library
class may be used to leverage the energy condensation features supported by the MGXS
class. In particular, one can use the Library.get_condensed_library(...)
with a coarse group structure which is a subset of the original “fine” group structure as shown below.
[32]:
# Create a 1-group structure
coarse_groups = openmc.mgxs.EnergyGroups(group_edges=[0., 20.0e6])
# Create a new MGXS Library on the coarse 1-group structure
coarse_mgxs_lib = mgxs_lib.get_condensed_library(coarse_groups)
[33]:
# Retrieve the NuFissionXS object for the fuel cell from the 1-group library
coarse_fuel_mgxs = coarse_mgxs_lib.get_mgxs(fuel_cell, 'nu-fission')
# Show the Pandas DataFrame for the 1-group MGXS
coarse_fuel_mgxs.get_pandas_dataframe()
[33]:
cell | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|
0 | 1 | 1 | U235 | 0.074556 | 0.000144 |
1 | 1 | 1 | U238 | 0.005976 | 0.000019 |
2 | 1 | 1 | O16 | 0.000000 | 0.000000 |
Verification with OpenMOC¶
Of course it is always a good idea to verify that one’s cross sections are accurate. We can easily do so here with the deterministic transport code OpenMOC. We first construct an equivalent OpenMOC geometry.
[34]:
# Create an OpenMOC Geometry from the OpenMC Geometry
openmoc_geometry = get_openmoc_geometry(mgxs_lib.geometry)
Now, we can inject the multi-group cross sections into the equivalent fuel assembly OpenMOC geometry. The openmoc.materialize
module supports the loading of Library
objects from OpenMC as illustrated below.
[35]:
# Load the library into the OpenMOC geometry
materials = load_openmc_mgxs_lib(mgxs_lib, openmoc_geometry)
We are now ready to run OpenMOC to verify our cross-sections from OpenMC.
[36]:
# Generate tracks for OpenMOC
track_generator = openmoc.TrackGenerator(openmoc_geometry, num_azim=32, azim_spacing=0.1)
track_generator.generateTracks()
# Run OpenMOC
solver = openmoc.CPUSolver(track_generator)
solver.computeEigenvalue()
[ NORMAL ] Initializing a default angular quadrature...
[ NORMAL ] Initializing 2D tracks...
[ NORMAL ] Initializing 2D tracks reflections...
[ NORMAL ] Initializing 2D tracks array...
[ NORMAL ] Ray tracing for 2D track segmentation...
[ WARNING ] The Geometry was set with non-infinite z-boundaries and supplied
[ WARNING ] ... to a 2D TrackGenerator. The min-z boundary was set to -10.00
[ WARNING ] ... and the max-z boundary was set to 10.00. Z-boundaries are
[ WARNING ] ... assumed to be infinite in 2D TrackGenerators.
[ NORMAL ] Progress Segmenting 2D tracks: 0.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 10.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 20.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 30.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 40.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 50.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 60.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 70.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 80.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 90.02 %
[ NORMAL ] Progress Segmenting 2D tracks: 100.00 %
[ NORMAL ] Initializing FSR lookup vectors
[ NORMAL ] Total number of FSRs 867
[ RESULT ] Total Track Generation & Segmentation Time...........4.2139E-01 sec
[ NORMAL ] Initializing MOC eigenvalue solver...
[ NORMAL ] Initializing solver arrays...
[ NORMAL ] Centering segments around FSR centroid...
[ NORMAL ] Max boundary angular flux storage per domain = 0.42 MB
[ NORMAL ] Max scalar flux storage per domain = 0.01 MB
[ NORMAL ] Max source storage per domain = 0.01 MB
[ NORMAL ] Number of azimuthal angles = 32
[ NORMAL ] Azimuthal ray spacing = 0.100000
[ NORMAL ] Number of polar angles = 6
[ NORMAL ] Source type = Flat
[ NORMAL ] MOC transport undamped
[ NORMAL ] CMFD acceleration: OFF
[ NORMAL ] Using 1 threads
[ NORMAL ] Computing the eigenvalue...
[ NORMAL ] Iteration 0: k_eff = 0.823216 res = 9.828E-02 delta-k (pcm) =
[ NORMAL ] ... -17678 D.R. = 0.10
[ NORMAL ] Iteration 1: k_eff = 0.779788 res = 4.642E-02 delta-k (pcm) =
[ NORMAL ] ... -4342 D.R. = 0.47
[ NORMAL ] Iteration 2: k_eff = 0.738779 res = 9.633E-03 delta-k (pcm) =
[ NORMAL ] ... -4100 D.R. = 0.21
[ NORMAL ] Iteration 3: k_eff = 0.710046 res = 8.556E-03 delta-k (pcm) =
[ NORMAL ] ... -2873 D.R. = 0.89
[ NORMAL ] Iteration 4: k_eff = 0.688781 res = 5.190E-03 delta-k (pcm) =
[ NORMAL ] ... -2126 D.R. = 0.61
[ NORMAL ] Iteration 5: k_eff = 0.674128 res = 3.585E-03 delta-k (pcm) =
[ NORMAL ] ... -1465 D.R. = 0.69
[ NORMAL ] Iteration 6: k_eff = 0.664928 res = 2.516E-03 delta-k (pcm) =
[ NORMAL ] ... -919 D.R. = 0.70
[ NORMAL ] Iteration 7: k_eff = 0.660304 res = 1.866E-03 delta-k (pcm) =
[ NORMAL ] ... -462 D.R. = 0.74
[ NORMAL ] Iteration 8: k_eff = 0.659481 res = 1.471E-03 delta-k (pcm) =
[ NORMAL ] ... -82 D.R. = 0.79
[ NORMAL ] Iteration 9: k_eff = 0.661799 res = 1.248E-03 delta-k (pcm) =
[ NORMAL ] ... 231 D.R. = 0.85
[ NORMAL ] Iteration 10: k_eff = 0.666690 res = 1.123E-03 delta-k (pcm) =
[ NORMAL ] ... 489 D.R. = 0.90
[ NORMAL ] Iteration 11: k_eff = 0.673664 res = 1.049E-03 delta-k (pcm) =
[ NORMAL ] ... 697 D.R. = 0.93
[ NORMAL ] Iteration 12: k_eff = 0.682301 res = 1.001E-03 delta-k (pcm) =
[ NORMAL ] ... 863 D.R. = 0.95
[ NORMAL ] Iteration 13: k_eff = 0.692239 res = 9.638E-04 delta-k (pcm) =
[ NORMAL ] ... 993 D.R. = 0.96
[ NORMAL ] Iteration 14: k_eff = 0.703171 res = 9.329E-04 delta-k (pcm) =
[ NORMAL ] ... 1093 D.R. = 0.97
[ NORMAL ] Iteration 15: k_eff = 0.714835 res = 9.055E-04 delta-k (pcm) =
[ NORMAL ] ... 1166 D.R. = 0.97
[ NORMAL ] Iteration 16: k_eff = 0.727008 res = 8.803E-04 delta-k (pcm) =
[ NORMAL ] ... 1217 D.R. = 0.97
[ NORMAL ] Iteration 17: k_eff = 0.739503 res = 8.566E-04 delta-k (pcm) =
[ NORMAL ] ... 1249 D.R. = 0.97
[ NORMAL ] Iteration 18: k_eff = 0.752162 res = 8.335E-04 delta-k (pcm) =
[ NORMAL ] ... 1265 D.R. = 0.97
[ NORMAL ] Iteration 19: k_eff = 0.764855 res = 8.108E-04 delta-k (pcm) =
[ NORMAL ] ... 1269 D.R. = 0.97
[ NORMAL ] Iteration 20: k_eff = 0.777472 res = 7.879E-04 delta-k (pcm) =
[ NORMAL ] ... 1261 D.R. = 0.97
[ NORMAL ] Iteration 21: k_eff = 0.789924 res = 7.647E-04 delta-k (pcm) =
[ NORMAL ] ... 1245 D.R. = 0.97
[ NORMAL ] Iteration 22: k_eff = 0.802140 res = 7.410E-04 delta-k (pcm) =
[ NORMAL ] ... 1221 D.R. = 0.97
[ NORMAL ] Iteration 23: k_eff = 0.814061 res = 7.168E-04 delta-k (pcm) =
[ NORMAL ] ... 1192 D.R. = 0.97
[ NORMAL ] Iteration 24: k_eff = 0.825643 res = 6.922E-04 delta-k (pcm) =
[ NORMAL ] ... 1158 D.R. = 0.97
[ NORMAL ] Iteration 25: k_eff = 0.836850 res = 6.672E-04 delta-k (pcm) =
[ NORMAL ] ... 1120 D.R. = 0.96
[ NORMAL ] Iteration 26: k_eff = 0.847658 res = 6.419E-04 delta-k (pcm) =
[ NORMAL ] ... 1080 D.R. = 0.96
[ NORMAL ] Iteration 27: k_eff = 0.858047 res = 6.165E-04 delta-k (pcm) =
[ NORMAL ] ... 1038 D.R. = 0.96
[ NORMAL ] Iteration 28: k_eff = 0.868008 res = 5.911E-04 delta-k (pcm) =
[ NORMAL ] ... 996 D.R. = 0.96
[ NORMAL ] Iteration 29: k_eff = 0.877535 res = 5.658E-04 delta-k (pcm) =
[ NORMAL ] ... 952 D.R. = 0.96
[ NORMAL ] Iteration 30: k_eff = 0.886625 res = 5.409E-04 delta-k (pcm) =
[ NORMAL ] ... 909 D.R. = 0.96
[ NORMAL ] Iteration 31: k_eff = 0.895281 res = 5.163E-04 delta-k (pcm) =
[ NORMAL ] ... 865 D.R. = 0.95
[ NORMAL ] Iteration 32: k_eff = 0.903509 res = 4.921E-04 delta-k (pcm) =
[ NORMAL ] ... 822 D.R. = 0.95
[ NORMAL ] Iteration 33: k_eff = 0.911317 res = 4.685E-04 delta-k (pcm) =
[ NORMAL ] ... 780 D.R. = 0.95
[ NORMAL ] Iteration 34: k_eff = 0.918715 res = 4.456E-04 delta-k (pcm) =
[ NORMAL ] ... 739 D.R. = 0.95
[ NORMAL ] Iteration 35: k_eff = 0.925715 res = 4.232E-04 delta-k (pcm) =
[ NORMAL ] ... 699 D.R. = 0.95
[ NORMAL ] Iteration 36: k_eff = 0.932329 res = 4.016E-04 delta-k (pcm) =
[ NORMAL ] ... 661 D.R. = 0.95
[ NORMAL ] Iteration 37: k_eff = 0.938571 res = 3.807E-04 delta-k (pcm) =
[ NORMAL ] ... 624 D.R. = 0.95
[ NORMAL ] Iteration 38: k_eff = 0.944455 res = 3.606E-04 delta-k (pcm) =
[ NORMAL ] ... 588 D.R. = 0.95
[ NORMAL ] Iteration 39: k_eff = 0.949996 res = 3.413E-04 delta-k (pcm) =
[ NORMAL ] ... 554 D.R. = 0.95
[ NORMAL ] Iteration 40: k_eff = 0.955210 res = 3.227E-04 delta-k (pcm) =
[ NORMAL ] ... 521 D.R. = 0.95
[ NORMAL ] Iteration 41: k_eff = 0.960110 res = 3.049E-04 delta-k (pcm) =
[ NORMAL ] ... 490 D.R. = 0.94
[ NORMAL ] Iteration 42: k_eff = 0.964713 res = 2.880E-04 delta-k (pcm) =
[ NORMAL ] ... 460 D.R. = 0.94
[ NORMAL ] Iteration 43: k_eff = 0.969032 res = 2.717E-04 delta-k (pcm) =
[ NORMAL ] ... 431 D.R. = 0.94
[ NORMAL ] Iteration 44: k_eff = 0.973082 res = 2.562E-04 delta-k (pcm) =
[ NORMAL ] ... 405 D.R. = 0.94
[ NORMAL ] Iteration 45: k_eff = 0.976877 res = 2.414E-04 delta-k (pcm) =
[ NORMAL ] ... 379 D.R. = 0.94
[ NORMAL ] Iteration 46: k_eff = 0.980431 res = 2.274E-04 delta-k (pcm) =
[ NORMAL ] ... 355 D.R. = 0.94
[ NORMAL ] Iteration 47: k_eff = 0.983757 res = 2.140E-04 delta-k (pcm) =
[ NORMAL ] ... 332 D.R. = 0.94
[ NORMAL ] Iteration 48: k_eff = 0.986869 res = 2.014E-04 delta-k (pcm) =
[ NORMAL ] ... 311 D.R. = 0.94
[ NORMAL ] Iteration 49: k_eff = 0.989777 res = 1.894E-04 delta-k (pcm) =
[ NORMAL ] ... 290 D.R. = 0.94
[ NORMAL ] Iteration 50: k_eff = 0.992495 res = 1.780E-04 delta-k (pcm) =
[ NORMAL ] ... 271 D.R. = 0.94
[ NORMAL ] Iteration 51: k_eff = 0.995033 res = 1.672E-04 delta-k (pcm) =
[ NORMAL ] ... 253 D.R. = 0.94
[ NORMAL ] Iteration 52: k_eff = 0.997402 res = 1.569E-04 delta-k (pcm) =
[ NORMAL ] ... 236 D.R. = 0.94
[ NORMAL ] Iteration 53: k_eff = 0.999613 res = 1.473E-04 delta-k (pcm) =
[ NORMAL ] ... 221 D.R. = 0.94
[ NORMAL ] Iteration 54: k_eff = 1.001675 res = 1.382E-04 delta-k (pcm) =
[ NORMAL ] ... 206 D.R. = 0.94
[ NORMAL ] Iteration 55: k_eff = 1.003597 res = 1.296E-04 delta-k (pcm) =
[ NORMAL ] ... 192 D.R. = 0.94
[ NORMAL ] Iteration 56: k_eff = 1.005388 res = 1.215E-04 delta-k (pcm) =
[ NORMAL ] ... 179 D.R. = 0.94
[ NORMAL ] Iteration 57: k_eff = 1.007057 res = 1.138E-04 delta-k (pcm) =
[ NORMAL ] ... 166 D.R. = 0.94
[ NORMAL ] Iteration 58: k_eff = 1.008612 res = 1.066E-04 delta-k (pcm) =
[ NORMAL ] ... 155 D.R. = 0.94
[ NORMAL ] Iteration 59: k_eff = 1.010059 res = 9.980E-05 delta-k (pcm) =
[ NORMAL ] ... 144 D.R. = 0.94
[ NORMAL ] Iteration 60: k_eff = 1.011406 res = 9.342E-05 delta-k (pcm) =
[ NORMAL ] ... 134 D.R. = 0.94
[ NORMAL ] Iteration 61: k_eff = 1.012659 res = 8.740E-05 delta-k (pcm) =
[ NORMAL ] ... 125 D.R. = 0.94
[ NORMAL ] Iteration 62: k_eff = 1.013825 res = 8.175E-05 delta-k (pcm) =
[ NORMAL ] ... 116 D.R. = 0.94
[ NORMAL ] Iteration 63: k_eff = 1.014909 res = 7.642E-05 delta-k (pcm) =
[ NORMAL ] ... 108 D.R. = 0.93
[ NORMAL ] Iteration 64: k_eff = 1.015917 res = 7.142E-05 delta-k (pcm) =
[ NORMAL ] ... 100 D.R. = 0.93
[ NORMAL ] Iteration 65: k_eff = 1.016853 res = 6.675E-05 delta-k (pcm) =
[ NORMAL ] ... 93 D.R. = 0.93
[ NORMAL ] Iteration 66: k_eff = 1.017724 res = 6.235E-05 delta-k (pcm) =
[ NORMAL ] ... 87 D.R. = 0.93
[ NORMAL ] Iteration 67: k_eff = 1.018533 res = 5.822E-05 delta-k (pcm) =
[ NORMAL ] ... 80 D.R. = 0.93
[ NORMAL ] Iteration 68: k_eff = 1.019284 res = 5.436E-05 delta-k (pcm) =
[ NORMAL ] ... 75 D.R. = 0.93
[ NORMAL ] Iteration 69: k_eff = 1.019982 res = 5.074E-05 delta-k (pcm) =
[ NORMAL ] ... 69 D.R. = 0.93
[ NORMAL ] Iteration 70: k_eff = 1.020630 res = 4.734E-05 delta-k (pcm) =
[ NORMAL ] ... 64 D.R. = 0.93
[ NORMAL ] Iteration 71: k_eff = 1.021232 res = 4.417E-05 delta-k (pcm) =
[ NORMAL ] ... 60 D.R. = 0.93
[ NORMAL ] Iteration 72: k_eff = 1.021790 res = 4.119E-05 delta-k (pcm) =
[ NORMAL ] ... 55 D.R. = 0.93
[ NORMAL ] Iteration 73: k_eff = 1.022308 res = 3.841E-05 delta-k (pcm) =
[ NORMAL ] ... 51 D.R. = 0.93
[ NORMAL ] Iteration 74: k_eff = 1.022789 res = 3.579E-05 delta-k (pcm) =
[ NORMAL ] ... 48 D.R. = 0.93
[ NORMAL ] Iteration 75: k_eff = 1.023235 res = 3.336E-05 delta-k (pcm) =
[ NORMAL ] ... 44 D.R. = 0.93
[ NORMAL ] Iteration 76: k_eff = 1.023648 res = 3.107E-05 delta-k (pcm) =
[ NORMAL ] ... 41 D.R. = 0.93
[ NORMAL ] Iteration 77: k_eff = 1.024032 res = 2.897E-05 delta-k (pcm) =
[ NORMAL ] ... 38 D.R. = 0.93
[ NORMAL ] Iteration 78: k_eff = 1.024388 res = 2.696E-05 delta-k (pcm) =
[ NORMAL ] ... 35 D.R. = 0.93
[ NORMAL ] Iteration 79: k_eff = 1.024718 res = 2.510E-05 delta-k (pcm) =
[ NORMAL ] ... 32 D.R. = 0.93
[ NORMAL ] Iteration 80: k_eff = 1.025024 res = 2.338E-05 delta-k (pcm) =
[ NORMAL ] ... 30 D.R. = 0.93
[ NORMAL ] Iteration 81: k_eff = 1.025307 res = 2.175E-05 delta-k (pcm) =
[ NORMAL ] ... 28 D.R. = 0.93
[ NORMAL ] Iteration 82: k_eff = 1.025570 res = 2.025E-05 delta-k (pcm) =
[ NORMAL ] ... 26 D.R. = 0.93
[ NORMAL ] Iteration 83: k_eff = 1.025814 res = 1.886E-05 delta-k (pcm) =
[ NORMAL ] ... 24 D.R. = 0.93
[ NORMAL ] Iteration 84: k_eff = 1.026039 res = 1.752E-05 delta-k (pcm) =
[ NORMAL ] ... 22 D.R. = 0.93
[ NORMAL ] Iteration 85: k_eff = 1.026249 res = 1.628E-05 delta-k (pcm) =
[ NORMAL ] ... 20 D.R. = 0.93
[ NORMAL ] Iteration 86: k_eff = 1.026442 res = 1.517E-05 delta-k (pcm) =
[ NORMAL ] ... 19 D.R. = 0.93
[ NORMAL ] Iteration 87: k_eff = 1.026622 res = 1.408E-05 delta-k (pcm) =
[ NORMAL ] ... 17 D.R. = 0.93
[ NORMAL ] Iteration 88: k_eff = 1.026788 res = 1.308E-05 delta-k (pcm) =
[ NORMAL ] ... 16 D.R. = 0.93
[ NORMAL ] Iteration 89: k_eff = 1.026942 res = 1.218E-05 delta-k (pcm) =
[ NORMAL ] ... 15 D.R. = 0.93
[ NORMAL ] Iteration 90: k_eff = 1.027085 res = 1.132E-05 delta-k (pcm) =
[ NORMAL ] ... 14 D.R. = 0.93
[ NORMAL ] Iteration 91: k_eff = 1.027217 res = 1.049E-05 delta-k (pcm) =
[ NORMAL ] ... 13 D.R. = 0.93
[ NORMAL ] Iteration 92: k_eff = 1.027339 res = 9.760E-06 delta-k (pcm) =
[ NORMAL ] ... 12 D.R. = 0.93
[ NORMAL ] Iteration 93: k_eff = 1.027453 res = 9.076E-06 delta-k (pcm) =
[ NORMAL ] ... 11 D.R. = 0.93
[ NORMAL ] Iteration 94: k_eff = 1.027557 res = 8.434E-06 delta-k (pcm) =
[ NORMAL ] ... 10 D.R. = 0.93
[ NORMAL ] Iteration 95: k_eff = 1.027655 res = 7.827E-06 delta-k (pcm) =
[ NORMAL ] ... 9 D.R. = 0.93
[ NORMAL ] Iteration 96: k_eff = 1.027744 res = 7.266E-06 delta-k (pcm) =
[ NORMAL ] ... 8 D.R. = 0.93
[ NORMAL ] Iteration 97: k_eff = 1.027828 res = 6.737E-06 delta-k (pcm) =
[ NORMAL ] ... 8 D.R. = 0.93
[ NORMAL ] Iteration 98: k_eff = 1.027905 res = 6.255E-06 delta-k (pcm) =
[ NORMAL ] ... 7 D.R. = 0.93
[ NORMAL ] Iteration 99: k_eff = 1.027976 res = 5.803E-06 delta-k (pcm) =
[ NORMAL ] ... 7 D.R. = 0.93
[ NORMAL ] Iteration 100: k_eff = 1.028042 res = 5.383E-06 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.93
[ NORMAL ] Iteration 101: k_eff = 1.028103 res = 5.017E-06 delta-k (pcm)
[ NORMAL ] ... = 6 D.R. = 0.93
[ NORMAL ] Iteration 102: k_eff = 1.028160 res = 4.618E-06 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.92
[ NORMAL ] Iteration 103: k_eff = 1.028212 res = 4.306E-06 delta-k (pcm)
[ NORMAL ] ... = 5 D.R. = 0.93
[ NORMAL ] Iteration 104: k_eff = 1.028260 res = 3.999E-06 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.93
[ NORMAL ] Iteration 105: k_eff = 1.028305 res = 3.706E-06 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.93
[ NORMAL ] Iteration 106: k_eff = 1.028347 res = 3.429E-06 delta-k (pcm)
[ NORMAL ] ... = 4 D.R. = 0.93
[ NORMAL ] Iteration 107: k_eff = 1.028385 res = 3.213E-06 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.94
[ NORMAL ] Iteration 108: k_eff = 1.028420 res = 2.943E-06 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.92
[ NORMAL ] Iteration 109: k_eff = 1.028453 res = 2.740E-06 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.93
[ NORMAL ] Iteration 110: k_eff = 1.028484 res = 2.531E-06 delta-k (pcm)
[ NORMAL ] ... = 3 D.R. = 0.92
[ NORMAL ] Iteration 111: k_eff = 1.028512 res = 2.369E-06 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.94
[ NORMAL ] Iteration 112: k_eff = 1.028538 res = 2.186E-06 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.92
[ NORMAL ] Iteration 113: k_eff = 1.028562 res = 2.026E-06 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.93
[ NORMAL ] Iteration 114: k_eff = 1.028584 res = 1.858E-06 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.92
[ NORMAL ] Iteration 115: k_eff = 1.028604 res = 1.760E-06 delta-k (pcm)
[ NORMAL ] ... = 2 D.R. = 0.95
[ NORMAL ] Iteration 116: k_eff = 1.028623 res = 1.612E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.92
[ NORMAL ] Iteration 117: k_eff = 1.028641 res = 1.496E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.93
[ NORMAL ] Iteration 118: k_eff = 1.028657 res = 1.382E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.92
[ NORMAL ] Iteration 119: k_eff = 1.028672 res = 1.293E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.94
[ NORMAL ] Iteration 120: k_eff = 1.028686 res = 1.191E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.92
[ NORMAL ] Iteration 121: k_eff = 1.028699 res = 1.112E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.93
[ NORMAL ] Iteration 122: k_eff = 1.028711 res = 1.005E-06 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.90
[ NORMAL ] Iteration 123: k_eff = 1.028722 res = 9.443E-07 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.94
[ NORMAL ] Iteration 124: k_eff = 1.028732 res = 8.583E-07 delta-k (pcm)
[ NORMAL ] ... = 1 D.R. = 0.91
[ NORMAL ] Iteration 125: k_eff = 1.028742 res = 8.028E-07 delta-k (pcm)
[ NORMAL ] ... = 0 D.R. = 0.94
We report the eigenvalues computed by OpenMC and OpenMOC here together to summarize our results.
[37]:
# Print report of keff and bias with OpenMC
openmoc_keff = solver.getKeff()
openmc_keff = sp.k_combined.nominal_value
bias = (openmoc_keff - openmc_keff) * 1e5
print('openmc keff = {0:1.6f}'.format(openmc_keff))
print('openmoc keff = {0:1.6f}'.format(openmoc_keff))
print('bias [pcm]: {0:1.1f}'.format(bias))
openmc keff = 1.024078
openmoc keff = 1.028742
bias [pcm]: 466.4
There is a non-trivial bias between the eigenvalues computed by OpenMC and OpenMOC. One can show that these biases do not converge to <100 pcm with more particle histories. For heterogeneous geometries, additional measures must be taken to address the following three sources of bias:
- Appropriate transport-corrected cross sections
- Spatial discretization of OpenMOC’s mesh
- Constant-in-angle multi-group cross sections
Flux and Pin Power Visualizations¶
We will conclude this tutorial by illustrating how to visualize the fission rates computed by OpenMOC and OpenMC. First, we extract volume-integrated fission rates from OpenMC’s mesh fission rate tally for each pin cell in the fuel assembly.
[38]:
# Get the OpenMC fission rate mesh tally data
mesh_tally = sp.get_tally(name='mesh tally')
openmc_fission_rates = mesh_tally.get_values(scores=['nu-fission'])
# Reshape array to 2D for plotting
openmc_fission_rates.shape = (17,17)
# Normalize to the average pin power
openmc_fission_rates /= np.mean(openmc_fission_rates[openmc_fission_rates > 0.])
Next, we extract OpenMOC’s volume-averaged fission rates into a 2D 17x17 NumPy array.
[39]:
# Create OpenMOC Mesh on which to tally fission rates
openmoc_mesh = openmoc.process.Mesh()
openmoc_mesh.dimension = np.array(mesh.dimension)
openmoc_mesh.lower_left = np.array(mesh.lower_left)
openmoc_mesh.upper_right = np.array(mesh.upper_right)
openmoc_mesh.width = openmoc_mesh.upper_right - openmoc_mesh.lower_left
openmoc_mesh.width /= openmoc_mesh.dimension
# Tally OpenMOC fission rates on the Mesh
openmoc_fission_rates = openmoc_mesh.tally_fission_rates(solver)
openmoc_fission_rates = np.squeeze(openmoc_fission_rates)
openmoc_fission_rates = np.fliplr(openmoc_fission_rates)
# Normalize to the average pin fission rate
openmoc_fission_rates /= np.mean(openmoc_fission_rates[openmoc_fission_rates > 0.])
Now we can easily use Matplotlib to visualize the fission rates from OpenMC and OpenMOC side-by-side.
[40]:
# Ignore zero fission rates in guide tubes with Matplotlib color scheme
openmc_fission_rates[openmc_fission_rates == 0] = np.nan
openmoc_fission_rates[openmoc_fission_rates == 0] = np.nan
# Plot OpenMC's fission rates in the left subplot
fig = plt.subplot(121)
plt.imshow(openmc_fission_rates, interpolation='none', cmap='jet')
plt.title('OpenMC Fission Rates')
# Plot OpenMOC's fission rates in the right subplot
fig2 = plt.subplot(122)
plt.imshow(openmoc_fission_rates, interpolation='none', cmap='jet')
plt.title('OpenMOC Fission Rates')
[40]:
Text(0.5, 1.0, 'OpenMOC Fission Rates')

Multigroup (Delayed) Cross Section Generation Part I: Introduction¶
This IPython Notebook introduces the use of the openmc.mgxs
module to calculate multi-energy-group and multi-delayed-group cross sections for an infinite homogeneous medium. In particular, this Notebook introduces the the following features:
- Creation of multi-delayed-group cross sections for an infinite homogeneous medium
- Calculation of delayed neutron precursor concentrations
Introduction to Multi-Delayed-Group Cross Sections (MDGXS)¶
Many Monte Carlo particle transport codes, including OpenMC, use continuous-energy nuclear cross section data. However, most deterministic neutron transport codes use multi-group cross sections defined over discretized energy bins or energy groups. Furthermore, kinetics calculations typically separate out parameters that involve delayed neutrons into prompt and delayed components and further subdivide delayed components by delayed groups. An example is the energy spectrum for prompt and delayed neutrons for U-235 and Pu-239 computed for a light water reactor spectrum.
[1]:
from IPython.display import Image
Image(filename='images/mdgxs.png', width=350)
A variety of tools employing different methodologies have been developed over the years to compute multi-group cross sections for certain applications, including NJOY (LANL), MC\(^2\)-3 (ANL), and Serpent (VTT). The openmc.mgxs
Python module is designed to leverage OpenMC’s tally system to calculate multi-group cross sections with arbitrary energy discretizations and different delayed group models (e.g. 6, 7, or 8 delayed group models) for fine-mesh heterogeneous deterministic neutron
transport applications.
Before proceeding to illustrate how one may use the openmc.mgxs
module, it is worthwhile to define the general equations used to calculate multi-energy-group and multi-delayed-group cross sections. This is only intended as a brief overview of the methodology used by openmc.mgxs
- we refer the interested reader to the large body of literature on the subject for a more comprehensive understanding of this complex topic.
Introductory Notation¶
The continuous real-valued microscopic cross section may be denoted \(\sigma_{n,x}(\mathbf{r}, E)\) for position vector \(\mathbf{r}\), energy \(E\), nuclide \(n\) and interaction type \(x\). Similarly, the scalar neutron flux may be denoted by \(\Phi(\mathbf{r},E)\) for position \(\mathbf{r}\) and energy \(E\). Note: Although nuclear cross sections are dependent on the temperature \(T\) of the interacting medium, the temperature variable is neglected here for brevity.
Spatial and Energy Discretization¶
The energy domain for critical systems such as thermal reactors spans more than 10 orders of magnitude of neutron energies from 10\(^{-5}\) - 10\(^7\) eV. The multi-group approximation discretization divides this energy range into one or more energy groups. In particular, for \(G\) total groups, we denote an energy group index \(g\) such that \(g \in \{1, 2, ..., G\}\). The energy group indices are defined such that the smaller group the higher the energy, and vice versa. The integration over neutron energies across a discrete energy group is commonly referred to as energy condensation.
The delayed neutrons created from fissions are created from > 30 delayed neutron precursors. Modeling each of the delayed neutron precursors is possible, but this approach has not recieved much attention due to large uncertainties in certain precursors. Therefore, the delayed neutrons are often combined into “delayed groups” that have a set time constant, \(\lambda_d\). Some cross section libraries use the same group time constants for all nuclides (e.g. JEFF 3.1) while other libraries use different time constants for all nuclides (e.g. ENDF/B-VII.1). Multi-delayed-group cross sections can either be created with the entire delayed group set, a subset of delayed groups, or integrated over all delayed groups.
Multi-group cross sections are computed for discretized spatial zones in the geometry of interest. The spatial zones may be defined on a structured and regular fuel assembly or pin cell mesh, an arbitrary unstructured mesh or the constructive solid geometry used by OpenMC. For a geometry with \(K\) distinct spatial zones, we designate each spatial zone an index \(k\) such that \(k \in \{1, 2, ..., K\}\). The volume of each spatial zone is denoted by \(V_{k}\). The integration over discrete spatial zones is commonly referred to as spatial homogenization.
General Scalar-Flux Weighted MDGXS¶
The multi-group cross sections computed by openmc.mgxs
are defined as a scalar flux-weighted average of the microscopic cross sections across each discrete energy group. This formulation is employed in order to preserve the reaction rates within each energy group and spatial zone. In particular, spatial homogenization and energy condensation are used to compute the general multi-group cross section. For instance, the delayed-nu-fission multi-energy-group and multi-delayed-group cross
section, \(\nu_d \sigma_{f,x,k,g}\), can be computed as follows:
This scalar flux-weighted average microscopic cross section is computed by openmc.mgxs
for only the delayed-nu-fission and delayed neutron fraction reaction type at the moment. These double integrals are stochastically computed with OpenMC’s tally system - in particular, filters on the energy range and spatial zone (material, cell, universe, or mesh) define the bounds of integration for both numerator and denominator.
Multi-Group Prompt and Delayed Fission Spectrum¶
The energy spectrum of neutrons emitted from fission is denoted by \(\chi_{n}(\mathbf{r},E' \rightarrow E'')\) for incoming and outgoing energies \(E'\) and \(E''\), respectively. Unlike the multi-group cross sections \(\sigma_{n,x,k,g}\) considered up to this point, the fission spectrum is a probability distribution and must sum to unity. The outgoing energy is typically much less dependent on the incoming energy for fission than for scattering interactions. As a result, it is common practice to integrate over the incoming neutron energy when computing the multi-group fission spectrum. The fission spectrum may be simplified as \(\chi_{n}(\mathbf{r},E)\) with outgoing energy \(E\).
Computing the cumulative energy spectrum of emitted neutrons, \(\chi_{n}(\mathbf{r},E)\), has been presented in the mgxs-part-i.ipynb
notebook. Here, we will present the energy spectrum of prompt and delayed emission neutrons, \(\chi_{n,p}(\mathbf{r},E)\) and \(\chi_{n,d}(\mathbf{r},E)\), respectively. Unlike the multi-group cross sections defined up to this point, the multi-group fission spectrum is weighted by the fission production rate rather than the scalar flux. This
formulation is intended to preserve the total fission production rate in the multi-group deterministic calculation. In order to mathematically define the multi-group fission spectrum, we denote the microscopic fission cross section as \(\sigma_{n,f}(\mathbf{r},E)\) and the average number of neutrons emitted from fission interactions with nuclide \(n\) as \(\nu_{n,p}(\mathbf{r},E)\) and \(\nu_{n,d}(\mathbf{r},E)\) for prompt and delayed neutrons, respectively. The multi-group
fission spectrum \(\chi_{n,k,g,d}\) is then the probability of fission neutrons emitted into energy group \(g\) and delayed group \(d\). There are not prompt groups, so inserting \(p\) in place of \(d\) just denotes all prompt neutrons.
Similar to before, spatial homogenization and energy condensation are used to find the multi-energy-group and multi-delayed-group fission spectrum \(\chi_{n,k,g,d}\) as follows:
The fission production-weighted multi-energy-group and multi-delayed-group fission spectrum for delayed neutrons is computed using OpenMC tallies with energy in, energy out, and delayed group filters. Alternatively, the delayed group filter can be omitted to compute the fission spectrum integrated over all delayed groups.
This concludes our brief overview on the methodology to compute multi-energy-group and multi-delayed-group cross sections. The following sections detail more concretely how users may employ the openmc.mgxs
module to power simulation workflows requiring multi-group cross sections for downstream deterministic calculations.
Generate Input Files¶
[2]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import openmc
import openmc.mgxs as mgxs
First we need to define materials that will be used in the problem. Let’s create a material for the homogeneous medium.
[3]:
# Instantiate a Material and register the Nuclides
inf_medium = openmc.Material(name='moderator')
inf_medium.set_density('g/cc', 5.)
inf_medium.add_nuclide('H1', 0.03)
inf_medium.add_nuclide('O16', 0.015)
inf_medium.add_nuclide('U235', 0.0001)
inf_medium.add_nuclide('U238', 0.007)
inf_medium.add_nuclide('Pu239', 0.00003)
inf_medium.add_nuclide('Zr90', 0.002)
With our material, we can now create a Materials
object that can be exported to an actual XML file.
[4]:
# Instantiate a Materials collection and export to XML
materials_file = openmc.Materials([inf_medium])
materials_file.export_to_xml()
Now let’s move on to the geometry. This problem will be a simple square cell with reflective boundary conditions to simulate an infinite homogeneous medium. The first step is to create the outer bounding surfaces of the problem.
[5]:
# Instantiate boundary Planes
min_x = openmc.XPlane(boundary_type='reflective', x0=-0.63)
max_x = openmc.XPlane(boundary_type='reflective', x0=0.63)
min_y = openmc.YPlane(boundary_type='reflective', y0=-0.63)
max_y = openmc.YPlane(boundary_type='reflective', y0=0.63)
With the surfaces defined, we can now create a cell that is defined by intersections of half-spaces created by the surfaces.
[6]:
# Instantiate a Cell
cell = openmc.Cell(cell_id=1, name='cell')
# Register bounding Surfaces with the Cell
cell.region = +min_x & -max_x & +min_y & -max_y
# Fill the Cell with the Material
cell.fill = inf_medium
We now must create a geometry and export it to XML.
[7]:
# Create Geometry and set root Universe
openmc_geometry = openmc.Geometry([cell])
# Export to "geometry.xml"
openmc_geometry.export_to_xml()
Next, we must define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 2500 particles.
[8]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 5000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': True}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-0.63, -0.63, -0.63, 0.63, 0.63, 0.63]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Now we are ready to generate multi-group cross sections! First, let’s define a 100-energy-group structure and 1-energy-group structure using the built-in EnergyGroups
class. We will also create a 6-delayed-group list.
[9]:
# Instantiate a 100-group EnergyGroups object
energy_groups = mgxs.EnergyGroups()
energy_groups.group_edges = np.logspace(-3, 7.3, 101)
# Instantiate a 1-group EnergyGroups object
one_group = mgxs.EnergyGroups()
one_group.group_edges = np.array([energy_groups.group_edges[0], energy_groups.group_edges[-1]])
delayed_groups = list(range(1,7))
We can now use the EnergyGroups
object and delayed group list, along with our previously created materials and geometry, to instantiate some MGXS
objects from the openmc.mgxs
module. In particular, the following are subclasses of the generic and abstract MGXS
class:
TotalXS
TransportXS
AbsorptionXS
CaptureXS
FissionXS
NuFissionMatrixXS
KappaFissionXS
ScatterXS
ScatterMatrixXS
Chi
InverseVelocity
A separate abstract MDGXS
class is used for cross-sections and parameters that involve delayed neutrons. The subclasses of MDGXS
include:
DelayedNuFissionXS
ChiDelayed
Beta
DecayRate
These classes provide us with an interface to generate the tally inputs as well as perform post-processing of OpenMC’s tally data to compute the respective multi-group cross sections.
In this case, let’s create the multi-group chi-prompt, chi-delayed, and prompt-nu-fission cross sections with our 100-energy-group structure and multi-group delayed-nu-fission and beta cross sections with our 100-energy-group and 6-delayed-group structures.
The prompt chi and nu-fission data can actually be gathered using the Chi
and FissionXS
classes, respectively, by passing in a value of True
for the optional prompt
parameter upon initialization.
[10]:
# Instantiate a few different sections
chi_prompt = mgxs.Chi(domain=cell, groups=energy_groups, by_nuclide=True, prompt=True)
prompt_nu_fission = mgxs.FissionXS(domain=cell, groups=energy_groups, by_nuclide=True, nu=True, prompt=True)
chi_delayed = mgxs.ChiDelayed(domain=cell, energy_groups=energy_groups, by_nuclide=True)
delayed_nu_fission = mgxs.DelayedNuFissionXS(domain=cell, energy_groups=energy_groups, delayed_groups=delayed_groups, by_nuclide=True)
beta = mgxs.Beta(domain=cell, energy_groups=energy_groups, delayed_groups=delayed_groups, by_nuclide=True)
decay_rate = mgxs.DecayRate(domain=cell, energy_groups=one_group, delayed_groups=delayed_groups, by_nuclide=True)
chi_prompt.nuclides = ['U235', 'Pu239']
prompt_nu_fission.nuclides = ['U235', 'Pu239']
chi_delayed.nuclides = ['U235', 'Pu239']
delayed_nu_fission.nuclides = ['U235', 'Pu239']
beta.nuclides = ['U235', 'Pu239']
decay_rate.nuclides = ['U235', 'Pu239']
Each multi-group cross section object stores its tallies in a Python dictionary called tallies
. We can inspect the tallies in the dictionary for our Decay Rate
object as follows.
[11]:
decay_rate.tallies
[11]:
OrderedDict([('delayed-nu-fission', Tally
ID = 1
Name =
Filters = CellFilter, DelayedGroupFilter, EnergyFilter
Nuclides = U235 Pu239
Scores = ['delayed-nu-fission']
Estimator = tracklength), ('decay-rate', Tally
ID = 2
Name =
Filters = CellFilter, DelayedGroupFilter, EnergyFilter
Nuclides = U235 Pu239
Scores = ['decay-rate']
Estimator = tracklength)])
The Beta
object includes tracklength tallies for the ‘nu-fission’ and ‘delayed-nu-fission’ scores in the 100-energy-group and 6-delayed-group structure in cell 1. Now that each MGXS
and MDGXS
object contains the tallies that it needs, we must add these tallies to a Tallies
object to generate the “tallies.xml” input file for OpenMC.
[12]:
# Instantiate an empty Tallies object
tallies_file = openmc.Tallies()
# Add chi-prompt tallies to the tallies file
tallies_file += chi_prompt.tallies.values()
# Add prompt-nu-fission tallies to the tallies file
tallies_file += prompt_nu_fission.tallies.values()
# Add chi-delayed tallies to the tallies file
tallies_file += chi_delayed.tallies.values()
# Add delayed-nu-fission tallies to the tallies file
tallies_file += delayed_nu_fission.tallies.values()
# Add beta tallies to the tallies file
tallies_file += beta.tallies.values()
# Add decay rate tallies to the tallies file
tallies_file += decay_rate.tallies.values()
# Export to "tallies.xml"
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=4.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=6.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=5.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=8.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=14.
warn(msg, IDWarning)
Now we a have a complete set of inputs, so we can go ahead and run our simulation.
[13]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-19 06:56:34
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading Pu239 from /opt/data/hdf5/nndc_hdf5_v15/Pu239.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for H1
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.21670
2/1 1.24155
3/1 1.21924
4/1 1.22486
5/1 1.21719
6/1 1.24330
7/1 1.22322
8/1 1.24133
9/1 1.21840
10/1 1.25141
11/1 1.21217
12/1 1.25625 1.23421 +/- 0.02204
13/1 1.22056 1.22966 +/- 0.01351
14/1 1.21757 1.22664 +/- 0.01002
15/1 1.24571 1.23045 +/- 0.00865
16/1 1.26489 1.23619 +/- 0.00910
17/1 1.22323 1.23434 +/- 0.00791
18/1 1.26108 1.23768 +/- 0.00762
19/1 1.23145 1.23699 +/- 0.00676
20/1 1.23548 1.23684 +/- 0.00605
21/1 1.20446 1.23390 +/- 0.00621
22/1 1.20533 1.23152 +/- 0.00615
23/1 1.22520 1.23103 +/- 0.00568
24/1 1.18367 1.22765 +/- 0.00625
25/1 1.23614 1.22821 +/- 0.00585
26/1 1.23746 1.22879 +/- 0.00550
27/1 1.23626 1.22923 +/- 0.00518
28/1 1.21334 1.22835 +/- 0.00497
29/1 1.25169 1.22958 +/- 0.00486
30/1 1.25579 1.23089 +/- 0.00479
31/1 1.23828 1.23124 +/- 0.00457
32/1 1.26911 1.23296 +/- 0.00468
33/1 1.20090 1.23157 +/- 0.00469
34/1 1.28606 1.23384 +/- 0.00503
35/1 1.23129 1.23374 +/- 0.00483
36/1 1.22535 1.23341 +/- 0.00465
37/1 1.20367 1.23231 +/- 0.00461
38/1 1.22886 1.23219 +/- 0.00444
39/1 1.24056 1.23248 +/- 0.00429
40/1 1.25038 1.23307 +/- 0.00419
41/1 1.21504 1.23249 +/- 0.00410
42/1 1.20762 1.23171 +/- 0.00404
43/1 1.20597 1.23093 +/- 0.00399
44/1 1.24424 1.23133 +/- 0.00389
45/1 1.24767 1.23179 +/- 0.00381
46/1 1.22998 1.23174 +/- 0.00370
47/1 1.26195 1.23256 +/- 0.00369
48/1 1.23146 1.23253 +/- 0.00359
49/1 1.22059 1.23222 +/- 0.00351
50/1 1.24724 1.23260 +/- 0.00345
Creating state point statepoint.50.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 4.7388e-01 seconds
Reading cross sections = 4.4709e-01 seconds
Total time in simulation = 3.9290e+01 seconds
Time in transport only = 3.9005e+01 seconds
Time in inactive batches = 1.4079e+00 seconds
Time in active batches = 3.7882e+01 seconds
Time synchronizing fission bank = 1.8814e-02 seconds
Sampling source sites = 1.6376e-02 seconds
SEND/RECV source sites = 2.3626e-03 seconds
Time accumulating tallies = 8.3299e-04 seconds
Total time for finalization = 1.1533e-02 seconds
Total time elapsed = 3.9783e+01 seconds
Calculation Rate (inactive) = 35514.2 particles/second
Calculation Rate (active) = 5279.54 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.23256 +/- 0.00308
k-effective (Track-length) = 1.23260 +/- 0.00345
k-effective (Absorption) = 1.23111 +/- 0.00186
Combined k-effective = 1.23135 +/- 0.00184
Leakage Fraction = 0.00000 +/- 0.00000
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a StatePoint
object.
[14]:
# Load the last statepoint file
sp = openmc.StatePoint('statepoint.50.h5')
In addition to the statepoint file, our simulation also created a summary file which encapsulates information about the materials and geometry. By default, a Summary
object is automatically linked when a StatePoint
is loaded. This is necessary for the openmc.mgxs
module to properly process the tally data.
The statepoint is now ready to be analyzed by our multi-group cross sections. We simply have to load the tallies from the StatePoint
into each object as follows and our MGXS
objects will compute the cross sections for us under-the-hood.
[15]:
# Load the tallies from the statepoint into each MGXS object
chi_prompt.load_from_statepoint(sp)
prompt_nu_fission.load_from_statepoint(sp)
chi_delayed.load_from_statepoint(sp)
delayed_nu_fission.load_from_statepoint(sp)
beta.load_from_statepoint(sp)
decay_rate.load_from_statepoint(sp)
Voila! Our multi-group cross sections are now ready to rock ‘n roll!
Extracting and Storing MGXS Data¶
Let’s first inspect our delayed-nu-fission section by printing it to the screen after condensing the cross section down to one group.
[16]:
delayed_nu_fission.get_condensed_xs(one_group).get_xs()
[16]:
array([[[5.14223507e-06, 1.16426087e-06]],
[[2.65426350e-05, 7.58220468e-06]],
[[2.53399053e-05, 5.73796202e-06]],
[[5.68141581e-05, 1.04757933e-05]],
[[2.32930026e-05, 5.45658817e-06]],
[[9.75735783e-06, 1.65150949e-06]]])
Since the openmc.mgxs
module uses tally arithmetic under-the-hood, the cross section is stored as a “derived” Tally
object. This means that it can be queried and manipulated using all of the same methods supported for the Tally
class in the OpenMC Python API. For example, we can construct a Pandas DataFrame
of the multi-group cross section data.
[17]:
df = delayed_nu_fission.get_pandas_dataframe()
df.head(10)
[17]:
cell | delayedgroup | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|---|
198 | 1 | 1 | 1 | U235 | 9.345817e-08 | 6.616216e-08 |
199 | 1 | 1 | 1 | Pu239 | 1.574816e-08 | 1.114955e-08 |
398 | 1 | 2 | 1 | U235 | 4.824023e-07 | 3.415087e-07 |
399 | 1 | 2 | 1 | Pu239 | 1.025593e-07 | 7.261101e-08 |
598 | 1 | 3 | 1 | U235 | 4.605432e-07 | 3.260339e-07 |
599 | 1 | 3 | 1 | Pu239 | 7.761348e-08 | 5.494962e-08 |
798 | 1 | 4 | 1 | U235 | 1.032576e-06 | 7.309948e-07 |
799 | 1 | 4 | 1 | Pu239 | 1.416989e-07 | 1.003215e-07 |
998 | 1 | 5 | 1 | U235 | 4.233415e-07 | 2.996976e-07 |
999 | 1 | 5 | 1 | Pu239 | 7.380753e-08 | 5.225504e-08 |
[18]:
df = decay_rate.get_pandas_dataframe()
df.head(12)
[18]:
cell | delayedgroup | group in | nuclide | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | U235 | 0.013336 | 0.000061 |
1 | 1 | 1 | 1 | Pu239 | 0.013271 | 0.000053 |
2 | 1 | 2 | 1 | U235 | 0.032739 | 0.000149 |
3 | 1 | 2 | 1 | Pu239 | 0.030881 | 0.000123 |
4 | 1 | 3 | 1 | U235 | 0.120780 | 0.000549 |
5 | 1 | 3 | 1 | Pu239 | 0.113370 | 0.000451 |
6 | 1 | 4 | 1 | U235 | 0.302780 | 0.001378 |
7 | 1 | 4 | 1 | Pu239 | 0.292500 | 0.001163 |
8 | 1 | 5 | 1 | U235 | 0.849490 | 0.003865 |
9 | 1 | 5 | 1 | Pu239 | 0.857490 | 0.003411 |
10 | 1 | 6 | 1 | U235 | 2.853000 | 0.012980 |
11 | 1 | 6 | 1 | Pu239 | 2.729700 | 0.010858 |
Each multi-group cross section object can be easily exported to a variety of file formats, including CSV, Excel, and LaTeX for storage or data processing.
[19]:
beta.export_xs_data(filename='beta', format='excel')
The following code snippet shows how to export the chi-prompt and chi-delayed MGXS
to the same HDF5 binary data store.
[20]:
chi_prompt.build_hdf5_store(filename='mdgxs', append=True)
chi_delayed.build_hdf5_store(filename='mdgxs', append=True)
Using Tally Arithmetic to Compute the Delayed Neutron Precursor Concentrations¶
Finally, we illustrate how one can leverage OpenMC’s tally arithmetic data processing feature with MGXS
objects. The openmc.mgxs
module uses tally arithmetic to compute multi-group cross sections with automated uncertainty propagation. Each MGXS
object includes an xs_tally
attribute which is a “derived” Tally
based on the tallies needed to compute the cross section type of interest. These derived tallies can be used in subsequent tally arithmetic
operations. For example, we can use tally artithmetic to compute the delayed neutron precursor concentrations using the Beta
, DelayedNuFissionXS
, and DecayRate
objects. The delayed neutron precursor concentrations are modeled using the following equations:
First, let’s investigate the decay rates for U235 and Pu235. The fraction of the delayed neutron precursors remaining as a function of time after fission for each delayed group and fissioning isotope have been plotted below.
[21]:
# Get the decay rate data
dr_tally = decay_rate.xs_tally
dr_u235 = dr_tally.get_values(nuclides=['U235']).flatten()
dr_pu239 = dr_tally.get_values(nuclides=['Pu239']).flatten()
# Compute the exponential decay of the precursors
time = np.logspace(-3,3)
dr_u235_points = np.exp(-np.outer(dr_u235, time))
dr_pu239_points = np.exp(-np.outer(dr_pu239, time))
# Create a plot of the fraction of the precursors remaining as a f(time)
colors = ['b', 'g', 'r', 'c', 'm', 'k']
legend = []
fig = plt.figure(figsize=(8,6))
for g,c in enumerate(colors):
plt.semilogx(time, dr_u235_points [g,:], color=c, linestyle='--', linewidth=3)
plt.semilogx(time, dr_pu239_points[g,:], color=c, linestyle=':' , linewidth=3)
legend.append('U-235 $t_{1/2}$ = ' + '{0:1.2f} seconds'.format(np.log(2) / dr_u235[g]))
legend.append('Pu-239 $t_{1/2}$ = ' + '{0:1.2f} seconds'.format(np.log(2) / dr_pu239[g]))
plt.title('Delayed Neutron Precursor Decay Rates')
plt.xlabel('Time (s)')
plt.ylabel('Fraction Remaining')
plt.legend(legend, loc=1, bbox_to_anchor=(1.55, 0.95))
[21]:
<matplotlib.legend.Legend at 0x14ac6a8999b0>

Now let’s compute the initial concentration of the delayed neutron precursors:
[22]:
# Use tally arithmetic to compute the precursor concentrations
precursor_conc = beta.get_condensed_xs(one_group).xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True) * \
delayed_nu_fission.get_condensed_xs(one_group).xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True) / \
decay_rate.xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True)
# Get the Pandas DataFrames for inspection
precursor_conc.get_pandas_dataframe()
[22]:
cell | delayedgroup | nuclide | score | mean | std. dev. | |
---|---|---|---|---|---|---|
0 | 1 | 1 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 8.779139e-08 | 4.658590e-10 |
1 | 1 | 1 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 7.149814e-09 | 3.559010e-11 |
2 | 1 | 2 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 9.527880e-07 | 5.055905e-09 |
3 | 1 | 2 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 1.303159e-07 | 6.486820e-10 |
4 | 1 | 3 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 2.353903e-07 | 1.249083e-09 |
5 | 1 | 3 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 2.032895e-08 | 1.011928e-10 |
6 | 1 | 4 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 4.720191e-07 | 2.504737e-09 |
7 | 1 | 4 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 2.626309e-08 | 1.307315e-10 |
8 | 1 | 5 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 2.827915e-08 | 1.500614e-10 |
9 | 1 | 5 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 2.430587e-09 | 1.209889e-11 |
10 | 1 | 6 | U235 | (((delayed-nu-fission / nu-fission) * (delayed... | 1.477530e-09 | 7.840413e-12 |
11 | 1 | 6 | Pu239 | (((delayed-nu-fission / nu-fission) * (delayed... | 6.994312e-11 | 3.481605e-13 |
We can plot the delayed neutron fractions for each nuclide.
[23]:
energy_filter = [f for f in beta.xs_tally.filters if type(f) is openmc.EnergyFilter]
beta_integrated = beta.get_condensed_xs(one_group).xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True)
beta_u235 = beta_integrated.get_values(nuclides=['U235'])
beta_pu239 = beta_integrated.get_values(nuclides=['Pu239'])
# Reshape the betas
beta_u235.shape = (beta_u235.shape[0])
beta_pu239.shape = (beta_pu239.shape[0])
df = beta_integrated.summation(filter_type=openmc.DelayedGroupFilter, remove_filter=True).get_pandas_dataframe()
print('Beta (U-235) : {:.6f} +/- {:.6f}'.format(df[df['nuclide'] == 'U235']['mean'][0], df[df['nuclide'] == 'U235']['std. dev.'][0]))
print('Beta (Pu-239): {:.6f} +/- {:.6f}'.format(df[df['nuclide'] == 'Pu239']['mean'][1], df[df['nuclide'] == 'Pu239']['std. dev.'][1]))
beta_u235 = np.append(beta_u235[0], beta_u235)
beta_pu239 = np.append(beta_pu239[0], beta_pu239)
# Create a step plot for the MGXS
plt.plot(np.arange(0.5, 7.5, 1), beta_u235, drawstyle='steps', color='b', linewidth=3)
plt.plot(np.arange(0.5, 7.5, 1), beta_pu239, drawstyle='steps', color='g', linewidth=3)
plt.title('Delayed Neutron Fraction (beta)')
plt.xlabel('Delayed Group')
plt.ylabel('Beta(fraction total neutrons)')
plt.legend(['U-235', 'Pu-239'])
plt.xlim([0,7])
Beta (U-235) : 0.006504 +/- 0.000007
Beta (Pu-239): 0.002245 +/- 0.000002
[23]:
(0, 7)

We can also plot the energy spectrum for fission emission of prompt and delayed neutrons.
[24]:
chi_d_u235 = np.squeeze(chi_delayed.get_xs(nuclides=['U235'], order_groups='decreasing'))
chi_d_pu239 = np.squeeze(chi_delayed.get_xs(nuclides=['Pu239'], order_groups='decreasing'))
chi_p_u235 = np.squeeze(chi_prompt.get_xs(nuclides=['U235'], order_groups='decreasing'))
chi_p_pu239 = np.squeeze(chi_prompt.get_xs(nuclides=['Pu239'], order_groups='decreasing'))
chi_d_u235 = np.append(chi_d_u235 , chi_d_u235[0])
chi_d_pu239 = np.append(chi_d_pu239, chi_d_pu239[0])
chi_p_u235 = np.append(chi_p_u235 , chi_p_u235[0])
chi_p_pu239 = np.append(chi_p_pu239, chi_p_pu239[0])
# Create a step plot for the MGXS
plt.semilogx(energy_groups.group_edges, chi_d_u235 , drawstyle='steps', color='b', linestyle='--', linewidth=3)
plt.semilogx(energy_groups.group_edges, chi_d_pu239, drawstyle='steps', color='g', linestyle='--', linewidth=3)
plt.semilogx(energy_groups.group_edges, chi_p_u235 , drawstyle='steps', color='b', linestyle=':', linewidth=3)
plt.semilogx(energy_groups.group_edges, chi_p_pu239, drawstyle='steps', color='g', linestyle=':', linewidth=3)
plt.title('Energy Spectrum for Fission Neutrons')
plt.xlabel('Energy (eV)')
plt.ylabel('Fraction on emitted neutrons')
plt.legend(['U-235 delayed', 'Pu-239 delayed', 'U-235 prompt', 'Pu-239 prompt'],loc=2)
plt.xlim(1.0e3, 20.0e6)
[24]:
(1000.0, 20000000.0)

Multigroup (Delayed) Cross Section Generation Part II: Advanced Features¶
This IPython Notebook illustrates the use of the ``openmc.mgxs.Library`` class. The Library
class is designed to automate the calculation of multi-group cross sections for use cases with one or more domains, cross section types, and/or nuclides. In particular, this Notebook illustrates the following features:
- Calculation of multi-energy-group and multi-delayed-group cross sections for a fuel assembly
- Automated creation, manipulation and storage of
MGXS
with ``openmc.mgxs.Library`` - Steady-state pin-by-pin delayed neutron fractions (beta) for each delayed group.
- Generation of surface currents on the interfaces and surfaces of a Mesh.
Generate Input Files¶
[1]:
%matplotlib inline
import math
import matplotlib.pyplot as plt
import numpy as np
import openmc
import openmc.mgxs
First we need to define materials that will be used in the problem: fuel, water, and cladding.
[2]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide('U235', 3.7503e-4)
fuel.add_nuclide('U238', 2.2625e-2)
fuel.add_nuclide('O16', 4.6007e-2)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide('H1', 4.9457e-2)
water.add_nuclide('O16', 2.4732e-2)
water.add_nuclide('B10', 8.0042e-6)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)
With our three materials, we can now create a Materials
object that can be exported to an actual XML file.
[3]:
# Create a materials collection and export to XML
materials = openmc.Materials((fuel, water, zircaloy))
materials.export_to_xml()
Now let’s move on to the geometry. This problem will be a square array of fuel pins and control rod guide tubes for which we can use OpenMC’s lattice/universe feature. The basic universe will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces for fuel and clad, as well as the outer bounding surfaces of the problem.
[4]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(r=0.39218)
clad_outer_radius = openmc.ZCylinder(r=0.45720)
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='reflective')
min_y = openmc.YPlane(y0=-10.71, boundary_type='reflective')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10., boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10., boundary_type='reflective')
With the surfaces defined, we can now construct a fuel pin cell from cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
fuel_pin_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
fuel_pin_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
fuel_pin_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
fuel_pin_universe.add_cell(moderator_cell)
Likewise, we can construct a control rod guide tube with the same surfaces.
[6]:
# Create a Universe to encapsulate a control rod guide tube
guide_tube_universe = openmc.Universe(name='Guide Tube')
# Create guide tube Cell
guide_tube_cell = openmc.Cell(name='Guide Tube Water')
guide_tube_cell.fill = water
guide_tube_cell.region = -fuel_outer_radius
guide_tube_universe.add_cell(guide_tube_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='Guide Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
guide_tube_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='Guide Tube Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
guide_tube_universe.add_cell(moderator_cell)
Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch.
[7]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel Assembly')
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2
Next, we create a NumPy array of fuel pin and guide tube universes for the lattice.
[8]:
# Create array indices for guide tube locations in lattice
template_x = np.array([5, 8, 11, 3, 13, 2, 5, 8, 11, 14, 2, 5, 8,
11, 14, 2, 5, 8, 11, 14, 3, 13, 5, 8, 11])
template_y = np.array([2, 2, 2, 3, 3, 5, 5, 5, 5, 5, 8, 8, 8, 8,
8, 11, 11, 11, 11, 11, 13, 13, 14, 14, 14])
# Create universes array with the fuel pin and guide tube universes
universes = np.tile(fuel_pin_universe, (17,17))
universes[template_x, template_y] = guide_tube_universe
# Store the array of universes in the lattice
assembly.universes = universes
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe.
[9]:
# Create root Cell
root_cell = openmc.Cell(name='root cell', fill=assembly)
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(universe_id=0, name='root universe')
root_universe.add_cell(root_cell)
We now must create a geometry that is assigned a root universe and export it to XML.
[10]:
# Create Geometry and export to XML
geometry = openmc.Geometry(root_universe)
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 2500 particles.
[11]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 2500
# Instantiate a Settings object
settings = openmc.Settings()
settings.batches = batches
settings.inactive = inactive
settings.particles = particles
settings.output = {'tallies': False}
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings.export_to_xml()
Let us also create a plot to verify that our fuel assembly geometry was created successfully.
[12]:
# Plot our geometry
plot = openmc.Plot.from_geometry(geometry)
plot.pixels = (250, 250)
plot.color_by = 'material'
openmc.plot_inline(plot)

As we can see from the plot, we have a nice array of fuel and guide tube pin cells with fuel, cladding, and water!
Create an MGXS Library¶
Now we are ready to generate multi-group cross sections! First, let’s define a 20-energy-group and 1-energy-group.
[13]:
# Instantiate a 20-group EnergyGroups object
energy_groups = openmc.mgxs.EnergyGroups()
energy_groups.group_edges = np.logspace(-3, 7.3, 21)
# Instantiate a 1-group EnergyGroups object
one_group = openmc.mgxs.EnergyGroups()
one_group.group_edges = np.array([energy_groups.group_edges[0], energy_groups.group_edges[-1]])
Next, we will instantiate an openmc.mgxs.Library
for the energy and delayed groups with our the fuel assembly geometry.
[14]:
# Instantiate a tally mesh
mesh = openmc.RegularMesh(mesh_id=1)
mesh.dimension = [17, 17, 1]
mesh.lower_left = [-10.71, -10.71, -10000.]
mesh.width = [1.26, 1.26, 20000.]
# Initialize an 20-energy-group and 6-delayed-group MGXS Library
mgxs_lib = openmc.mgxs.Library(geometry)
mgxs_lib.energy_groups = energy_groups
mgxs_lib.num_delayed_groups = 6
# Specify multi-group cross section types to compute
mgxs_lib.mgxs_types = ['total', 'transport', 'nu-scatter matrix', 'kappa-fission', 'inverse-velocity', 'chi-prompt',
'prompt-nu-fission', 'chi-delayed', 'delayed-nu-fission', 'beta']
# Specify a "mesh" domain type for the cross section tally filters
mgxs_lib.domain_type = 'mesh'
# Specify the mesh domain over which to compute multi-group cross sections
mgxs_lib.domains = [mesh]
# Construct all tallies needed for the multi-group cross section library
mgxs_lib.build_library()
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
mgxs_lib.add_to_tallies_file(tallies_file, merge=True)
# Instantiate a current tally
mesh_filter = openmc.MeshSurfaceFilter(mesh)
current_tally = openmc.Tally(name='current tally')
current_tally.scores = ['current']
current_tally.filters = [mesh_filter]
# Add current tally to the tallies file
tallies_file.append(current_tally)
# Export to "tallies.xml"
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=1.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=5.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=6.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=17.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=23.
warn(msg, IDWarning)
Now, we can run OpenMC to generate the cross sections.
[15]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | 61c911cffdae2406f9f4bc667a9a6954748bb70c
Date/Time | 2019-07-18 22:07:58
OpenMP Threads | 4
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U235 from /opt/data/hdf5/nndc_hdf5_v15/U235.h5
Reading U238 from /opt/data/hdf5/nndc_hdf5_v15/U238.h5
Reading O16 from /opt/data/hdf5/nndc_hdf5_v15/O16.h5
Reading H1 from /opt/data/hdf5/nndc_hdf5_v15/H1.h5
Reading B10 from /opt/data/hdf5/nndc_hdf5_v15/B10.h5
Reading Zr90 from /opt/data/hdf5/nndc_hdf5_v15/Zr90.h5
Maximum neutron transport energy: 20000000.000000 eV for U235
Reading tallies XML file...
Writing summary.h5 file...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 1.03852
2/1 0.99743
3/1 1.02987
4/1 1.04397
5/1 1.06262
6/1 1.06657
7/1 0.98574
8/1 1.04364
9/1 1.01253
10/1 1.02094
11/1 0.99586
12/1 1.00508 1.00047 +/- 0.00461
13/1 1.05292 1.01795 +/- 0.01769
14/1 1.04732 1.02530 +/- 0.01450
15/1 1.04886 1.03001 +/- 0.01218
16/1 1.00948 1.02659 +/- 0.01052
17/1 1.02644 1.02657 +/- 0.00889
18/1 1.03080 1.02710 +/- 0.00772
19/1 1.00018 1.02411 +/- 0.00743
20/1 1.05668 1.02736 +/- 0.00740
21/1 1.01160 1.02593 +/- 0.00685
22/1 1.04334 1.02738 +/- 0.00642
23/1 1.03105 1.02766 +/- 0.00591
24/1 1.01174 1.02653 +/- 0.00559
25/1 0.99844 1.02465 +/- 0.00553
26/1 1.02241 1.02451 +/- 0.00517
27/1 1.02904 1.02478 +/- 0.00487
28/1 1.02132 1.02459 +/- 0.00459
29/1 1.01384 1.02402 +/- 0.00438
30/1 1.03891 1.02477 +/- 0.00422
31/1 1.04092 1.02553 +/- 0.00409
32/1 1.00058 1.02440 +/- 0.00406
33/1 0.99940 1.02331 +/- 0.00403
34/1 0.98362 1.02166 +/- 0.00420
35/1 1.05358 1.02294 +/- 0.00422
36/1 0.99923 1.02202 +/- 0.00416
37/1 1.08491 1.02435 +/- 0.00463
38/1 1.01838 1.02414 +/- 0.00447
39/1 0.98567 1.02281 +/- 0.00451
40/1 1.05047 1.02374 +/- 0.00445
41/1 1.01993 1.02361 +/- 0.00431
42/1 1.01223 1.02326 +/- 0.00419
43/1 1.06259 1.02445 +/- 0.00423
44/1 1.01993 1.02432 +/- 0.00411
45/1 0.99233 1.02340 +/- 0.00409
46/1 0.98532 1.02234 +/- 0.00411
47/1 1.02513 1.02242 +/- 0.00400
48/1 1.01637 1.02226 +/- 0.00390
49/1 1.03215 1.02251 +/- 0.00381
50/1 1.01826 1.02241 +/- 0.00371
Creating state point statepoint.50.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 4.2397e-01 seconds
Reading cross sections = 4.0321e-01 seconds
Total time in simulation = 2.0407e+01 seconds
Time in transport only = 2.0154e+01 seconds
Time in inactive batches = 1.0937e+00 seconds
Time in active batches = 1.9314e+01 seconds
Time synchronizing fission bank = 7.8056e-03 seconds
Sampling source sites = 6.7223e-03 seconds
SEND/RECV source sites = 9.5783e-04 seconds
Time accumulating tallies = 9.2006e-02 seconds
Total time for finalization = 1.0890e-02 seconds
Total time elapsed = 2.0869e+01 seconds
Calculation Rate (inactive) = 22858.4 particles/second
Calculation Rate (active) = 5177.70 particles/second
============================> RESULTS <============================
k-effective (Collision) = 1.02207 +/- 0.00343
k-effective (Track-length) = 1.02241 +/- 0.00371
k-effective (Absorption) = 1.02408 +/- 0.00356
Combined k-effective = 1.02306 +/- 0.00307
Leakage Fraction = 0.00000 +/- 0.00000
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a StatePoint
object.
[16]:
# Load the last statepoint file
sp = openmc.StatePoint('statepoint.50.h5')
The statepoint is now ready to be analyzed by the Library
. We simply have to load the tallies from the statepoint into the Library
and our MGXS
objects will compute the cross sections for us under-the-hood.
[17]:
# Initialize MGXS Library with OpenMC statepoint data
mgxs_lib.load_from_statepoint(sp)
# Extrack the current tally separately
current_tally = sp.get_tally(name='current tally')
Using Tally Arithmetic to Compute the Delayed Neutron Precursor Concentrations¶
Finally, we illustrate how one can leverage OpenMC’s tally arithmetic data processing feature with MGXS
objects. The openmc.mgxs
module uses tally arithmetic to compute multi-group cross sections with automated uncertainty propagation. Each MGXS
object includes an xs_tally
attribute which is a “derived” Tally
based on the tallies needed to compute the cross section type of interest. These derived tallies can be used in subsequent tally arithmetic
operations. For example, we can use tally artithmetic to compute the delayed neutron precursor concentrations using the Beta
and DelayedNuFissionXS
objects. The delayed neutron precursor concentrations are modeled using the following equations:
[18]:
# Set the time constants for the delayed precursors (in seconds^-1)
precursor_halflife = np.array([55.6, 24.5, 16.3, 2.37, 0.424, 0.195])
precursor_lambda = math.log(2.0) / precursor_halflife
beta = mgxs_lib.get_mgxs(mesh, 'beta')
# Create a tally object with only the delayed group filter for the time constants
beta_filters = [f for f in beta.xs_tally.filters if type(f) is not openmc.DelayedGroupFilter]
lambda_tally = beta.xs_tally.summation(nuclides=beta.xs_tally.nuclides)
for f in beta_filters:
lambda_tally = lambda_tally.summation(filter_type=type(f), remove_filter=True) * 0. + 1.
# Set the mean of the lambda tally and reshape to account for nuclides and scores
lambda_tally._mean = precursor_lambda
lambda_tally._mean.shape = lambda_tally.std_dev.shape
# Set a total nuclide and lambda score
lambda_tally.nuclides = [openmc.Nuclide(name='total')]
lambda_tally.scores = ['lambda']
delayed_nu_fission = mgxs_lib.get_mgxs(mesh, 'delayed-nu-fission')
# Use tally arithmetic to compute the precursor concentrations
precursor_conc = beta.xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True) * \
delayed_nu_fission.xs_tally.summation(filter_type=openmc.EnergyFilter, remove_filter=True) / lambda_tally
# The difference is a derived tally which can generate Pandas DataFrames for inspection
precursor_conc.get_pandas_dataframe().head(10)
[18]:
mesh 1 | delayedgroup | nuclide | score | mean | std. dev. | |||
---|---|---|---|---|---|---|---|---|
x | y | z | ||||||
0 | 1 | 1 | 1 | 1 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000099 | 2.275247e-05 |
1 | 1 | 1 | 1 | 2 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.001260 | 2.852271e-04 |
2 | 1 | 1 | 1 | 3 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000800 | 1.795615e-04 |
3 | 1 | 1 | 1 | 4 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000630 | 1.397151e-04 |
4 | 1 | 1 | 1 | 5 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000023 | 4.861639e-06 |
5 | 1 | 1 | 1 | 6 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000002 | 3.879558e-07 |
6 | 2 | 1 | 1 | 1 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000091 | 2.062544e-05 |
7 | 2 | 1 | 1 | 2 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.001162 | 2.584797e-04 |
8 | 2 | 1 | 1 | 3 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000737 | 1.626991e-04 |
9 | 2 | 1 | 1 | 4 | total | (((delayed-nu-fission / nu-fission) * (delayed... | 0.000581 | 1.265708e-04 |
Another useful feature of the Python API is the ability to extract the surface currents for the interfaces and surfaces of a mesh. We can inspect the currents for the mesh by getting the pandas dataframe.
[19]:
current_tally.get_pandas_dataframe().head(10)
[19]:
mesh 1 | nuclide | score | mean | std. dev. | ||||
---|---|---|---|---|---|---|---|---|
x | y | z | surf | |||||
0 | 1 | 1 | 1 | x-min out | total | current | 0.00000 | 0.000000 |
1 | 1 | 1 | 1 | x-min in | total | current | 0.00000 | 0.000000 |
2 | 1 | 1 | 1 | x-max out | total | current | 0.03245 | 0.000677 |
3 | 1 | 1 | 1 | x-max in | total | current | 0.03180 | 0.000659 |
4 | 1 | 1 | 1 | y-min out | total | current | 0.00000 | 0.000000 |
5 | 1 | 1 | 1 | y-min in | total | current | 0.00000 | 0.000000 |
6 | 1 | 1 | 1 | y-max out | total | current | 0.03072 | 0.000677 |
7 | 1 | 1 | 1 | y-max in | total | current | 0.03104 | 0.000652 |
8 | 1 | 1 | 1 | z-min out | total | current | 0.00000 | 0.000000 |
9 | 1 | 1 | 1 | z-min in | total | current | 0.00000 | 0.000000 |
Cross Section Visualizations¶
In addition to inspecting the data in the tallies by getting the pandas dataframe, we can also plot the tally data on the domain mesh. Below is the delayed neutron fraction tallied in each mesh cell for each delayed group.
[20]:
# Extract the energy-condensed delayed neutron fraction tally
beta_by_group = beta.get_condensed_xs(one_group).xs_tally.summation(filter_type='energy', remove_filter=True)
beta_by_group.mean.shape = (17, 17, 6)
beta_by_group.mean[beta_by_group.mean == 0] = np.nan
# Plot the betas
plt.figure(figsize=(18,9))
fig = plt.subplot(231)
plt.imshow(beta_by_group.mean[:,:,0], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 1')
fig = plt.subplot(232)
plt.imshow(beta_by_group.mean[:,:,1], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 2')
fig = plt.subplot(233)
plt.imshow(beta_by_group.mean[:,:,2], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 3')
fig = plt.subplot(234)
plt.imshow(beta_by_group.mean[:,:,3], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 4')
fig = plt.subplot(235)
plt.imshow(beta_by_group.mean[:,:,4], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 5')
fig = plt.subplot(236)
plt.imshow(beta_by_group.mean[:,:,5], interpolation='none', cmap='jet')
plt.colorbar()
plt.title('Beta - delayed group 6')
[20]:
Text(0.5, 1.0, 'Beta - delayed group 6')

Multigroup Mode¶
Multigroup Mode Part I: Introduction¶
This Notebook illustrates the usage of OpenMC’s multi-group calculational mode with the Python API. This example notebook creates and executes the 2-D C5G7 benchmark model using the openmc.MGXSLibrary
class to create the supporting data library on the fly.
Generate MGXS Library¶
[1]:
import os
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import numpy as np
import openmc
%matplotlib inline
We will now create the multi-group library using data directly from Appendix A of the C5G7 benchmark documentation. All of the data below will be created at 294K, consistent with the benchmark.
This notebook will first begin by setting the group structure and building the groupwise data for UO2. As you can see, the cross sections are input in the order of increasing groups (or decreasing energy).
Note: The C5G7 benchmark uses transport-corrected cross sections. So the total cross section we input here will technically be the transport cross section.
[2]:
# Create a 7-group structure with arbitrary boundaries (the specific boundaries are unimportant)
groups = openmc.mgxs.EnergyGroups(np.logspace(-5, 7, 8))
uo2_xsdata = openmc.XSdata('uo2', groups)
uo2_xsdata.order = 0
# When setting the data let the object know you are setting the data for a temperature of 294K.
uo2_xsdata.set_total([1.77949E-1, 3.29805E-1, 4.80388E-1, 5.54367E-1,
3.11801E-1, 3.95168E-1, 5.64406E-1], temperature=294.)
uo2_xsdata.set_absorption([8.0248E-03, 3.7174E-3, 2.6769E-2, 9.6236E-2,
3.0020E-02, 1.1126E-1, 2.8278E-1], temperature=294.)
uo2_xsdata.set_fission([7.21206E-3, 8.19301E-4, 6.45320E-3, 1.85648E-2,
1.78084E-2, 8.30348E-2, 2.16004E-1], temperature=294.)
uo2_xsdata.set_nu_fission([2.005998E-2, 2.027303E-3, 1.570599E-2, 4.518301E-2,
4.334208E-2, 2.020901E-1, 5.257105E-1], temperature=294.)
uo2_xsdata.set_chi([5.87910E-1, 4.11760E-1, 3.39060E-4, 1.17610E-7,
0.00000E-0, 0.00000E-0, 0.00000E-0], temperature=294.)
We will now add the scattering matrix data.
Note: Most users familiar with deterministic transport libraries are already familiar with the idea of entering one scattering matrix for every order (i.e. scattering order as the outer dimension). However, the shape of OpenMC’s scattering matrix entry is instead [Incoming groups, Outgoing Groups, Scattering Order] to best enable other scattering representations. We will follow the more familiar approach in this notebook, and then use numpy’s numpy.rollaxis
function to change the ordering
to what we need (scattering order on the inner dimension).
[3]:
# The scattering matrix is ordered with incoming groups as rows and outgoing groups as columns
# (i.e., below the diagonal is up-scattering).
scatter_matrix = \
[[[1.27537E-1, 4.23780E-2, 9.43740E-6, 5.51630E-9, 0.00000E-0, 0.00000E-0, 0.00000E-0],
[0.00000E-0, 3.24456E-1, 1.63140E-3, 3.14270E-9, 0.00000E-0, 0.00000E-0, 0.00000E-0],
[0.00000E-0, 0.00000E-0, 4.50940E-1, 2.67920E-3, 0.00000E-0, 0.00000E-0, 0.00000E-0],
[0.00000E-0, 0.00000E-0, 0.00000E-0, 4.52565E-1, 5.56640E-3, 0.00000E-0, 0.00000E-0],
[0.00000E-0, 0.00000E-0, 0.00000E-0, 1.25250E-4, 2.71401E-1, 1.02550E-2, 1.00210E-8],
[0.00000E-0, 0.00000E-0, 0.00000E-0, 0.00000E-0, 1.29680E-3, 2.65802E-1, 1.68090E-2],
[0.00000E-0, 0.00000E-0, 0.00000E-0, 0.00000E-0, 0.00000E-0, 8.54580E-3, 2.73080E-1]]]
scatter_matrix = np.array(scatter_matrix)
scatter_matrix = np.rollaxis(scatter_matrix, 0, 3)
uo2_xsdata.set_scatter_matrix(scatter_matrix, temperature=294.)
Now that the UO2 data has been created, we can move on to the remaining materials using the same process.
However, we will actually skip repeating the above for now. Our simulation will instead use the c5g7.h5
file that has already been created using exactly the same logic as above, but for the remaining materials in the benchmark problem.
For now we will show how you would use the uo2_xsdata
information to create an openmc.MGXSLibrary
object and write to disk.
[4]:
# Initialize the library
mg_cross_sections_file = openmc.MGXSLibrary(groups)
# Add the UO2 data to it
mg_cross_sections_file.add_xsdata(uo2_xsdata)
# And write to disk
mg_cross_sections_file.export_to_hdf5('mgxs.h5')
Generate 2-D C5G7 Problem Input Files¶
To build the actual 2-D model, we will first begin by creating the materials.xml
file.
First we need to define materials that will be used in the problem. In other notebooks, either nuclides or elements were added to materials at the equivalent stage. We can do that in multi-group mode as well. However, multi-group cross-sections are sometimes provided as macroscopic cross-sections; the C5G7 benchmark data are macroscopic. In this case, we can instead use the Material.add_macroscopic
method to specify a macroscopic object. Unlike for nuclides and elements, we do not need
provide information on atom/weight percents as no number densities are needed.
When assigning macroscopic objects to a material, the density can still be scaled by setting the density to a value that is not 1.0. This would be useful, for example, when slightly perturbing the density of water due to a small change in temperature (while of course ignoring any resultant spectral shift). The density of a macroscopic dataset is set to 1.0 in the openmc.Material
object by default when a macroscopic dataset is used; so we will show its use the first time and then afterwards
it will not be required.
Aside from these differences, the following code is very similar to similar code in other OpenMC example Notebooks.
[5]:
# For every cross section data set in the library, assign an openmc.Macroscopic object to a material
materials = {}
for xs in ['uo2', 'mox43', 'mox7', 'mox87', 'fiss_chamber', 'guide_tube', 'water']:
materials[xs] = openmc.Material(name=xs)
materials[xs].set_density('macro', 1.)
materials[xs].add_macroscopic(xs)
Now we can go ahead and produce a materials.xml
file for use by OpenMC
[6]:
# Instantiate a Materials collection, register all Materials, and export to XML
materials_file = openmc.Materials(materials.values())
# Set the location of the cross sections file to our pre-written set
materials_file.cross_sections = 'c5g7.h5'
materials_file.export_to_xml()
Our next step will be to create the geometry information needed for our assembly and to write that to the geometry.xml
file.
We will begin by defining the surfaces, cells, and universes needed for each of the individual fuel pins, guide tubes, and fission chambers.
[7]:
# Create the surface used for each pin
pin_surf = openmc.ZCylinder(x0=0, y0=0, R=0.54, name='pin_surf')
# Create the cells which will be used to represent each pin type.
cells = {}
universes = {}
for material in materials.values():
# Create the cell for the material inside the cladding
cells[material.name] = openmc.Cell(name=material.name)
# Assign the half-spaces to the cell
cells[material.name].region = -pin_surf
# Register the material with this cell
cells[material.name].fill = material
# Repeat the above for the material outside the cladding (i.e., the moderator)
cell_name = material.name + '_moderator'
cells[cell_name] = openmc.Cell(name=cell_name)
cells[cell_name].region = +pin_surf
cells[cell_name].fill = materials['water']
# Finally add the two cells we just made to a Universe object
universes[material.name] = openmc.Universe(name=material.name)
universes[material.name].add_cells([cells[material.name], cells[cell_name]])
The next step is to take our universes (representing the different pin types) and lay them out in a lattice to represent the assembly types
[8]:
lattices = {}
# Instantiate the UO2 Lattice
lattices['UO2 Assembly'] = openmc.RectLattice(name='UO2 Assembly')
lattices['UO2 Assembly'].dimension = [17, 17]
lattices['UO2 Assembly'].lower_left = [-10.71, -10.71]
lattices['UO2 Assembly'].pitch = [1.26, 1.26]
u = universes['uo2']
g = universes['guide_tube']
f = universes['fiss_chamber']
lattices['UO2 Assembly'].universes = \
[[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, u, u, g, u, u, g, u, u, g, u, u, u, u, u],
[u, u, u, g, u, u, u, u, u, u, u, u, u, g, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, g, u, u, g, u, u, g, u, u, g, u, u, g, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, g, u, u, g, u, u, f, u, u, g, u, u, g, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, g, u, u, g, u, u, g, u, u, g, u, u, g, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, g, u, u, u, u, u, u, u, u, u, g, u, u, u],
[u, u, u, u, u, g, u, u, g, u, u, g, u, u, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u],
[u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u]]
# Create a containing cell and universe
cells['UO2 Assembly'] = openmc.Cell(name='UO2 Assembly')
cells['UO2 Assembly'].fill = lattices['UO2 Assembly']
universes['UO2 Assembly'] = openmc.Universe(name='UO2 Assembly')
universes['UO2 Assembly'].add_cell(cells['UO2 Assembly'])
# Instantiate the MOX Lattice
lattices['MOX Assembly'] = openmc.RectLattice(name='MOX Assembly')
lattices['MOX Assembly'].dimension = [17, 17]
lattices['MOX Assembly'].lower_left = [-10.71, -10.71]
lattices['MOX Assembly'].pitch = [1.26, 1.26]
m = universes['mox43']
n = universes['mox7']
o = universes['mox87']
g = universes['guide_tube']
f = universes['fiss_chamber']
lattices['MOX Assembly'].universes = \
[[m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m],
[m, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, m],
[m, n, n, n, n, g, n, n, g, n, n, g, n, n, n, n, m],
[m, n, n, g, n, o, o, o, o, o, o, o, n, g, n, n, m],
[m, n, n, n, o, o, o, o, o, o, o, o, o, n, n, n, m],
[m, n, g, o, o, g, o, o, g, o, o, g, o, o, g, n, m],
[m, n, n, o, o, o, o, o, o, o, o, o, o, o, n, n, m],
[m, n, n, o, o, o, o, o, o, o, o, o, o, o, n, n, m],
[m, n, g, o, o, g, o, o, f, o, o, g, o, o, g, n, m],
[m, n, n, o, o, o, o, o, o, o, o, o, o, o, n, n, m],
[m, n, n, o, o, o, o, o, o, o, o, o, o, o, n, n, m],
[m, n, g, o, o, g, o, o, g, o, o, g, o, o, g, n, m],
[m, n, n, n, o, o, o, o, o, o, o, o, o, n, n, n, m],
[m, n, n, g, n, o, o, o, o, o, o, o, n, g, n, n, m],
[m, n, n, n, n, g, n, n, g, n, n, g, n, n, n, n, m],
[m, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, m],
[m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m, m]]
# Create a containing cell and universe
cells['MOX Assembly'] = openmc.Cell(name='MOX Assembly')
cells['MOX Assembly'].fill = lattices['MOX Assembly']
universes['MOX Assembly'] = openmc.Universe(name='MOX Assembly')
universes['MOX Assembly'].add_cell(cells['MOX Assembly'])
# Instantiate the reflector Lattice
lattices['Reflector Assembly'] = openmc.RectLattice(name='Reflector Assembly')
lattices['Reflector Assembly'].dimension = [1,1]
lattices['Reflector Assembly'].lower_left = [-10.71, -10.71]
lattices['Reflector Assembly'].pitch = [21.42, 21.42]
lattices['Reflector Assembly'].universes = [[universes['water']]]
# Create a containing cell and universe
cells['Reflector Assembly'] = openmc.Cell(name='Reflector Assembly')
cells['Reflector Assembly'].fill = lattices['Reflector Assembly']
universes['Reflector Assembly'] = openmc.Universe(name='Reflector Assembly')
universes['Reflector Assembly'].add_cell(cells['Reflector Assembly'])
Let’s now create the core layout in a 3x3 lattice where each lattice position is one of the assemblies we just defined.
After that we can create the final cell to contain the entire core.
[9]:
lattices['Core'] = openmc.RectLattice(name='3x3 core lattice')
lattices['Core'].dimension= [3, 3]
lattices['Core'].lower_left = [-32.13, -32.13]
lattices['Core'].pitch = [21.42, 21.42]
r = universes['Reflector Assembly']
u = universes['UO2 Assembly']
m = universes['MOX Assembly']
lattices['Core'].universes = [[u, m, r],
[m, u, r],
[r, r, r]]
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-32.13, boundary_type='reflective')
max_x = openmc.XPlane(x0=+32.13, boundary_type='vacuum')
min_y = openmc.YPlane(y0=-32.13, boundary_type='vacuum')
max_y = openmc.YPlane(y0=+32.13, boundary_type='reflective')
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = lattices['Core']
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y
# Create root Universe
root_universe = openmc.Universe(name='root universe', universe_id=0)
root_universe.add_cell(root_cell)
Before we commit to the geometry, we should view it using the Python API’s plotting capability
[10]:
root_universe.plot(origin=(0., 0., 0.), width=(3 * 21.42, 3 * 21.42), pixels=(500, 500),
color_by='material')

OK, it looks pretty good, let’s go ahead and write the file
[11]:
# Create Geometry and set root Universe
geometry = openmc.Geometry(root_universe)
# Export to "geometry.xml"
geometry.export_to_xml()
We can now create the tally file information. The tallies will be set up to give us the pin powers in this notebook. We will do this with a mesh filter, with one mesh cell per pin.
[12]:
tallies_file = openmc.Tallies()
# Instantiate a tally Mesh
mesh = openmc.RegularMesh()
mesh.dimension = [17 * 2, 17 * 2]
mesh.lower_left = [-32.13, -10.71]
mesh.upper_right = [+10.71, +32.13]
# Instantiate tally Filter
mesh_filter = openmc.MeshFilter(mesh)
# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter]
tally.scores = ['fission']
# Add tally to collection
tallies_file.append(tally)
# Export all tallies to a "tallies.xml" file
tallies_file.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters for the settings.xml
file. Note the use of the energy_mode
attribute of our settings_file
object. This is used to tell OpenMC that we intend to run in multi-group mode instead of the default continuous-energy mode. If we didn’t specify this but our cross sections file was not a continuous-energy data set, then OpenMC would complain.
This will be a relatively coarse calculation with only 500,000 active histories. A benchmark-fidelity run would of course require many more!
[13]:
# OpenMC simulation parameters
batches = 150
inactive = 50
particles = 5000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
# Tell OpenMC this is a multi-group problem
settings_file.energy_mode = 'multi-group'
# Set the verbosity to 6 so we dont see output for every batch
settings_file.verbosity = 6
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-32.13, -10.71, -1e50, 10.71, 32.13, 1e50]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Tell OpenMC we want to run in eigenvalue mode
settings_file.run_mode = 'eigenvalue'
# Export to "settings.xml"
settings_file.export_to_xml()
Let’s go ahead and execute the simulation! You’ll notice that the output for multi-group mode is exactly the same as for continuous-energy. The differences are all under the hood.
[14]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2017 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.8.0
Git SHA1 | 966169de084fcfda3a5aaca3edc0065c8caf6bbc
Date/Time | 2017-03-09 08:18:02
OpenMP Threads | 8
Reading settings XML file...
Reading geometry XML file...
Reading materials XML file...
Reading cross sections HDF5 file...
Reading tallies XML file...
Loading cross section data...
Loading uo2 data...
Loading mox43 data...
Loading mox7 data...
Loading mox87 data...
Loading fiss_chamber data...
Loading guide_tube data...
Loading water data...
Building neighboring cells lists for each surface...
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Creating state point statepoint.150.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 1.3630E-01 seconds
Reading cross sections = 3.0827E-02 seconds
Total time in simulation = 8.9648E+00 seconds
Time in transport only = 8.2752E+00 seconds
Time in inactive batches = 2.4798E+00 seconds
Time in active batches = 6.4849E+00 seconds
Time synchronizing fission bank = 1.4553E-02 seconds
Sampling source sites = 1.0318E-02 seconds
SEND/RECV source sites = 4.0840E-03 seconds
Time accumulating tallies = 4.2427E-04 seconds
Total time for finalization = 1.7081E-02 seconds
Total time elapsed = 9.1340E+00 seconds
Calculation Rate (inactive) = 1.00813E+05 neutrons/second
Calculation Rate (active) = 77101.8 neutrons/second
============================> RESULTS <============================
k-effective (Collision) = 1.18880 +/- 0.00179
k-effective (Track-length) = 1.18853 +/- 0.00244
k-effective (Absorption) = 1.18601 +/- 0.00111
Combined k-effective = 1.18628 +/- 0.00111
Leakage Fraction = 0.00175 +/- 0.00006
[14]:
0
Results Visualization¶
Now that we have run the simulation, let’s look at the fission rate and flux tallies that we tallied.
[15]:
# Load the last statepoint file and keff value
sp = openmc.StatePoint('statepoint.' + str(batches) + '.h5')
# Get the OpenMC pin power tally data
mesh_tally = sp.get_tally(name='mesh tally')
fission_rates = mesh_tally.get_values(scores=['fission'])
# Reshape array to 2D for plotting
fission_rates.shape = mesh.dimension
# Normalize to the average pin power
fission_rates /= np.mean(fission_rates[fission_rates > 0.])
# Force zeros to be NaNs so their values are not included when matplotlib calculates
# the color scale
fission_rates[fission_rates == 0.] = np.nan
# Plot the pin powers and the fluxes
plt.figure()
plt.imshow(fission_rates, interpolation='none', cmap='jet', origin='lower')
plt.colorbar()
plt.title('Pin Powers')
plt.show()

There we have it! We have just successfully run the C5G7 benchmark model!
Multigroup Mode Part II: MGXS Library Generation with OpenMC¶
The previous Notebook in this series used multi-group mode to perform a calculation with previously defined cross sections. However, in many circumstances the multi-group data is not given and one must instead generate the cross sections for the specific application (or at least verify the use of cross sections from another application).
This Notebook illustrates the use of the openmc.mgxs.Library class specifically for the calculation of MGXS to be used in OpenMC’s multi-group mode. This example notebook is therefore very similar to the MGXS Part III notebook, except OpenMC is used as the multi-group solver instead of OpenMOC.
During this process, this notebook will illustrate the following features:
- Calculation of multi-group cross sections for a fuel assembly
- Automated creation and storage of MGXS with openmc.mgxs.Library
- Steady-state pin-by-pin fission rates comparison between continuous-energy and multi-group OpenMC.
- Modification of the scattering data in the library to show the flexibility of the multi-group solver
Generate Input Files¶
[1]:
import matplotlib.pyplot as plt
import numpy as np
import os
import openmc
%matplotlib inline
We will begin by creating three materials for the fuel, water, and cladding of the fuel pins.
[2]:
# 1.6% enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_element('U', 1., enrichment=1.6)
fuel.add_element('O', 2.)
# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_element('Zr', 1.)
# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_element('H', 4.9457e-2)
water.add_element('O', 2.4732e-2)
water.add_element('B', 8.0042e-6)
With our three materials, we can now create a Materials object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials object
materials_file = openmc.Materials((fuel, zircaloy, water))
# Export to "materials.xml"
materials_file.export_to_xml()
Now let’s move on to the geometry. This problem will be a square array of fuel pins and control rod guide tubes for which we can use OpenMC’s lattice/universe feature. The basic universe will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces for fuel and clad, as well as the outer bounding surfaces of the problem.
[4]:
# Create cylinders for the fuel and clad
# The x0 and y0 parameters (0. and 0.) are the default values for an
# openmc.ZCylinder object. We could therefore leave them out to no effect
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.45720)
# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='reflective')
min_y = openmc.YPlane(y0=-10.71, boundary_type='reflective')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10., boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10., boundary_type='reflective')
With the surfaces defined, we can now construct a fuel pin cell from cells that are defined by intersections of half-spaces created by the surfaces.
[5]:
# Create a Universe to encapsulate a fuel pin
fuel_pin_universe = openmc.Universe(name='1.6% Fuel Pin')
# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
fuel_pin_universe.add_cell(fuel_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
fuel_pin_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
fuel_pin_universe.add_cell(moderator_cell)
Likewise, we can construct a control rod guide tube with the same surfaces.
[6]:
# Create a Universe to encapsulate a control rod guide tube
guide_tube_universe = openmc.Universe(name='Guide Tube')
# Create guide tube Cell
guide_tube_cell = openmc.Cell(name='Guide Tube Water')
guide_tube_cell.fill = water
guide_tube_cell.region = -fuel_outer_radius
guide_tube_universe.add_cell(guide_tube_cell)
# Create a clad Cell
clad_cell = openmc.Cell(name='Guide Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
guide_tube_universe.add_cell(clad_cell)
# Create a moderator Cell
moderator_cell = openmc.Cell(name='Guide Tube Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
guide_tube_universe.add_cell(moderator_cell)
Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch.
[7]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel Assembly')
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2
Next, we create a NumPy array of fuel pin and guide tube universes for the lattice.
[8]:
# Create array indices for guide tube locations in lattice
template_x = np.array([5, 8, 11, 3, 13, 2, 5, 8, 11, 14, 2, 5, 8,
11, 14, 2, 5, 8, 11, 14, 3, 13, 5, 8, 11])
template_y = np.array([2, 2, 2, 3, 3, 5, 5, 5, 5, 5, 8, 8, 8, 8,
8, 11, 11, 11, 11, 11, 13, 13, 14, 14, 14])
# Initialize an empty 17x17 array of the lattice universes
universes = np.empty((17, 17), dtype=openmc.Universe)
# Fill the array with the fuel pin and guide tube universes
universes[:, :] = fuel_pin_universe
universes[template_x, template_y] = guide_tube_universe
# Store the array of universes in the lattice
assembly.universes = universes
OpenMC requires that there is a “root” universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe.
[9]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = assembly
# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z
# Create root Universe
root_universe = openmc.Universe(name='root universe', universe_id=0)
root_universe.add_cell(root_cell)
Before proceeding lets check the geometry.
[10]:
root_universe.plot(origin=(0., 0., 0.), width=(21.42, 21.42), pixels=(500, 500), color_by='material')
[10]:
<matplotlib.image.AxesImage at 0x7f6d864d44a8>

Looks good!
We now must create a geometry that is assigned a root universe and export it to XML.
[11]:
# Create Geometry and set root universe
geometry = openmc.Geometry(root_universe)
# Export to "geometry.xml"
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters.
[12]:
# OpenMC simulation parameters
batches = 600
inactive = 50
particles = 3000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': False}
settings_file.run_mode = 'eigenvalue'
settings_file.verbosity = 4
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Create an MGXS Library¶
Now we are ready to generate multi-group cross sections! First, let’s define a 2-group structure using the built-in EnergyGroups class.
[13]:
# Instantiate a 2-group EnergyGroups object
groups = openmc.mgxs.EnergyGroups([0., 0.625, 20.0e6])
Next, we will instantiate an openmc.mgxs.Library for the energy groups with our the fuel assembly geometry.
[14]:
# Initialize a 2-group MGXS Library for OpenMC
mgxs_lib = openmc.mgxs.Library(geometry)
mgxs_lib.energy_groups = groups
Now, we must specify to the Library which types of cross sections to compute. OpenMC’s multi-group mode can accept isotropic flux-weighted cross sections or angle-dependent cross sections, as well as supporting anisotropic scattering represented by either Legendre polynomials, histogram, or tabular angular distributions. We will create the following multi-group cross sections needed to run an OpenMC simulation to verify the accuracy of our cross sections: “total”, “absorption”, “nu-fission”, ‘“fission”, “nu-scatter matrix”, “multiplicity matrix”, and “chi”.
The “multiplicity matrix” type is a relatively rare cross section type. This data is needed to provide OpenMC’s multi-group mode with additional information needed to accurately treat scattering multiplication (i.e., (n,xn) reactions)), including how this multiplication varies depending on both incoming and outgoing neutron energies.
[15]:
# Specify multi-group cross section types to compute
mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission',
'nu-scatter matrix', 'multiplicity matrix', 'chi']
Now we must specify the type of domain over which we would like the Library
to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the Library
supports “material”, “cell”, “universe”, and “mesh” domain types. In this simple example, we wish to compute multi-group cross sections only for each material and therefore will use a “material” domain type.
NOTE: By default, the Library
class will instantiate MGXS
objects for each and every domain (material, cell, universe, or mesh) in the geometry of interest. However, one may specify a subset of these domains to the Library.domains
property.
[16]:
# Specify a "cell" domain type for the cross section tally filters
mgxs_lib.domain_type = "material"
# Specify the cell domains over which to compute multi-group cross sections
mgxs_lib.domains = geometry.get_all_materials().values()
We will instruct the library to not compute cross sections on a nuclide-by-nuclide basis, and instead to focus on generating material-specific macroscopic cross sections.
NOTE: The default value of the by_nuclide
parameter is False
, so the following step is not necessary but is included for illustrative purposes.
[17]:
# Do not compute cross sections on a nuclide-by-nuclide basis
mgxs_lib.by_nuclide = False
Now we will set the scattering order that we wish to use. For this problem we will use P3 scattering. A warning is expected telling us that the default behavior (a P0 correction on the scattering data) is over-ridden by our choice of using a Legendre expansion to treat anisotropic scattering.
[18]:
# Set the Legendre order to 3 for P3 scattering
mgxs_lib.legendre_order = 3
/home/nelsonag/git/openmc/openmc/mgxs/library.py:412: RuntimeWarning: The P0 correction will be ignored since the scattering order 0 is greater than zero
warn(msg, RuntimeWarning)
Now that the Library
has been setup let’s verify that it contains the types of cross sections which meet the needs of OpenMC’s multi-group solver. Note that this step is done automatically when writing the Multi-Group Library file later in the process (as part of mgxs_lib.write_mg_library()
), but it is a good practice to also run this before spending all the time running OpenMC to generate the cross sections.
If no error is raised, then we have a good set of data.
[19]:
# Check the library - if no errors are raised, then the library is satisfactory.
mgxs_lib.check_library_for_openmc_mgxs()
Great, now we can use the Library
to construct the tallies needed to compute all of the requested multi-group cross sections in each domain.
[20]:
# Construct all tallies needed for the multi-group cross section library
mgxs_lib.build_library()
The tallies can now be exported to a “tallies.xml” input file for OpenMC.
NOTE: At this point the Library
has constructed nearly 100 distinct Tally objects. The overhead to tally in OpenMC scales as O(N) for N tallies, which can become a bottleneck for large tally datasets. To compensate for this, the Python API’s Tally
, Filter
and Tallies
classes allow for the smart merging of tallies when possible. The Library
class supports this runtime optimization with the use of the optional merge
parameter (False
by default) for the
Library.add_to_tallies_file(...)
method, as shown below.
[21]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
mgxs_lib.add_to_tallies_file(tallies_file, merge=True)
In addition, we instantiate a fission rate mesh tally that we will eventually use to compare with the corresponding multi-group results.
[22]:
# Instantiate a tally Mesh
mesh = openmc.RegularMesh()
mesh.dimension = [17, 17]
mesh.lower_left = [-10.71, -10.71]
mesh.upper_right = [+10.71, +10.71]
# Instantiate tally Filter
mesh_filter = openmc.MeshFilter(mesh)
# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter]
tally.scores = ['fission']
# Add tally to collection
tallies_file.append(tally, merge=True)
# Export all tallies to a "tallies.xml" file
tallies_file.export_to_xml()
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=66.
warn(msg, IDWarning)
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=11.
warn(msg, IDWarning)
Time to run the calculation and get our results!
[23]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2018 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.10.0
Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416
Date/Time | 2018-04-22 15:02:43
OpenMP Threads | 8
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 1.16513 +/- 0.00090
k-effective (Track-length) = 1.16337 +/- 0.00104
k-effective (Absorption) = 1.16479 +/- 0.00080
Combined k-effective = 1.16460 +/- 0.00068
Leakage Fraction = 0.00000 +/- 0.00000
To make sure the results we need are available after running the multi-group calculation, we will now rename the statepoint and summary files.
[24]:
# Move the statepoint File
ce_spfile = './statepoint_ce.h5'
os.rename('statepoint.' + str(batches) + '.h5', ce_spfile)
# Move the Summary file
ce_sumfile = './summary_ce.h5'
os.rename('summary.h5', ce_sumfile)
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. Let’s begin by loading the StatePoint file.
[25]:
# Load the statepoint file
sp = openmc.StatePoint(ce_spfile, autolink=False)
# Load the summary file in its new location
su = openmc.Summary(ce_sumfile)
sp.link_with_summary(su)
The statepoint is now ready to be analyzed by the Library
. We simply have to load the tallies from the statepoint into the Library
and our MGXS
objects will compute the cross sections for us under-the-hood.
[26]:
# Initialize MGXS Library with OpenMC statepoint data
mgxs_lib.load_from_statepoint(sp)
The next step will be to prepare the input for OpenMC to use our newly created multi-group data.
Multi-Group OpenMC Calculation¶
Library
to produce a multi-group cross section data set for use by the OpenMC multi-group solver.Library
class is designed to gracefully handle these scenarios.[27]:
# Create a MGXS File which can then be written to disk
mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=['fuel', 'zircaloy', 'water'])
# Write the file to disk using the default filename of "mgxs.h5"
mgxs_file.export_to_hdf5()
OpenMC’s multi-group mode uses the same input files as does the continuous-energy mode (materials, geometry, settings, plots, and tallies file). Differences would include the use of a flag to tell the code to use multi-group transport, a location of the multi-group library file, and any changes needed in the materials.xml and geometry.xml files to re-define materials as necessary. The materials and geometry file changes could be necessary if materials or their nuclide/element/macroscopic constituents need to be renamed.
In this example we have created macroscopic cross sections (by material), and thus we will need to change the material definitions accordingly.
First we will create the new materials.xml file.
[28]:
# Re-define our materials to use the multi-group macroscopic data
# instead of the continuous-energy data.
# 1.6% enriched fuel UO2
fuel_mg = openmc.Material(name='UO2', material_id=1)
fuel_mg.add_macroscopic('fuel')
# cladding
zircaloy_mg = openmc.Material(name='Clad', material_id=2)
zircaloy_mg.add_macroscopic('zircaloy')
# moderator
water_mg = openmc.Material(name='Water', material_id=3)
water_mg.add_macroscopic('water')
# Finally, instantiate our Materials object
materials_file = openmc.Materials((fuel_mg, zircaloy_mg, water_mg))
# Set the location of the cross sections file
materials_file.cross_sections = 'mgxs.h5'
# Export to "materials.xml"
materials_file.export_to_xml()
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=1.
warn(msg, IDWarning)
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=2.
warn(msg, IDWarning)
/home/nelsonag/git/openmc/openmc/mixin.py:71: IDWarning: Another Material instance already exists with id=3.
warn(msg, IDWarning)
No geometry file neeeds to be written as the continuous-energy file is correctly defined for the multi-group case as well.
Next, we can make the changes we need to the simulation parameters. These changes are limited to telling OpenMC to run a multi-group vice contrinuous-energy calculation.
[29]:
# Set the energy mode
settings_file.energy_mode = 'multi-group'
# Export to "settings.xml"
settings_file.export_to_xml()
Lets clear the tallies file so it doesn’t include tallies for re-generating a multi-group library, but then put back in a tally for the fission mesh.
[30]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
# Add fission and flux mesh to tally for plotting using the same mesh we've already defined
mesh_tally = openmc.Tally(name='mesh tally')
mesh_tally.filters = [openmc.MeshFilter(mesh)]
mesh_tally.scores = ['fission']
tallies_file.append(mesh_tally)
# Export to "tallies.xml"
tallies_file.export_to_xml()
Before running the calculation let’s visually compare a subset of the newly-generated multi-group cross section data to the continuous-energy data. We will do this using the cross section plotting functionality built-in to the OpenMC Python API.
[31]:
# First lets plot the fuel data
# We will first add the continuous-energy data
fig = openmc.plot_xs(fuel, ['total'])
# We will now add in the corresponding multi-group data and show the result
openmc.plot_xs(fuel_mg, ['total'], plot_CE=False, mg_cross_sections='mgxs.h5', axis=fig.axes[0])
fig.axes[0].legend().set_visible(False)
plt.show()
plt.close()
# Then repeat for the zircaloy data
fig = openmc.plot_xs(zircaloy, ['total'])
openmc.plot_xs(zircaloy_mg, ['total'], plot_CE=False, mg_cross_sections='mgxs.h5', axis=fig.axes[0])
fig.axes[0].legend().set_visible(False)
plt.show()
plt.close()
# And finally repeat for the water data
fig = openmc.plot_xs(water, ['total'])
openmc.plot_xs(water_mg, ['total'], plot_CE=False, mg_cross_sections='mgxs.h5', axis=fig.axes[0])
fig.axes[0].legend().set_visible(False)
plt.show()
plt.close()



At this point, the problem is set up and we can run the multi-group calculation.
[32]:
# Run the Multi-Group OpenMC Simulation
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2018 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.10.0
Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416
Date/Time | 2018-04-22 15:04:03
OpenMP Threads | 8
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 1.16541 +/- 0.00086
k-effective (Track-length) = 1.16590 +/- 0.00096
k-effective (Absorption) = 1.16469 +/- 0.00046
Combined k-effective = 1.16480 +/- 0.00045
Leakage Fraction = 0.00000 +/- 0.00000
Results Comparison¶
Now we can compare the multi-group and continuous-energy results.
We will begin by loading the multi-group statepoint file we just finished writing and extracting the calculated keff.
[33]:
# Move the StatePoint File
mg_spfile = './statepoint_mg.h5'
os.rename('statepoint.' + str(batches) + '.h5', mg_spfile)
# Move the Summary file
mg_sumfile = './summary_mg.h5'
os.rename('summary.h5', mg_sumfile)
# Rename and then load the last statepoint file and keff value
mgsp = openmc.StatePoint(mg_spfile, autolink=False)
# Load the summary file in its new location
mgsu = openmc.Summary(mg_sumfile)
mgsp.link_with_summary(mgsu)
# Get keff
mg_keff = mgsp.k_combined
Next, we can load the continuous-energy eigenvalue for comparison.
[34]:
ce_keff = sp.k_combined
Lets compare the two eigenvalues, including their bias
[35]:
bias = 1.0E5 * (ce_keff - mg_keff)
print('Continuous-Energy keff = {0:1.6f}'.format(ce_keff))
print('Multi-Group keff = {0:1.6f}'.format(mg_keff))
print('bias [pcm]: {0:1.1f}'.format(bias.nominal_value))
Continuous-Energy keff = 1.164600+/-0.000677
Multi-Group keff = 1.164805+/-0.000448
bias [pcm]: -20.4
This shows a small but nontrivial pcm bias between the two methods. Some degree of mismatch is expected simply to the very few histories being used in these example problems. An additional mismatch is always inherent in the practical application of multi-group theory due to the high degree of approximations inherent in that method.
Pin Power Visualizations¶
Next we will visualize the pin power results obtained from both the Continuous-Energy and Multi-Group OpenMC calculations.
First, we extract volume-integrated fission rates from the Multi-Group calculation’s mesh fission rate tally for each pin cell in the fuel assembly.
[36]:
# Get the OpenMC fission rate mesh tally data
mg_mesh_tally = mgsp.get_tally(name='mesh tally')
mg_fission_rates = mg_mesh_tally.get_values(scores=['fission'])
# Reshape array to 2D for plotting
mg_fission_rates.shape = (17,17)
# Normalize to the average pin power
mg_fission_rates /= np.mean(mg_fission_rates[mg_fission_rates > 0.])
We can now do the same for the Continuous-Energy results.
[37]:
# Get the OpenMC fission rate mesh tally data
ce_mesh_tally = sp.get_tally(name='mesh tally')
ce_fission_rates = ce_mesh_tally.get_values(scores=['fission'])
# Reshape array to 2D for plotting
ce_fission_rates.shape = (17,17)
# Normalize to the average pin power
ce_fission_rates /= np.mean(ce_fission_rates[ce_fission_rates > 0.])
Now we can easily use Matplotlib to visualize the two fission rates side-by-side.
[38]:
# Force zeros to be NaNs so their values are not included when matplotlib calculates
# the color scale
ce_fission_rates[ce_fission_rates == 0.] = np.nan
mg_fission_rates[mg_fission_rates == 0.] = np.nan
# Plot the CE fission rates in the left subplot
fig = plt.subplot(121)
plt.imshow(ce_fission_rates, interpolation='none', cmap='jet')
plt.title('Continuous-Energy Fission Rates')
# Plot the MG fission rates in the right subplot
fig2 = plt.subplot(122)
plt.imshow(mg_fission_rates, interpolation='none', cmap='jet')
plt.title('Multi-Group Fission Rates')
[38]:
<matplotlib.text.Text at 0x7f6db0a885c0>

These figures really indicate that more histories are probably necessary when trying to achieve a fully converged solution, but hey, this is good enough for our example!
Scattering Anisotropy Treatments¶
We will next show how we can work with the scattering angular distributions. OpenMC’s MG solver has the capability to use group-to-group angular distributions which are represented as any of the following: a truncated Legendre series of up to the 10th order, a histogram distribution, and a tabular distribution. Any combination of these representations can be used by OpenMC during the transport process, so long as all constituents of a given material use the same representation. This means it is possible to have water represented by a tabular distribution and fuel represented by a Legendre if so desired.
Note: To have the highest runtime performance OpenMC natively converts Legendre series to a tabular distribution before the transport begins. This default functionality can be turned off with the tabular_legendre
element of the settings.xml
file (or for the Python API, the openmc.Settings.tabular_legendre
attribute).
This section will examine the following: - Re-run the MG-mode calculation with P0 scattering everywhere using the openmc.Settings.max_order
attribute - Re-run the problem with only the water represented with P3 scattering and P0 scattering for the remaining materials using the Python API’s ability to convert between formats.
Global P0 Scattering¶
First we begin by re-running with P0 scattering (i.e., isotropic) everywhere. If a global maximum order is requested, the most effective way to do this is to use the max_order
attribute of our openmc.Settings
object.
[39]:
# Set the maximum scattering order to 0 (i.e., isotropic scattering)
settings_file.max_order = 0
# Export to "settings.xml"
settings_file.export_to_xml()
Now we can re-run OpenMC to obtain our results
[40]:
# Run the Multi-Group OpenMC Simulation
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2018 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.10.0
Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416
Date/Time | 2018-04-22 15:04:39
OpenMP Threads | 8
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 1.16379 +/- 0.00090
k-effective (Track-length) = 1.16469 +/- 0.00101
k-effective (Absorption) = 1.16315 +/- 0.00052
Combined k-effective = 1.16335 +/- 0.00050
Leakage Fraction = 0.00000 +/- 0.00000
And then get the eigenvalue differences from the Continuous-Energy and P3 MG solution
[41]:
# Move the statepoint File
mgp0_spfile = './statepoint_mg_p0.h5'
os.rename('statepoint.' + str(batches) + '.h5', mgp0_spfile)
# Move the Summary file
mgp0_sumfile = './summary_mg_p0.h5'
os.rename('summary.h5', mgp0_sumfile)
# Load the last statepoint file and keff value
mgsp_p0 = openmc.StatePoint(mgp0_spfile, autolink=False)
# Get keff
mg_p0_keff = mgsp_p0.k_combined
bias_p0 = 1.0E5 * (ce_keff - mg_p0_keff)
print('P3 bias [pcm]: {0:1.1f}'.format(bias.nominal_value))
print('P0 bias [pcm]: {0:1.1f}'.format(bias_p0.nominal_value))
P3 bias [pcm]: -20.4
P0 bias [pcm]: 125.1
Mixed Scattering Representations¶
OpenMC’s Multi-Group mode also includes a feature where not every data in the library is required to have the same scattering treatment. For example, we could represent the water with P3 scattering, and the fuel and cladding with P0 scattering. This series will show how this can be done.
First we will convert the data to P0 scattering, unless its water, then we will leave that as P3 data.
[42]:
# Convert the zircaloy and fuel data to P0 scattering
for i, xsdata in enumerate(mgxs_file.xsdatas):
if xsdata.name != 'water':
mgxs_file.xsdatas[i] = xsdata.convert_scatter_format('legendre', 0)
We can also use whatever scattering format that we want for the materials in the library. As an example, we will take this P0 data and convert zircaloy to a histogram anisotropic scattering format and the fuel to a tabular anisotropic scattering format
[43]:
# Convert the formats as discussed
for i, xsdata in enumerate(mgxs_file.xsdatas):
if xsdata.name == 'zircaloy':
mgxs_file.xsdatas[i] = xsdata.convert_scatter_format('histogram', 2)
elif xsdata.name == 'fuel':
mgxs_file.xsdatas[i] = xsdata.convert_scatter_format('tabular', 2)
mgxs_file.export_to_hdf5('mgxs.h5')
Finally we will re-set our max_order
parameter of our openmc.Settings
object to our maximum order so that OpenMC will use whatever scattering data is available in the library.
After we do this we can re-run the simulation.
[44]:
settings_file.max_order = None
# Export to "settings.xml"
settings_file.export_to_xml()
# Run the Multi-Group OpenMC Simulation
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2018 Massachusetts Institute of Technology
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.10.0
Git SHA1 | 6c2d82a4d7dfe10312329d5969568fc03a698416
Date/Time | 2018-04-22 15:05:16
OpenMP Threads | 8
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 1.16471 +/- 0.00093
k-effective (Track-length) = 1.16412 +/- 0.00106
k-effective (Absorption) = 1.16449 +/- 0.00050
Combined k-effective = 1.16441 +/- 0.00049
Leakage Fraction = 0.00000 +/- 0.00000
For a final step we can again obtain the eigenvalue differences from this case and compare with the same from the P3 MG solution
[45]:
# Load the last statepoint file and keff value
mgsp_mixed = openmc.StatePoint('./statepoint.' + str(batches) + '.h5')
mg_mixed_keff = mgsp_mixed.k_combined
bias_mixed = 1.0E5 * (ce_keff - mg_mixed_keff)
print('P3 bias [pcm]: {0:1.1f}'.format(bias.nominal_value))
print('Mixed Scattering bias [pcm]: {0:1.1f}'.format(bias_mixed.nominal_value))
P3 bias [pcm]: -20.4
Mixed Scattering bias [pcm]: 19.5
Our tests in this section showed the flexibility of data formatting within OpenMC’s multi-group mode: every material can be represented with its own format with the approximations that make the most sense. Now, as you’ll see above, the runtimes from our P3, P0, and mixed cases are not significantly different and therefore this might not be a useful strategy for multi-group Monte Carlo. However, this capability provides a useful benchmark for the accuracy hit one may expect due to these scattering approximations before implementing this generality in a deterministic solver where the runtime savings are more significant.
NOTE: The biases obtained above with P3, P0, and mixed representations do not necessarily reflect the inherent accuracies of the options. These cases were not run with a sufficient number of histories to truly differentiate methods improvement from statistical noise.
Multigroup Mode Part III: Advanced Feature Showcase¶
This Notebook illustrates the use of the the more advanced features of OpenMC’s multi-group mode and the openmc.mgxs.Library class. During this process, this notebook will illustrate the following features:
- Calculation of multi-group cross sections for a simplified BWR 8x8 assembly with isotropic and angle-dependent MGXS.
- Automated creation and storage of MGXS with openmc.mgxs.Library
- Fission rate comparison between continuous-energy and the two multi-group OpenMC cases.
To avoid focusing on unimportant details, the BWR assembly in this notebook is greatly simplified. The descriptions which follow will point out some areas of simplification.
Generate Input Files¶
[1]:
import os
import matplotlib.pyplot as plt
import numpy as np
import openmc
%matplotlib inline
We will be running a rodded 8x8 assembly with Gadolinia fuel pins. Let’s start by creating the materials that we will use later.
Material Definition Simplifications:
- This model will be run at room temperature so the NNDC ENDF-B/VII.1 data set can be used but the water density will be representative of a module with around 20% voiding. This water density will be non-physically used in all regions of the problem.
- Steel is composed of more than just iron, but we will only treat it as such here.
[2]:
materials = {}
# Fuel
materials['Fuel'] = openmc.Material(name='Fuel')
materials['Fuel'].set_density('g/cm3', 10.32)
materials['Fuel'].add_element('O', 2)
materials['Fuel'].add_element('U', 1, enrichment=3.)
# Gadolinia bearing fuel
materials['Gad'] = openmc.Material(name='Gad')
materials['Gad'].set_density('g/cm3', 10.23)
materials['Gad'].add_element('O', 2)
materials['Gad'].add_element('U', 1, enrichment=3.)
materials['Gad'].add_element('Gd', .02)
# Zircaloy
materials['Zirc2'] = openmc.Material(name='Zirc2')
materials['Zirc2'].set_density('g/cm3', 6.55)
materials['Zirc2'].add_element('Zr', 1)
# Boiling Water
materials['Water'] = openmc.Material(name='Water')
materials['Water'].set_density('g/cm3', 0.6)
materials['Water'].add_element('H', 2)
materials['Water'].add_element('O', 1)
# Boron Carbide for the Control Rods
materials['B4C'] = openmc.Material(name='B4C')
materials['B4C'].set_density('g/cm3', 0.7 * 2.52)
materials['B4C'].add_element('B', 4)
materials['B4C'].add_element('C', 1)
# Steel
materials['Steel'] = openmc.Material(name='Steel')
materials['Steel'].set_density('g/cm3', 7.75)
materials['Steel'].add_element('Fe', 1)
We can now create a Materials object that can be exported to an actual XML file.
[3]:
# Instantiate a Materials object
materials_file = openmc.Materials(materials.values())
# Export to "materials.xml"
materials_file.export_to_xml()
Now let’s move on to the geometry. The first step is to define some constants which will be used to set our dimensions and then we can start creating the surfaces and regions for the problem, the 8x8 lattice, the rods and the control blade.
Before proceeding let’s discuss some simplifications made to the problem geometry: - To enable the use of an equal-width mesh for running the multi-group calculations, the intra-assembly gap was increased to the same size as the pitch of the 8x8 fuel lattice - The can is neglected - The pin-in-water geometry for the control blade is ignored and instead the blade is a solid block of B4C - Rounded corners are ignored - There is no cladding for the water rod
[4]:
# Set constants for the problem and assembly dimensions
fuel_rad = 0.53213
clad_rad = 0.61341
Np = 8
pin_pitch = 1.6256
length = float(Np + 2) * pin_pitch
assembly_width = length - 2. * pin_pitch
rod_thick = 0.47752 / 2. + 0.14224
rod_span = 7. * pin_pitch
surfaces = {}
# Create boundary planes to surround the geometry
surfaces['Global x-'] = openmc.XPlane(0., boundary_type='reflective')
surfaces['Global x+'] = openmc.XPlane(length, boundary_type='reflective')
surfaces['Global y-'] = openmc.YPlane(0., boundary_type='reflective')
surfaces['Global y+'] = openmc.YPlane(length, boundary_type='reflective')
# Create cylinders for the fuel and clad
surfaces['Fuel Radius'] = openmc.ZCylinder(r=fuel_rad)
surfaces['Clad Radius'] = openmc.ZCylinder(r=clad_rad)
surfaces['Assembly x-'] = openmc.XPlane(pin_pitch)
surfaces['Assembly x+'] = openmc.XPlane(length - pin_pitch)
surfaces['Assembly y-'] = openmc.YPlane(pin_pitch)
surfaces['Assembly y+'] = openmc.YPlane(length - pin_pitch)
# Set surfaces for the control blades
surfaces['Top Blade y-'] = openmc.YPlane(length - rod_thick)
surfaces['Top Blade x-'] = openmc.XPlane(pin_pitch)
surfaces['Top Blade x+'] = openmc.XPlane(rod_span)
surfaces['Left Blade x+'] = openmc.XPlane(rod_thick)
surfaces['Left Blade y-'] = openmc.YPlane(length - rod_span)
surfaces['Left Blade y+'] = openmc.YPlane(9. * pin_pitch)
With the surfaces defined, we can now construct regions with these surfaces before we use those to create cells
[5]:
# Set regions for geometry building
regions = {}
regions['Global'] = \
(+surfaces['Global x-'] & -surfaces['Global x+'] &
+surfaces['Global y-'] & -surfaces['Global y+'])
regions['Assembly'] = \
(+surfaces['Assembly x-'] & -surfaces['Assembly x+'] &
+surfaces['Assembly y-'] & -surfaces['Assembly y+'])
regions['Fuel'] = -surfaces['Fuel Radius']
regions['Clad'] = +surfaces['Fuel Radius'] & -surfaces['Clad Radius']
regions['Water'] = +surfaces['Clad Radius']
regions['Top Blade'] = \
(+surfaces['Top Blade y-'] & -surfaces['Global y+']) & \
(+surfaces['Top Blade x-'] & -surfaces['Top Blade x+'])
regions['Top Steel'] = \
(+surfaces['Global x-'] & -surfaces['Top Blade x-']) & \
(+surfaces['Top Blade y-'] & -surfaces['Global y+'])
regions['Left Blade'] = \
(+surfaces['Left Blade y-'] & -surfaces['Left Blade y+']) & \
(+surfaces['Global x-'] & -surfaces['Left Blade x+'])
regions['Left Steel'] = \
(+surfaces['Left Blade y+'] & -surfaces['Top Blade y-']) & \
(+surfaces['Global x-'] & -surfaces['Left Blade x+'])
regions['Corner Blade'] = \
regions['Left Steel'] | regions['Top Steel']
regions['Water Fill'] = \
regions['Global'] & ~regions['Assembly'] & \
~regions['Top Blade'] & ~regions['Left Blade'] &\
~regions['Corner Blade']
We will begin building the 8x8 assembly. To do that we will have to build the cells and universe for each pin type (fuel, gadolinia-fuel, and water).
[6]:
universes = {}
cells = {}
for name, mat, in zip(['Fuel Pin', 'Gd Pin'],
[materials['Fuel'], materials['Gad']]):
universes[name] = openmc.Universe(name=name)
cells[name] = openmc.Cell(name=name)
cells[name].fill = mat
cells[name].region = regions['Fuel']
universes[name].add_cell(cells[name])
cells[name + ' Clad'] = openmc.Cell(name=name + ' Clad')
cells[name + ' Clad'].fill = materials['Zirc2']
cells[name + ' Clad'].region = regions['Clad']
universes[name].add_cell(cells[name + ' Clad'])
cells[name + ' Water'] = openmc.Cell(name=name + ' Water')
cells[name + ' Water'].fill = materials['Water']
cells[name + ' Water'].region = regions['Water']
universes[name].add_cell(cells[name + ' Water'])
universes['Hole'] = openmc.Universe(name='Hole')
cells['Hole'] = openmc.Cell(name='Hole')
cells['Hole'].fill = materials['Water']
universes['Hole'].add_cell(cells['Hole'])
Let’s use this pin information to create our 8x8 assembly.
[7]:
# Create fuel assembly Lattice
universes['Assembly'] = openmc.RectLattice(name='Assembly')
universes['Assembly'].pitch = (pin_pitch, pin_pitch)
universes['Assembly'].lower_left = [pin_pitch, pin_pitch]
f = universes['Fuel Pin']
g = universes['Gd Pin']
h = universes['Hole']
lattices = [[f, f, f, f, f, f, f, f],
[f, f, f, f, f, f, f, f],
[f, f, f, g, f, g, f, f],
[f, f, g, h, h, f, g, f],
[f, f, f, h, h, f, f, f],
[f, f, g, f, f, f, g, f],
[f, f, f, g, f, g, f, f],
[f, f, f, f, f, f, f, f]]
# Store the array of lattice universes
universes['Assembly'].universes = lattices
cells['Assembly'] = openmc.Cell(name='Assembly')
cells['Assembly'].fill = universes['Assembly']
cells['Assembly'].region = regions['Assembly']
So far we have the rods and water within the assembly , but we still need the control blade and the water which fills the rest of the space. We will create those cells now
[8]:
# The top portion of the blade, poisoned with B4C
cells['Top Blade'] = openmc.Cell(name='Top Blade')
cells['Top Blade'].fill = materials['B4C']
cells['Top Blade'].region = regions['Top Blade']
# The left portion of the blade, poisoned with B4C
cells['Left Blade'] = openmc.Cell(name='Left Blade')
cells['Left Blade'].fill = materials['B4C']
cells['Left Blade'].region = regions['Left Blade']
# The top-left corner portion of the blade, with no poison
cells['Corner Blade'] = openmc.Cell(name='Corner Blade')
cells['Corner Blade'].fill = materials['Steel']
cells['Corner Blade'].region = regions['Corner Blade']
# Water surrounding all other cells and our assembly
cells['Water Fill'] = openmc.Cell(name='Water Fill')
cells['Water Fill'].fill = materials['Water']
cells['Water Fill'].region = regions['Water Fill']
OpenMC requires that there is a “root” universe. Let us create our root universe and fill it with the cells just defined.
[9]:
# Create root Universe
universes['Root'] = openmc.Universe(name='root universe', universe_id=0)
universes['Root'].add_cells([cells['Assembly'], cells['Top Blade'],
cells['Corner Blade'], cells['Left Blade'],
cells['Water Fill']])
What do you do after you create your model? Check it! We will use the plotting capabilities of the Python API to do this for us.
When doing so, we will coloring by material with fuel being red, gadolinia-fuel as yellow, zirc cladding as a light grey, water as blue, B4C as black and steel as a darker gray.
[10]:
universes['Root'].plot(origin=(length / 2., length / 2., 0.),
pixels=(500, 500), width=(length, length),
color_by='material',
colors={materials['Fuel']: (1., 0., 0.),
materials['Gad']: (1., 1., 0.),
materials['Zirc2']: (0.5, 0.5, 0.5),
materials['Water']: (0.0, 0.0, 1.0),
materials['B4C']: (0.0, 0.0, 0.0),
materials['Steel']: (0.4, 0.4, 0.4)})
[10]:
<matplotlib.image.AxesImage at 0x14a9643a2ef0>

Looks pretty good to us!
We now must create a geometry that is assigned a root universe and export it to XML.
[11]:
# Create Geometry and set root universe
geometry = openmc.Geometry(universes['Root'])
# Export to "geometry.xml"
geometry.export_to_xml()
With the geometry and materials finished, we now just need to define simulation parameters, including how to run the model and what we want to learn from the model (i.e., define the tallies). We will start with our simulation parameters in the next block.
This will include setting the run strategy, telling OpenMC not to bother creating a tallies.out
file, and limiting the verbosity of our output to just the header and results to not clog up our notebook with results from each batch.
[12]:
# OpenMC simulation parameters
batches = 1000
inactive = 20
particles = 1000
# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': False}
settings_file.verbosity = 4
# Create an initial uniform spatial source distribution over fissionable zones
bounds = [pin_pitch, pin_pitch, 10, length - pin_pitch, length - pin_pitch, 10]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.Source(space=uniform_dist)
# Export to "settings.xml"
settings_file.export_to_xml()
Create an MGXS Library¶
Now we are ready to generate multi-group cross sections! First, let’s define a 2-group structure using the built-in EnergyGroups class.
[13]:
# Instantiate a 2-group EnergyGroups object
groups = openmc.mgxs.EnergyGroups()
groups.group_edges = np.array([0., 0.625, 20.0e6])
Next, we will instantiate an openmc.mgxs.Library for the energy groups with our the problem geometry. This library will use the default setting of isotropically-weighting the multi-group cross sections.
[14]:
# Initialize a 2-group Isotropic MGXS Library for OpenMC
iso_mgxs_lib = openmc.mgxs.Library(geometry)
iso_mgxs_lib.energy_groups = groups
Now, we must specify to the Library which types of cross sections to compute. OpenMC’s multi-group mode can accept isotropic flux-weighted cross sections or angle-dependent cross sections, as well as supporting anisotropic scattering represented by either Legendre polynomials, histogram, or tabular angular distributions.
Just like before, we will create the following multi-group cross sections needed to run an OpenMC simulation to verify the accuracy of our cross sections: “total”, “absorption”, “nu-fission”, ‘“fission”, “nu-scatter matrix”, “multiplicity matrix”, and “chi”. “multiplicity matrix” is needed to provide OpenMC’s multi-group mode with additional information needed to accurately treat scattering multiplication (i.e., (n,xn) reactions)) explicitly.
[15]:
# Specify multi-group cross section types to compute
iso_mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission',
'nu-scatter matrix', 'multiplicity matrix', 'chi']
Now we must specify the type of domain over which we would like the Library
to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the Library
supports “material” “cell”, “universe”, and “mesh” domain types.
For the sake of example we will use a mesh to gather our cross sections. This mesh will be set up so there is one mesh bin for every pin cell.
[16]:
# Instantiate a tally Mesh
mesh = openmc.RegularMesh()
mesh.dimension = [10, 10]
mesh.lower_left = [0., 0.]
mesh.upper_right = [length, length]
# Specify a "mesh" domain type for the cross section tally filters
iso_mgxs_lib.domain_type = "mesh"
# Specify the mesh over which to compute multi-group cross sections
iso_mgxs_lib.domains = [mesh]
Now we will set the scattering treatment that we wish to use.
In the mg-mode-part-ii notebook, the cross sections were generated with a typical P3 scattering expansion in mind. Now, however, we will use a more advanced technique: OpenMC will directly provide us a histogram of the change-in-angle (i.e., \(\mu\)) distribution.
Where as in the mg-mode-part-ii notebook, all that was required was to set the legendre_order
attribute of mgxs_lib
, here we have only slightly more work: we have to tell the Library that we want to use a histogram distribution (as it is not the default), and then tell it the number of bins.
For this problem we will use 11 bins.
[17]:
# Set the scattering format to histogram and then define the number of bins
# Avoid a warning that corrections don't make sense with histogram data
iso_mgxs_lib.correction = None
# Set the histogram data
iso_mgxs_lib.scatter_format = 'histogram'
iso_mgxs_lib.histogram_bins = 11
Ok, we made our isotropic library with histogram-scattering!
Now why don’t we go ahead and create a library to do the same, but with angle-dependent MGXS. That is, we will avoid making the isotropic flux weighting approximation and instead just store a cross section for every polar and azimuthal angle pair.
To do this with the Python API and OpenMC, all we have to do is set the number of polar and azimuthal bins. Here we only need to set the number of bins, the API will convert all of angular space into equal-width bins for us.
Since this problem is symmetric in the z-direction, we only need to concern ourselves with the azimuthal variation here. We will use eight angles.
Ok, we will repeat all the above steps for a new library object, but will also set the number of azimuthal bins at the end.
[18]:
# Let's repeat all of the above for an angular MGXS library so we can gather
# that in the same continuous-energy calculation
angle_mgxs_lib = openmc.mgxs.Library(geometry)
angle_mgxs_lib.energy_groups = groups
angle_mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission',
'nu-scatter matrix', 'multiplicity matrix', 'chi']
angle_mgxs_lib.domain_type = "mesh"
angle_mgxs_lib.domains = [mesh]
angle_mgxs_lib.correction = None
angle_mgxs_lib.scatter_format = 'histogram'
angle_mgxs_lib.histogram_bins = 11
# Set the angular bins to 8
angle_mgxs_lib.num_azimuthal = 8
Now that our libraries have been setup, let’s make sure they contain the types of cross sections which meet the needs of OpenMC’s multi-group solver. Note that this step is done automatically when writing the Multi-Group Library file later in the process (as part of the mgxs_lib.write_mg_library()
), but it is a good practice to also run this before spending all the time running OpenMC to generate the cross sections.
[19]:
# Check the libraries - if no errors are raised, then the library is satisfactory.
iso_mgxs_lib.check_library_for_openmc_mgxs()
angle_mgxs_lib.check_library_for_openmc_mgxs()
Lastly, we use our two Library
objects to construct the tallies needed to compute all of the requested multi-group cross sections in each domain.
We expect a warning here telling us that the default Legendre order is not meaningful since we are using histogram scattering.
[20]:
# Construct all tallies needed for the multi-group cross section library
iso_mgxs_lib.build_library()
angle_mgxs_lib.build_library()
/home/romano/openmc/openmc/mgxs/mgxs.py:4144: UserWarning: The legendre order will be ignored since the scatter format is set to histogram
warnings.warn(msg)
The tallies within the libraries can now be exported to a “tallies.xml” input file for OpenMC.
[21]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
iso_mgxs_lib.add_to_tallies_file(tallies_file, merge=True)
angle_mgxs_lib.add_to_tallies_file(tallies_file, merge=True)
In addition, we instantiate a fission rate mesh tally for eventual comparison of results.
[22]:
# Instantiate tally Filter
mesh_filter = openmc.MeshFilter(mesh)
# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter]
tally.scores = ['fission']
# Add tally to collection
tallies_file.append(tally, merge=True)
# Export all tallies to a "tallies.xml" file
tallies_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=1.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=2.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=11.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=21.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=22.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=12.
warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Filter instance already exists with id=18.
warn(msg, IDWarning)
Time to run the calculation and get our results!
[23]:
# Run OpenMC
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea
Date/Time | 2019-10-04 10:54:35
OpenMP Threads | 4
Minimum neutron data temperature: 294.000000 K
Maximum neutron data temperature: 294.000000 K
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 0.83866 +/- 0.00102
k-effective (Track-length) = 0.83799 +/- 0.00118
k-effective (Absorption) = 0.83968 +/- 0.00103
Combined k-effective = 0.83900 +/- 0.00085
Leakage Fraction = 0.00000 +/- 0.00000
To make the files available and not be over-written when running the multi-group calculation, we will now rename the statepoint and summary files.
[24]:
# Move the StatePoint File
ce_spfile = './statepoint_ce.h5'
os.rename('statepoint.' + str(batches) + '.h5', ce_spfile)
# Move the Summary file
ce_sumfile = './summary_ce.h5'
os.rename('summary.h5', ce_sumfile)
Tally Data Processing¶
Our simulation ran successfully and created statepoint and summary output files. Let’s begin by loading the StatePoint file, but not automatically linking the summary file.
[25]:
# Load the statepoint file, but not the summary file, as it is a different filename than expected.
sp = openmc.StatePoint(ce_spfile, autolink=False)
In addition to the statepoint file, our simulation also created a summary file which encapsulates information about the materials and geometry. This is necessary for the openmc.Library
to properly process the tally data. We first create a Summary
object and link it with the statepoint. Normally this would not need to be performed, but since we have renamed our summary file to avoid conflicts with the Multi-Group calculation’s summary file, we will load this in explicitly.
[26]:
su = openmc.Summary(ce_sumfile)
sp.link_with_summary(su)
The statepoint is now ready to be analyzed. To create our libraries we simply have to load the tallies from the statepoint into each Library
and our MGXS
objects will compute the cross sections for us under-the-hood.
[27]:
# Initialize MGXS Library with OpenMC statepoint data
iso_mgxs_lib.load_from_statepoint(sp)
angle_mgxs_lib.load_from_statepoint(sp)
The next step will be to prepare the input for OpenMC to use our newly created multi-group data.
Isotropic Multi-Group OpenMC Calculation¶
We will now use the Library
to produce the isotropic multi-group cross section data set for use by the OpenMC multi-group solver.
If the model to be run in multi-group mode is the same as the continuous-energy mode, the openmc.mgxs.Library
class has the ability to directly create the multi-group geometry, materials, and multi-group library for us. Note that this feature is only useful if the MG model is intended to replicate the CE geometry - it is not useful if the CE library is not the same geometry (like it would be for generating MGXS from a generic spectral region).
This method creates and assigns the materials automatically, including creating a geometry which is equivalent to our mesh cells for which the cross sections were derived.
[28]:
# Allow the API to create our Library, materials, and geometry file
iso_mgxs_file, materials_file, geometry_file = iso_mgxs_lib.create_mg_mode()
# Tell the materials file what we want to call the multi-group library
materials_file.cross_sections = 'mgxs.h5'
# Write our newly-created files to disk
iso_mgxs_file.export_to_hdf5('mgxs.h5')
materials_file.export_to_xml()
geometry_file.export_to_xml()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.
warn(msg, IDWarning)
Next, we can make the changes we need to the settings file. These changes are limited to telling OpenMC to run a multi-group calculation and provide the location of our multi-group cross section file.
[29]:
# Set the energy mode
settings_file.energy_mode = 'multi-group'
# Export to "settings.xml"
settings_file.export_to_xml()
Let’s clear up the tallies file so it doesn’t include all the extra tallies for re-generating a multi-group library
[30]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
# Add our fission rate mesh tally
tallies_file.append(tally)
# Export to "tallies.xml"
tallies_file.export_to_xml()
Before running the calculation let’s look at our meshed model. It might not be interesting, but let’s take a look anyways.
[31]:
geometry_file.root_universe.plot(origin=(length / 2., length / 2., 0.),
pixels=(300, 300), width=(length, length),
color_by='material')
[31]:
<matplotlib.image.AxesImage at 0x14a963c86c88>

So, we see a 10x10 grid with a different color for every material, sounds good!
At this point, the problem is set up and we can run the multi-group calculation.
[32]:
# Execute the Isotropic MG OpenMC Run
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea
Date/Time | 2019-10-04 10:56:58
OpenMP Threads | 4
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 0.82471 +/- 0.00104
k-effective (Track-length) = 0.82466 +/- 0.00103
k-effective (Absorption) = 0.82482 +/- 0.00075
Combined k-effective = 0.82477 +/- 0.00068
Leakage Fraction = 0.00000 +/- 0.00000
Before we go the angle-dependent case, let’s save the StatePoint and Summary files so they don’t get over-written
[33]:
# Move the StatePoint File
iso_mg_spfile = './statepoint_mg_iso.h5'
os.rename('statepoint.' + str(batches) + '.h5', iso_mg_spfile)
# Move the Summary file
iso_mg_sumfile = './summary_mg_iso.h5'
os.rename('summary.h5', iso_mg_sumfile)
Angle-Dependent Multi-Group OpenMC Calculation¶
Let’s now run the calculation with the angle-dependent multi-group cross sections. This process will be the exact same as above, except this time we will use the angle-dependent Library as our starting point.
We do not need to re-write the materials, geometry, or tallies file to disk since they are the same as for the isotropic case.
[34]:
# Let's repeat for the angle-dependent case
angle_mgxs_lib.load_from_statepoint(sp)
angle_mgxs_file, materials_file, geometry_file = angle_mgxs_lib.create_mg_mode()
angle_mgxs_file.export_to_hdf5()
/home/romano/openmc/openmc/mixin.py:71: IDWarning: Another Universe instance already exists with id=0.
warn(msg, IDWarning)
At this point, the problem is set up and we can run the multi-group calculation.
[35]:
# Execute the angle-dependent OpenMC Run
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2019 MIT and OpenMC contributors
License | http://openmc.readthedocs.io/en/latest/license.html
Version | 0.11.0-dev
Git SHA1 | ed7123f4e7ce7b097c4b2bfb0ef5283eaa8afaea
Date/Time | 2019-10-04 10:57:24
OpenMP Threads | 4
====================> K EIGENVALUE SIMULATION <====================
============================> RESULTS <============================
k-effective (Collision) = 0.83564 +/- 0.00103
k-effective (Track-length) = 0.83572 +/- 0.00104
k-effective (Absorption) = 0.83478 +/- 0.00073
Combined k-effective = 0.83504 +/- 0.00066
Leakage Fraction = 0.00000 +/- 0.00000
Results Comparison¶
In this section we will compare the eigenvalues and fission rate distributions of the continuous-energy, isotropic multi-group and angle-dependent multi-group cases.
We will begin by loading the multi-group statepoint files, first the isotropic, then angle-dependent. The angle-dependent was not renamed, so we can autolink its summary.
[36]:
# Load the isotropic statepoint file
iso_mgsp = openmc.StatePoint(iso_mg_spfile, autolink=False)
iso_mgsum = openmc.Summary(iso_mg_sumfile)
iso_mgsp.link_with_summary(iso_mgsum)
# Load the angle-dependent statepoint file
angle_mgsp = openmc.StatePoint('statepoint.' + str(batches) + '.h5')
Eigenvalue Comparison¶
Next, we can load the eigenvalues for comparison and do that comparison
[37]:
ce_keff = sp.k_combined
iso_mg_keff = iso_mgsp.k_combined
angle_mg_keff = angle_mgsp.k_combined
# Find eigenvalue bias
iso_bias = 1.0e5 * (ce_keff - iso_mg_keff)
angle_bias = 1.0e5 * (ce_keff - angle_mg_keff)
Let’s compare the eigenvalues in units of pcm
[38]:
print('Isotropic to CE Bias [pcm]: {0:1.1f}'.format(iso_bias.nominal_value))
print('Angle to CE Bias [pcm]: {0:1.1f}'.format(angle_bias.nominal_value))
Isotropic to CE Bias [pcm]: 1423.5
Angle to CE Bias [pcm]: 396.6
We see a large reduction in error by switching to the usage of angle-dependent multi-group cross sections!
Of course, this rodded and partially voided BWR problem was chosen specifically to exacerbate the angular variation of the reaction rates (and thus cross sections). Such improvements should not be expected in every case, especially if localized absorbers are not present.
It is important to note that both eigenvalues can be improved by the application of finer geometric or energetic discretizations, but this shows that the angle discretization may be a factor for consideration.
Fission Rate Distribution Comparison¶
Next we will visualize the mesh tally results obtained from our three cases.
This will be performed by first obtaining the one-group fission rate tally information from our state point files. After we have this information we will re-shape the data to match the original mesh laydown. We will then normalize, and finally create side-by-side plots of all.
[39]:
sp_files = [sp, iso_mgsp, angle_mgsp]
titles = ['Continuous-Energy', 'Isotropic Multi-Group',
'Angle-Dependent Multi-Group']
fiss_rates = []
fig = plt.figure(figsize=(12, 6))
for i, (case, title) in enumerate(zip(sp_files, titles)):
# Get our mesh tally information
mesh_tally = case.get_tally(name='mesh tally')
fiss_rates.append(mesh_tally.get_values(scores=['fission']))
# Reshape the array
fiss_rates[-1].shape = mesh.dimension
# Normalize the fission rates
fiss_rates[-1] /= np.mean(fiss_rates[-1][fiss_rates[-1] > 0.])
# Set 0s to NaNs so they show as white
fiss_rates[-1][fiss_rates[-1] == 0.] = np.nan
fig = plt.subplot(1, len(titles), i + 1)
# Plot only the fueled regions
plt.imshow(fiss_rates[-1][1:-1, 1:-1], cmap='jet', origin='lower',
vmin=0.4, vmax=4.)
plt.title(title + '\nFission Rates')

With this colormap, dark blue is the lowest power and dark red is the highest power.
We see general agreement between the fission rate distributions, but it looks like there may be less of a gradient near the rods in the continuous-energy and angle-dependent MGXS cases than in the isotropic MGXS case.
To better see the differences, let’s plot ratios of the fission powers for our two multi-group cases compared to the continuous-energy case t
[40]:
# Calculate and plot the ratios of MG to CE for each of the 2 MG cases
ratios = []
fig, axes = plt.subplots(figsize=(12, 6), nrows=1, ncols=2)
for i, (case, title, axis) in enumerate(zip(sp_files[1:], titles[1:], axes.flat)):
# Get our ratio relative to the CE (in fiss_ratios[0])
ratios.append(np.divide(fiss_rates[i + 1], fiss_rates[0]))
# Plot only the fueled regions
im = axis.imshow(ratios[-1][1:-1, 1:-1], cmap='bwr', origin='lower',
vmin = 0.9, vmax = 1.1)
axis.set_title(title + '\nFission Rates Relative\nto Continuous-Energy')
# Add a color bar
fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)
[40]:
<matplotlib.colorbar.Colorbar at 0x14a961258eb8>

With this ratio its clear that the errors are significantly worse in the isotropic case. These errors are conveniently located right where the most anisotropy is espected: by the control blades and by the Gd-bearing pins!
Unstructured Mesh¶
Unstructured Mesh: Introduction¶
In this example we’ll look at how to setup and use unstructured mesh tallies in OpenMC. Unstructured meshes are able to provide results over spatial regions of a problem while conforming to a specific geometric features – something that is often difficult to do using the regular and rectilinear meshes in OpenMC.
Here, we’ll apply an unstructured mesh tally to the PWR assembly model from the OpenMC examples.
*NOTE: This notebook will not run successfully if OpenMC has not been built with DAGMC or libMesh support enabled.*
[1]:
from IPython.display import Image
import openmc
import openmc.lib
# ensure one of the two mesh librares is enabled
assert(openmc.lib._dagmc_enabled() or openmc.lib._libmesh_enabled())
We’ll need to download the unstructured mesh file used in this notebook. We’ll be retrieving those using the function and URLs below.
[2]:
%matplotlib inline
from matplotlib import pyplot as plt
plt.rcParams["figure.figsize"] = (30,10)
import urllib.request
pin_mesh_moab_url = 'https://tinyurl.com/u9ce9d7' # MOAB file - 22 MB
pin_mesh_libmesh_url = 'https://tinyurl.com/yysgs3tr' # Exodus file - 9.7 MB
def download(url, filename='dagmc.h5m'):
"""
Helper function for retrieving dagmc models
"""
u = urllib.request.urlopen(url)
if u.status != 200:
raise RuntimeError("Failed to download file.")
# save file as dagmc.h5m
with open(filename, 'wb') as f:
f.write(u.read())
First we’ll import that model from the set of OpenMC examples.
[3]:
model = openmc.examples.pwr_assembly()
We’ll make a couple of adjustments to this 2D model as it won’t play very well with the 3D mesh we’ll be looking at. First, we’ll bound the pincell between +/- 10 cm in the Z dimension.
[4]:
min_z = openmc.ZPlane(z0=-10.0)
max_z = openmc.ZPlane(z0=10.0)
z_region = +min_z & -max_z
cells = model.geometry.get_all_cells()
for cell in cells.values():
cell.region &= z_region
The other adjustment we’ll make is to remove the reflective boundary conditions on the X and Y boundaries. (This is purely to generate a more interesting flux profile.)
[5]:
surfaces = model.geometry.get_all_surfaces()
# modify the boundary condition of the
# planar surfaces bounding the assembly
for surface in surfaces.values():
if isinstance(surface, openmc.Plane):
surface.boundary_type = 'vacuum'
Let’s take a quick look at the model to ensure our changs have been added properly.
[6]:
root_univ = model.geometry.root_universe
# axial image
root_univ.plot(width=(22.0, 22.0),
pixels=(200, 300),
basis='xz',
color_by='material',
seed=0)
[6]:
<matplotlib.image.AxesImage at 0x7f5f451a0860>

[7]:
# radial image
root_univ.plot(width=(22.0, 22.0),
pixels=(400, 400),
basis='xy',
color_by='material',
seed=0)
[7]:
<matplotlib.image.AxesImage at 0x7f5f452f0cf8>

Looks good! Let’s run some particles through the problem.
[8]:
model.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2020 MIT and OpenMC contributors
License | https://docs.openmc.org/en/latest/license.html
Version | 0.12.1-dev
Git SHA1 | e62681221a625ce7eb635df966826379fb4e9453
Date/Time | 2021-01-10 00:51:10
MPI Processes | 1
OpenMP Threads | 2
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading geometry XML file...
Reading U234 from /home/shriwise/opt/openmc/xs/nndc_hdf5/U234.h5
Reading U235 from /home/shriwise/opt/openmc/xs/nndc_hdf5/U235.h5
Reading U238 from /home/shriwise/opt/openmc/xs/nndc_hdf5/U238.h5
Reading O16 from /home/shriwise/opt/openmc/xs/nndc_hdf5/O16.h5
Reading Zr90 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Zr90.h5
Reading Zr91 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Zr91.h5
Reading Zr92 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Zr92.h5
Reading Zr94 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Zr94.h5
Reading Zr96 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Zr96.h5
Reading H1 from /home/shriwise/opt/openmc/xs/nndc_hdf5/H1.h5
Reading B10 from /home/shriwise/opt/openmc/xs/nndc_hdf5/B10.h5
Reading B11 from /home/shriwise/opt/openmc/xs/nndc_hdf5/B11.h5
Reading c_H_in_H2O from /home/shriwise/opt/openmc/xs/nndc_hdf5/c_H_in_H2O.h5
Minimum neutron data temperature: 294.0 K
Maximum neutron data temperature: 294.0 K
Preparing distributed cell instances...
Writing summary.h5 file...
Maximum neutron transport energy: 20000000.0 eV for U235
Initializing source particles...
====================> K EIGENVALUE SIMULATION <====================
Bat./Gen. k Average k
========= ======== ====================
1/1 0.20444
2/1 0.15502
3/1 0.19804
4/1 0.22159
5/1 0.19776
6/1 0.20086
7/1 0.21896 0.20991 +/- 0.00905
8/1 0.23134 0.21706 +/- 0.00885
9/1 0.29029 0.23536 +/- 0.01935
10/1 0.20094 0.22848 +/- 0.01649
Creating state point statepoint.10.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 2.1129e+00 seconds
Reading cross sections = 2.0943e+00 seconds
Total time in simulation = 1.1595e-01 seconds
Time in transport only = 1.1219e-01 seconds
Time in inactive batches = 5.8272e-02 seconds
Time in active batches = 5.7678e-02 seconds
Time synchronizing fission bank = 2.3802e-04 seconds
Sampling source sites = 1.3992e-04 seconds
SEND/RECV source sites = 2.9613e-05 seconds
Time accumulating tallies = 1.4423e-05 seconds
Time writing statepoints = 3.0105e-03 seconds
Total time for finalization = 4.0100e-06 seconds
Total time elapsed = 2.2442e+00 seconds
Calculation Rate (inactive) = 8580.51 particles/second
Calculation Rate (active) = 8668.78 particles/second
============================> RESULTS <============================
k-effective (Collision) = 0.25094 +/- 0.02010
k-effective (Track-length) = 0.22848 +/- 0.01649
k-effective (Absorption) = 0.21556 +/- 0.04156
Combined k-effective = 0.20707 +/- 0.01965
Leakage Fraction = 0.79200 +/- 0.03382
[8]:
PosixPath('/home/shriwise/opt/openmc/openmc/examples/jupyter/statepoint.10.h5')
Now it’s time to apply our mesh tally to the problem. We’ll be using the tetrahedral mesh “pins1-4.h5m” shown below:
[9]:
Image("./images/pin_mesh.png", width=600)
This mesh was generated using Trelis with radii that match the fuel/coolant channels of the PWR model. These four channels correspond to the highlighted channels of the assembly below.
Two of the channels are coolant and the other two are fuel.
[10]:
from matplotlib.patches import Rectangle
from matplotlib import pyplot as plt
pitch = 1.26 # cm
img = root_univ.plot(width=(22.0, 22.0),
pixels=(600, 600),
basis='xy',
color_by='material',
seed=0)
# highlight channels
for i in range(0, 4):
corner = (i * pitch - pitch / 2.0, -i * pitch - pitch / 2.0)
rect = Rectangle(corner,
pitch,
pitch,
edgecolor='blue',
fill=False)
img.axes.add_artist(rect)

Applying an unstructured mesh tally¶
To use this mesh, we’ll create an unstructured mesh instance and apply it to a mesh filter. We do this by specifying a mesh file and mesh library on an UnstructuredMesh
object. The specified mesh library will be used to load the mesh file during simulation initialization. OpenMC must be built with support for the specified mesh library enabled.
[11]:
mesh_library = 'moab' # change to 'libmesh' to use libMesh instead
if mesh_library == 'moab':
assert(openmc.lib._dagmc_enabled())
mesh_file = 'pins1-4.h5m'
mesh_url = pin_mesh_moab_url
elif mesh_library == 'libmesh':
assert(openmc.lib._libmesh_enabled())
mesh_file = 'pins1-4.e'
mesh_url = pin_mesh_libmesh_url
# download the file and create the UnstructuredMesh object
download(mesh_url, mesh_file)
umesh = openmc.UnstructuredMesh(mesh_file, library=mesh_library)
Regardless of the library used to represent the mesh, we can apply this mesh object in a MeshFilter
.
[12]:
mesh_filter = openmc.MeshFilter(umesh)
We can now apply this filter like any other. For this demonstration we’ll score both the flux and heating in these pins.
[13]:
tally = openmc.Tally()
tally.filters = [mesh_filter]
tally.scores = ['heating', 'flux']
# Only collision estimators are supported for
if umesh.library == 'libmesh':
tally.estimator = 'collision'
model.tallies = [tally]
Now we’ll run this model with the unstructured mesh tally applied. Notice that the simulation takes some time to start due to some additional data structures used by the unstructured mesh tally. Additionally, the particle rate drops dramatically during the active cycles of this simulation.
Unstructured meshes are useful, but they can be computationally expensive!
[14]:
model.settings.particles = 100_000
model.settings.inactive = 20
model.settings.batches = 100
model.run(output=False)
[14]:
PosixPath('/home/shriwise/opt/openmc/openmc/examples/jupyter/statepoint.100.h5')
At the end of the simulation, we see the statepoint file along with a file named “tally_1.100.vtk”. This file contains the results of the unstructured mesh tally with convenient labels for the scores applied. In our case the following scores will be present in the VTK:
- flux_total_value
- flux_total_std_dev
- heating_total_value
- heating_total_std_dev
Where “total” represents the nuclide entry in the tally. If a set of nuclides are specified in the tally, a different score will be added to the VTK for each one in addition to “total”.
Currently, an unstructured VTK file will only be generated for tallies if the unstructured mesh is is the only filter applied to that tally. All results for the unstructured mesh tally are present in the statepoint file regardless of the number of filters applied, however.
These files can be viewed using free tools like Paraview and VisIt to examine the results.
[15]:
!ls *.vtk
tally_1.100.vtk
Heating¶
Here is an image of the heating score as viewed in VisIt. Note that no heating is scored in the water-filled channels as expected.
[17]:
Image("./images/umesh_heating.png", width=600)
Statepoint Data¶
[18]:
with openmc.StatePoint("statepoint.100.h5") as sp:
tally = sp.tallies[1]
umesh = sp.meshes[1]
Enough information for visualization of results on the unstructured mesh is also provided in the statepoint file. Namely, the mesh element volumes and centroids are available.
[19]:
print(umesh.volumes)
print(umesh.centroids)
[1.43381086e-04 1.48043747e-04 1.60408339e-04 ... 7.04197023e-05
7.04197023e-05 7.04197023e-05]
[[ 2.88485691 -2.55429784 9.97768184]
[ 2.87565092 -2.60469781 9.8884092 ]
[ 2.85832254 -2.65291228 9.97768184]
...
[ 1.46082175 -1.15569203 -3.62914358]
[ 1.4443143 -1.1321793 -3.65475081]
[ 1.46884412 -1.15657736 -3.68206543]]
The combination of these values can provide for an appoxmiate visualization of the unstructured mesh without its explicit representation or use of an additional mesh library.
We hope you’ve found this example notebook useful!
[20]:
Image("./images/umesh_w_assembly.png", width=600)
Unstructured Mesh: Tallies with CAD and Point Cloud Visualization¶
In the first notebook on this topic, we looked at how to set up a tally using an unstructured mesh in OpenMC. In this notebook, we will explore using unstructured mesh in conjunction with CAD-based geometry to perform detailed geometry analysis on complex geomerty.
*NOTE: This notebook will not run successfully if OpenMC has not been built with DAGMC support enabled.*
[1]:
import os
from IPython.display import Image
import openmc
import openmc.lib
assert(openmc.lib._dagmc_enabled())
We’ll need to download our DAGMC geometry and unstructured mesh files. We’ll be retrieving those using the function and URLs below.
[2]:
from IPython.display import display, clear_output
import urllib.request
manifold_geom_url = 'https://tinyurl.com/rp7grox' # 99 MB
manifold_mesh_url = 'https://tinyurl.com/wojemuh' # 5.4 MB
def download(url, filename):
"""
Helper function for retrieving dagmc models
"""
def progress_hook(count, block_size, total_size):
prog_percent = 100 * count * block_size / total_size
prog_percent = min(100., prog_percent)
clear_output(wait=True)
display('Downloading {}: {:.1f}%'.format(filename, prog_percent))
urllib.request.urlretrieve(url, filename, progress_hook)
The model we’ll be looking at in this example is a steel piping manifold:
[3]:
Image("./images/manifold-cad.png", width=800)
This is a nice example of a model which would be extremely difficult to model using CSG. To get started, we’ll need two files: 1. the DAGMC gometry file on which we’ll track particles and 2. a tetrahedral mesh of the piping structure on which we’ll score tallies
To start, let’s create the materials we’ll need for this problem. The pipes are steel and we’ll model the surrounding area as air.
[4]:
air = openmc.Material(name='air')
air.set_density('g/cc', 0.001205)
air.add_element('N', 0.784431)
air.add_element('O', 0.210748)
air.add_element('Ar',0.0046)
steel = openmc.Material(name='steel')
steel.set_density('g/cc', 8.0)
steel.add_element('Si', 0.010048)
steel.add_element('S', 0.00023)
steel.add_element('Fe', 0.669)
steel.add_element('Ni', 0.12)
steel.add_element('Mo', 0.025)
steel.add_nuclide('P31',0.00023)
steel.add_nuclide('Mn55',0.011014)
materials = openmc.Materials([air, steel])
materials.export_to_xml()
Now let’s download the geometry and mesh files. (This may take some time.)
[5]:
# get the manifold DAGMC geometry file
download(manifold_geom_url, 'dagmc.h5m')
# get the manifold tet mesh
download(manifold_mesh_url, 'manifold.h5m')
'Downloading manifold.h5m: 100.0%'
Next we’ll create a 5 MeV neutron point source at the entrance the single pipe on the low side of the model with
[6]:
src_pnt = openmc.stats.Point(xyz=(0.0, 0.0, 0.0))
src_energy = openmc.stats.Discrete(x=[5.e+06], p=[1.0])
source = openmc.Source(space=src_pnt, energy=src_energy)
settings = openmc.Settings()
settings.source = source
settings.run_mode = "fixed source"
settings.batches = 10
settings.particles = 100
And we’ll indicate that we’re using a CAD-based geometry.
[7]:
settings.dagmc = True
settings.export_to_xml()
We’ll run a few particles through this geometry to make sure everything is working properly.
[8]:
openmc.run()
%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%%%%%%%%%
################## %%%%%%%%%%%%%%%%%%%%%%%
################### %%%%%%%%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%%%%%%
##################### %%%%%%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%%
####################### %%%%%%%%%%%%%%%%%
###################### %%%%%%%%%%%%%%%%%
#################### %%%%%%%%%%%%%%%%%
################# %%%%%%%%%%%%%%%%%
############### %%%%%%%%%%%%%%%%
############ %%%%%%%%%%%%%%%
######## %%%%%%%%%%%%%%
%%%%%%%%%%%
| The OpenMC Monte Carlo Code
Copyright | 2011-2020 MIT and OpenMC contributors
License | https://docs.openmc.org/en/latest/license.html
Version | 0.12.1-dev
Git SHA1 | 8ae407c90e927af62bfdc8f150e96bfd5d7b2ec2
Date/Time | 2021-01-06 09:17:05
MPI Processes | 1
OpenMP Threads | 2
Reading settings XML file...
Reading cross sections XML file...
Reading materials XML file...
Reading DAGMC geometry...
Set overlap thickness = 0
Set numerical precision = 0.001
Loading file dagmc.h5m
Initializing the GeomQueryTool...
Using faceting tolerance: 0.001
Building acceleration data structures...
Implicit Complement assumed to be Vacuum
Reading N14 from /home/shriwise/opt/openmc/xs/nndc_hdf5/N14.h5
Reading N15 from /home/shriwise/opt/openmc/xs/nndc_hdf5/N15.h5
Reading O16 from /home/shriwise/opt/openmc/xs/nndc_hdf5/O16.h5
Reading O17 from /home/shriwise/opt/openmc/xs/nndc_hdf5/O17.h5
Reading Ar36 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ar36.h5
WARNING: Negative value(s) found on probability table for nuclide Ar36 at 294K
Reading Ar38 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ar38.h5
Reading Ar40 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ar40.h5
Reading Si28 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Si28.h5
Reading Si29 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Si29.h5
Reading Si30 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Si30.h5
Reading S32 from /home/shriwise/opt/openmc/xs/nndc_hdf5/S32.h5
Reading S33 from /home/shriwise/opt/openmc/xs/nndc_hdf5/S33.h5
Reading S34 from /home/shriwise/opt/openmc/xs/nndc_hdf5/S34.h5
Reading S36 from /home/shriwise/opt/openmc/xs/nndc_hdf5/S36.h5
Reading Fe54 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe54.h5
Reading Fe56 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe56.h5
Reading Fe57 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe57.h5
Reading Fe58 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Fe58.h5
Reading Ni58 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ni58.h5
Reading Ni60 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ni60.h5
Reading Ni61 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ni61.h5
Reading Ni62 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ni62.h5
Reading Ni64 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Ni64.h5
Reading Mo100 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo100.h5
Reading Mo92 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo92.h5
Reading Mo94 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo94.h5
Reading Mo95 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo95.h5
Reading Mo96 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo96.h5
Reading Mo97 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo97.h5
Reading Mo98 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mo98.h5
Reading P31 from /home/shriwise/opt/openmc/xs/nndc_hdf5/P31.h5
Reading Mn55 from /home/shriwise/opt/openmc/xs/nndc_hdf5/Mn55.h5
Minimum neutron data temperature: 294.0 K
Maximum neutron data temperature: 294.0 K
Preparing distributed cell instances...
Writing summary.h5 file...
Maximum neutron transport energy: 20000000.0 eV for N15
===============> FIXED SOURCE TRANSPORT SIMULATION <===============
Simulating batch 1
Simulating batch 2
Simulating batch 3
Simulating batch 4
Simulating batch 5
Simulating batch 6
Simulating batch 7
Simulating batch 8
Simulating batch 9
Simulating batch 10
Creating state point statepoint.10.h5...
=======================> TIMING STATISTICS <=======================
Total time for initialization = 2.4196e+01 seconds
Reading cross sections = 2.1428e+00 seconds
Total time in simulation = 1.9512e-01 seconds
Time in transport only = 1.9269e-01 seconds
Time in active batches = 1.9512e-01 seconds
Time accumulating tallies = 2.0220e-06 seconds
Time writing statepoints = 2.2461e-03 seconds
Total time for finalization = 1.8940e-06 seconds
Total time elapsed = 2.4401e+01 seconds
Calculation Rate (active) = 5125.02 particles/second
============================> RESULTS <============================
Leakage Fraction = 0.96800 +/- 0.00573
Now let’s setup the unstructured mesh tally. We’ll do this the same way we did in the previous notebook.
[9]:
unstructured_mesh = openmc.UnstructuredMesh("manifold.h5m", library='moab')
mesh_filter = openmc.MeshFilter(unstructured_mesh)
tally = openmc.Tally()
tally.filters = [mesh_filter]
tally.scores = ['flux']
tally.estimator = 'tracklength'
tallies = openmc.Tallies([tally])
tallies.export_to_xml()
[10]:
settings.batches = 200
settings.particles = 5000
settings.export_to_xml()
[11]:
openmc.run(output=False)
Again we should see that tally_1.200.vtk
file which we can use to visualize our results in VisIt, ParaView, or another tool of your choice that supports VTK files.
[12]:
!ls *.vtk
tally_1.200.vtk
[13]:
Image("./images/manifold_flux.png", width="800")
For the purpose of this example, we haven’t run enough particles to score in all of the tet elements, but we indeed see larger flux values near the source location at the bottom of the model.
Visualization with statepoint data¶
It was mentioned in the previous unstructured mesh example that the centroids and volumes of elements are written to the state point file. Here, we’ll explore how to use that information to produce point cloud information for visualization of this data.
This is particularly important when combining an unstructured mesh tally with other filters as a .vtk
file will not automatically be written with the statepoint file in that scenario. To demonstrate this, let’s setup a tally similar to the one above, but add an energy filter and re-run the model.
[14]:
# energy filter with bins from 0 to 1 MeV and 1 MeV to 5 MeV
energy_filter = openmc.EnergyFilter((0.0, 1.e+06, 5.e+06))
tally.filters = [mesh_filter, energy_filter]
print(tally)
print(energy_filter)
tallies.export_to_xml()
Tally
ID = 1
Name =
Filters = MeshFilter, EnergyFilter
Nuclides =
Scores = ['flux']
Estimator = tracklength
EnergyFilter
Values = [ 0. 1000000. 5000000.]
ID = 2
[15]:
!cat tallies.xml
<?xml version='1.0' encoding='utf-8'?>
<tallies>
<mesh id="1" library="moab" type="unstructured">
<filename>manifold.h5m</filename>
</mesh>
<filter id="1" type="mesh">
<bins>1</bins>
</filter>
<filter id="2" type="energy">
<bins>0.0 1000000.0 5000000.0</bins>
</filter>
<tally id="1">
<filters>1 2</filters>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
</tallies>
[16]:
openmc.run(output=False)
Noice the warning at the end of the output above indicating that the .vtk file we used before isn’t written in this case.
Let’s open up this statepoint file and get the information we need to create the point cloud data instead.
*NOTE: You will need the Python vtk module installed to run this part of the notebook.*
[17]:
with openmc.StatePoint("statepoint.200.h5") as sp:
tally = sp.tallies[1]
umesh = sp.meshes[1]
centroids = umesh.centroids
mesh_vols = umesh.volumes
thermal_flux = tally.get_values(scores=['flux'],
filters=[openmc.EnergyFilter],
filter_bins=[((0.0, 1.e+06),)])
fast_flux = tally.get_values(scores=['flux'],
filters=[openmc.EnergyFilter],
filter_bins=[((1.e+06, 5.e+06),)])
[18]:
data_dict = {'Flux 0 - 1 MeV' : thermal_flux,
'Flux 1 - 5 MeV' : fast_flux,
'Total Flux' : thermal_flux + fast_flux}
umesh.write_data_to_vtk("manifold", data_dict)
We should now see our new flux file in the directory. It can be used to visualize the results in the same way as our other .vtk
files.
[19]:
!ls *.vtk
manifold.vtk tally_1.200.vtk
[20]:
Image("./images/manifold_pnt_cld.png", width=800)
Release Notes¶
What’s New in 0.12.2¶
Summary¶
This release of OpenMC is primarily a hotfix release with numerous important bug fixes. Several tally-related enhancements have also been added.
New Features¶
Three tally-related enhancements were added to the code in this release:
- A new
CollisionFilter
class that allows tallies to be filtered by the number of collisions a particle has undergone. - A translation attribute has been added to
MeshFilter
that allows a mesh to be translated from its original position before location checks are performed. - The
UnstructuredMesh
class now supports libMesh unstructured meshes to enable better ingration with MOOSE-based applications.
Bug Fixes¶
- Reset particle coordinates during find cell operation
- Cover quadric edge case
- Prevent divide-by-zero in bins_crossed methods for meshes
- Fix for translational periodic boundary conditions
- Fix angle sampling in CorrelatedAngleEnergy
- Fix typo in fmt string for a lattice error
- Nu-fission tally and stochastic volume bug fixes
- Make sure failed neighbor list triggers exhaustic search
- Change element to element.title to catch lowercase entries
- Disallow non-current scores with a surface filter
- Depletion operator obeys Materials.cross_sections
- Fix for surface_bins_crossed override
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.12.1¶
Summary¶
This release of OpenMC includes an assortment of new features and many bug
fixes. The openmc.deplete
module incorporates a number of improvements in
usability, accuracy, and performance. Other enhancements include generalized
rotational periodic boundary conditions, expanded source modeling capabilities,
and a capability to generate windowed multipole library files from ENDF files.
New Features¶
- Boundary conditions have been refactored and generalized. Rotational periodic boundary conditions can now be applied to any N-fold symmetric geometry.
- External source distributions have been refactored and extended. Users writing
their own C++ custom sources need to write a class that derives from
openmc::Source
. These changes have enabled new functionality, such as:- Mixing more than one custom source library together
- Mixing a normal source with a custom source
- Using a file-based source for fixed source simulations
- Using a file-based source for eigenvalue simulations even when the number of particles doesn’t match
- New capability to read and write a source file based on particles that cross a surface (known as a “surface source”).
- Various improvements related to depletion:
- Reactions used in a depletion chain can now be configured through the
reactions
argument toopenmc.deplete.Chain.from_endf()
. - Specifying a power of zero during a depletion simulation no longer results in an unnecessary transport solve.
- Reaction rates can be computed either directly or using multigroup flux
tallies that are used to collapse reaction rates afterward. This is enabled
through the
reaction_rate_mode
andreaction_rate_opts
toopenmc.deplete.Operator
. - Depletion results can be used to create a new
openmc.Materials
object using theopenmc.deplete.ResultsList.export_to_materials()
method.
- Reactions used in a depletion chain can now be configured through the
- Multigroup current and diffusion cross sections can be generated through the
openmc.mgxs.Current
andopenmc.mgxs.DiffusionCoefficient
classes. - Added
openmc.data.isotopes()
function that returns a list of naturally occurring isotopes for a given element. - Windowed multipole libraries can now be generated directly from the Python API
using
openmc.data.WindowedMultipole.from_endf()
. - The new
openmc.write_source_file()
function allows source files to be generated programmatically.
Bug Fixes¶
- Proper detection of MPI wrappers
- Fix related to declaration order of maps/vectors
- Check for existence of decay rate attribute
- Small updates to deal with JEFF 3.3 data
- Fix for depletion chain generation
- Fix call to superclass constructor in MeshPlotter
- Fix for data crossover in VTK files
- Make sure reaction names are recognized as valid tally scores
- Fix bug related to logging of particle restarts
- Examine if region exists before removing redundant surfaces
- Fix plotting of individual universe levels
- Mixed materials should inherit depletable attribute
- Fix typo in energy units in dose coefficients
- Fixes for large tally cases
- Fix verification of volume calculation results
- Fix calculation of decay energy for depletion chains
- Fix pointers in CartesianIndependent
- Ensure correct initialization of members for RegularMesh
- Add missing import in depletion module
- Fixed several bugs related to decay-rate
- Fix how depletion operator distributes burnable materials
- Fix assignment of elemental carbon in JEFF 3.3
- Fix typo in RectangularParallelepiped.__pos__
- Fix temperature tolerance with S(a,b) data
- Fix sampling or normal distribution
- Fix for SharedArray relaxed memory ordering
- Check for proper format of source files
- Ensure (n,gamma) reaction rate tally uses sampled cross section
- Fix for temperature range behavior
Contributors¶
This release contains new contributions from the following people:
- Andrew Davis
- Guillaume Giudicelli
- Sterling Harper
- Bryan Herman
- Yue Jin
- Andrew Johnson
- Miriam Kreher
- Shikhar Kumar
- Jingang Liang
- Amanda Lund
- Adam Nelson
- April Novak
- YoungHui Park
- Ariful Islam Pranto
- Ron Rahaman
- Gavin Ridley
- Paul Romano
- Jonathan Shimwell
- Dan Short
- Patrick Shriwise
- Roy Stogner
- John Tramm
- Cyrus Wyett
- Jiankai Yu
What’s New in 0.12.0¶
Summary¶
This release of OpenMC includes an assortment of new features and many bug fixes.
In particular, the openmc.deplete
module has been heavily tested which
has resulted in a number of usability improvements, bug fixes, and other
enhancements. Energy deposition calculations, particularly for coupled
neutron-photon simulations, have been improved as well.
Improvements in modeling capabilities continue to be added to the code,
including the ability to rotate surfaces in the Python API, several new
“composite” surfaces, a variety of new methods on openmc.Material
,
unstructured mesh tallies that leverage the existing DAGMC infrastructure,
effective dose coefficients from ICRP-116, and a new cell instance tally
filter.
New Features¶
- All surfaces now have a rotate method that allows them to be rotated.
- Several “composite” surfaces, which are actually composed of multiple surfaces
but can be treated as a normal surface through the -/+ unary operators, have
been added. These include:
openmc.model.RightCircularCylinder
openmc.model.RectangularParallelepiped
openmc.model.XConeOneSided
(and equivalent versions for y- and z-axes)
- Various improvements related to depletion:
- The matrix exponential solver can now be configured through the solver argument on depletion integrator classes.
- The
openmc.deplete.Chain.reduce()
method can automatically reduce the number of nuclides in a depletion chain. - Depletion integrator classes now allow a user to specify timesteps in several units (s, min, h, d, MWd/kg).
openmc.deplete.ResultsList.get_atoms()
now allows a user to obtain depleted material compositions in atom/b-cm.
- Several new methods on
openmc.Material
:- The
openmc.Material.add_elements_from_formula()
method allows a user to create a material based on a chemical formula. openmc.Material.add_element()
now supports the enrichment argument for non-uranium elements when only two isotopes are naturally occurring.openmc.Material.add_element()
now supports adding elements by name rather than by symbol.- The
openmc.Material.get_elements()
method returns a list of elements within a material. - The
openmc.Material.mix_materials()
method allows multiple materials to be mixed together based on atom, weight, or volume fractions.
- The
- The acceptable number of lost particles can now be configured through
openmc.Settings.max_lost_particles
andopenmc.Settings.rel_max_lost_particles
. - Delayed photons produced from fission are now accounted for by default by
scaling the yield of prompt fission photons. This behavior can be modified
through the
openmc.Settings.delayed_photon_scaling
attribute. - A trigger can now be specified for a volume calculation via the
openmc.VolumeCalculation.set_trigger()
method. - The
openmc.stats.SphericalIndependent
andopenmc.stats.CylindricalIndependent
classes allow a user to specify source distributions based on spherical or cylindrical coordinates. - Custom external source distributions can be used via the
openmc.Source.library
attribute. - Unstructured mesh class,
openmc.UnstructuredMesh
, that can be used in tallies. - The
openmc.CellInstanceFilter
class allows one or more instances of a repeated cell to be tallied. This is effectively a more flexible version of the existingopenmc.DistribcellFilter
class. - The
openmc.data.dose_coefficients()
function provides effective dose coefficients from ICRP-116 and can be used in conjunction withopenmc.EnergyFunctionFilter
in a tally.
Bug Fixes¶
- Keep user-supplied prev_results on operator
- Fix bug when S(a,b) tables appear in depletable material
- DAGMC fix for implicit complement material assignment
- Bug fix for tallying reaction rates in coupled n-p runs
- Corrected issue with multiplicity matrix
- Fix depletion with photon transport
- Fix secondary photon creation
- Bug fix for total xs plotting
- Account for light nuclide production in depletion
- Reset timer in depletion calculations
- Fix for Model.run
- Ensure NJOY output goes to specified directory
- Fix bug preventing creating photon data
- Fix bug when surface ID > 999999
- Fix bug for reading output settings in Settings.from_xml
- Fix improve energy deposition for coupled neutron-photon
- Use number of particles for tally normalization
- Fix a number of problems related to photoatomic data
- Fix cosine smearing for S(a,b)
- Use relative distances for coincidence test in hex lattice
- Fix RPATH for non-Debian linux systems
- Fix mesh plotter energy filter bins
- Fix memory leak
- Fix volume allocation related to burnable materials
- Fix tally mesh bug for short tracks
- DAGMC void material assignment fix
- Fix for Mesh __repr__ methods
Contributors¶
This release contains new contributions from the following people:
- Paul Cosgrove
- Steven Dargaville
- Andrew Davis
- Iurii Drobyshev
- Guillaume Giudicelli
- Alec Golas
- Avery Grieve
- Sterling Harper
- Yuan Hu
- Yue Jin
- Andrew Johnson
- Mikolaj Kowalski
- Shikhar Kumar
- Jingang Liang
- David Long
- Amanda Lund
- Alex Lyons
- Adam Nelson
- Ethan Peterson
- Sam Powell-Gill
- Ariful Islam Pranto
- Simon Richards
- Gavin Ridley
- Paul Romano
- Jonathan Shimwell
- Patrick Shriwise
- John Tramm
- Paul P.H. Wilson
- Jiankai Yu
What’s New in 0.11.0¶
Summary¶
This release of OpenMC adds several major new features: depletion, photon transport, and support for CAD geometries through DAGMC. In addition, the core codebase has been rewritten in C++14 (it was previously written in Fortran 2008). This makes compiling the code considerably simpler as no Fortran compiler is needed.
Functional expansion tallies are now supported through several new tally filters that can be arbitrarily combined:
openmc.LegendreFilter
openmc.SpatialLegendreFilter
openmc.SphericalHarmonicsFilter
openmc.ZernikeFilter
openmc.ZernikeRadialFilter
Note that these filters replace the use expansion scores like scatter-P1
.
Instead, a normal scatter
score should be used along with a
openmc.LegendreFilter
.
The interface for random sphere packing has been significantly improved. A new
openmc.model.pack_spheres()
function takes a region and generates a
random, non-overlapping configuration of spheres within the region.
New Features¶
- White boundary conditions can be applied to surfaces
- Support for rectilinear meshes through
openmc.RectilinearMesh
. - The
Geometry
,Materials
, andSettings
classes now have afrom_xml
method that will build an instance from an existing XML file. - Predefined energy group structures can be found in
openmc.mgxs.GROUP_STRUCTURES
. - New tally scores:
H1-production
,H2-production
,H3-production
,He3-production
,He4-production
,heating
,heating-local
, anddamage-energy
. - Switched to cell-based neighor lists (PR 1140)
- Two new probability distributions that can be used for source distributions:
openmc.stats.Normal
andopenmc.stats.Muir
- The
openmc.data
module now supports reading and sampling from ENDF File 32 resonance covariance data (PR 1024). - Several new convenience functions/methods have been added:
- The
openmc.model.cylinder_from_points()
function creates a cylinder given two points passing through its center and a radius. - The
openmc.Plane.from_points()
function creates a plane given three points that pass through it. - The
openmc.model.pin()
function creates a pin cell universe given a sequence of concentric cylinders and materials.
- The
Python API Changes¶
All surface classes now have coefficient arguments given as lowercase names.
The order of arguments in surface classes has been changed so that coefficients are the first arguments (rather than the optional surface ID). This means you can now write:
x = openmc.XPlane(5.0, 'reflective') zc = openmc.ZCylinder(0., 0., 10.)
The
Mesh
class has been renamedopenmc.RegularMesh
.The
get_rectangular_prism
function has been renamedopenmc.model.rectangular_prism()
.The
get_hexagonal_prism
function has been renamedopenmc.model.hexagonal_prism()
.Python bindings to the C/C++ API have been move from
openmc.capi
toopenmc.lib
.
Bug Fixes¶
- Rotate azimuthal distributions correctly for source sampling
- Fix reading ASCII ACE tables in Python 3
- Fix bug for distributed temperatures
- Fix bug for distance to boundary in complex cells
- Bug fixes for precursor decay rate tallies
- Check for invalid surface IDs in region expression
- Support for 32-bit operating systems
- Avoid segfault from unused nuclides
- Avoid overflow when broadcasting tally results
Contributors¶
This release contains new contributions from the following people:
- Brody Bassett
- Will Boyd
- Andrew Davis
- Iurii Drobyshev
- Guillaume Giudicelli
- Brittany Grayson
- Zhuoran Han
- Sterling Harper
- Andrew Johnson
- Colin Josey
- Shikhar Kumar
- Travis Labossiere-Hickman
- Matias Lavista
- Jingang Liang
- Alex Lindsay
- Johnny Liu
- Amanda Lund
- Jan Malec
- Isaac Meyer
- April Novak
- Adam Nelson
- Gavin Ridley
- Jose Salcedo Perez
- Paul Romano
- Sam Shaner
- Jonathan Shimwell
- Patrick Shriwise
- John Tramm
- Jiankai Yu
- Xiaokang Zhang
What’s New in 0.10.0¶
This release of OpenMC includes several new features, performance improvements,
and bug fixes compared to version 0.9.0. Notably, a C API has been added that
enables in-memory coupling of neutronics to other physics fields, e.g., burnup
calculations and thermal-hydraulics. The C API is also backed by Python bindings
in a new openmc.capi
package. Users should be forewarned that the C API
is still in an experimental state and the interface is likely to undergo changes
in future versions.
The Python API continues to improve over time; several backwards incompatible changes were made in the API which users of previous versions should take note of:
To indicate that nuclides in a material should be treated such that elastic scattering is isotropic in the laboratory system, there is a new
Material.isotropic
property:mat = openmc.Material() mat.add_nuclide('H1', 1.0) mat.isotropic = ['H1']
To treat all nuclides in a material this way, the
Material.make_isotropic_in_lab()
method can still be used.The initializers for
openmc.Intersection
andopenmc.Union
now expect an iterable.Auto-generated unique IDs for classes now start from 1 rather than 10000.
Attention
This is the last release of OpenMC that will support Python 2.7. Future releases of OpenMC will require Python 3.4 or later.
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions and Mac OS X. Numerous users have reported working builds on Microsoft Windows, but your mileage may vary. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides and tallies in the problem).
New Features¶
- Rotationally-periodic boundary conditions
- C API (with Python bindings) for in-memory coupling
- Improved correlation for Uranium enrichment
- Support for partial S(a,b) tables
- Improved handling of autogenerated IDs
- Many performance/memory improvements
Bug Fixes¶
- 937469: Fix energy group sampling for multi-group simulations
- a149ef: Ensure mutable objects are not hashable
- 2c9b21: Preserve backwards compatibility for generated HDF5 libraries
- 8047f6: Handle units of division for tally arithmetic correctly
- 0beb4c: Compatibility with newer versions of Pandas
- f124be: Fix generating 0K data with openmc.data.njoy module
- 0c6915: Bugfix for generating thermal scattering data
- 61ecb4: Fix bugs in Python multipole objects
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.9.0¶
This release of OpenMC is the first release to use a new native HDF5 cross
section format rather than ACE format cross sections. Other significant new
features include a nuclear data interface in the Python API (openmc.data
)
a stochastic volume calculation capability, a random sphere packing algorithm
that can handle packing fractions up to 60%, and a new XML parser with
significantly better performance than the parser used previously.
Caution
With the new cross section format, the default energy units are now electronvolts (eV) rather than megaelectronvolts (MeV)! If you are specifying an energy filter for a tally, make sure you use units of eV now.
The Python API continues to improve over time; several backwards incompatible changes were made in the API which users of previous versions should take note of:
Each type of tally filter is now specified with a separate class. For example:
energy_filter = openmc.EnergyFilter([0.0, 0.625, 4.0, 1.0e6, 20.0e6])
Several attributes of the
Plot
class have changed (color
->color_by
andcol_spec
>colors
).Plot.colors
now accepts a dictionary mappingCell
orMaterial
instances to RGB 3-tuples or string colors names, e.g.:plot.colors = { fuel: 'yellow', water: 'blue' }
make_hexagon_region
is nowget_hexagonal_prism()
Several changes in
Settings
attributes:weight
is now set asSettings.cutoff['weight']
- Shannon entropy is now specified by passing a
openmc.Mesh
toSettings.entropy_mesh
- Uniform fission site method is now specified by passing a
openmc.Mesh
toSettings.ufs_mesh
- All
sourcepoint_*
options are now specified in aSettings.sourcepoint
dictionary - Resonance scattering method is now specified as a dictionary in
Settings.resonance_scattering
- Multipole is now turned on by setting
Settings.temperature['multipole'] = True
- The
output_path
attribute is nowSettings.output['path']
All the
openmc.mgxs.Nu*
classes are gone. Instead, anu
argument was added to the constructor of the corresponding classes.
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions and Mac OS X. Numerous users have reported working builds on Microsoft Windows, but your mileage may vary. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides and tallies in the problem).
New Features¶
- Stochastic volume calculations
- Multi-delayed group cross section generation
- Ability to calculate multi-group cross sections over meshes
- Temperature interpolation on cross section data
- Nuclear data interface in Python API,
openmc.data
- Allow cutoff energy via
Settings.cutoff
- Ability to define fuel by enrichment (see
Material.add_element()
) - Random sphere packing for TRISO particle generation,
openmc.model.pack_trisos()
- Critical eigenvalue search,
openmc.search_for_keff()
- Model container,
openmc.model.Model
- In-line plotting in Jupyter,
openmc.plot_inline()
- Energy function tally filters,
openmc.EnergyFunctionFilter
- Replaced FoX XML parser with pugixml
- Cell/material instance counting,
Geometry.determine_paths()
- Differential tallies (see
openmc.TallyDerivative
) - Consistent multi-group scattering matrices
- Improved documentation and new Jupyter notebooks
- OpenMOC compatibility module,
openmc.openmoc_compatible
Bug Fixes¶
- c5df6c: Fix mesh filter max iterator check
- 1cfa39: Reject external source only if 95% of sites are rejected
- 335359: Fix bug in plotting meshlines
- 17c678: Make sure system_clock uses high-resolution timer
- 23ec0b: Fix use of S(a,b) with multipole data
- 7eefb7: Fix several bugs in tally module
- 7880d4: Allow plotting calculation with no boundary conditions
- ad2d9f: Fix filter weight missing when scoring all nuclides
- 59fdca: Fix use of source files for fixed source calculations
- 9eff5b: Fix thermal scattering bugs
- 7848a9: Fix combined k-eff estimator producing NaN
- f139ce: Fix printing bug for tallies with AggregateNuclide
- b8ddfa: Bugfix for short tracks near tally mesh edges
- ec3cfb: Fix inconsistency in filter weights
- 5e9b06: Fix XML representation for verbosity
- c39990: Fix bug tallying reaction rates with multipole on
- c6b67e: Fix fissionable source sampling bug
- 489540: Check for void materials in tracklength tallies
- f0214f: Fixes/improvements to the ARES algorithm
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.8.0¶
This release of OpenMC includes a few new major features including the capability to perform neutron transport with multi-group cross section data as well as experimental support for the windowed multipole method being developed at MIT. Source sampling options have also been expanded significantly, with the option to supply arbitrary tabular and discrete distributions for energy, angle, and spatial coordinates.
The Python API has been significantly restructured in this release compared to version 0.7.1. Any scripts written based on the version 0.7.1 API will likely need to be rewritten. Some of the most visible changes include the following:
SettingsFile
is nowSettings
,MaterialsFile
is nowMaterials
, andTalliesFile
is nowTallies
.- The
GeometryFile
class no longer exists and is replaced by theGeometry
class which now has anexport_to_xml()
method. - Source distributions are defined using the
Source
class and assigned to theSettings.source
property. - The
Executor
class no longer exists and is replaced byopenmc.run()
andopenmc.plot_geometry()
functions.
The Python API documentation has also been significantly expanded.
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions and Mac OS X. Numerous users have reported working builds on Microsoft Windows, but your mileage may vary. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides and tallies in the problem).
New Features¶
- Multi-group mode
- Vast improvements to the Python API
- Experimental windowed multipole capability
- Periodic boundary conditions
- Expanded source sampling options
- Distributed materials
- Subcritical multiplication support
- Improved method for reproducible URR table sampling
- Refactor of continuous-energy reaction data
- Improved documentation and new Jupyter notebooks
Bug Fixes¶
- 70daa7: Make sure MT=3 cross section is not used
- 40b05f: Ensure source bank is resampled for fixed source runs
- 9586ed: Fix two hexagonal lattice bugs
- a855e8: Make sure graphite models don’t error out on max events
- 7294a1: Fix incorrect check on cmfd.xml
- 12f246: Ensure number of realizations is written to statepoint
- 0227f4: Fix bug when sampling multiple energy distributions
- 51deaa: Prevent segfault when user specifies ‘18’ on tally scores
- fed74b: Prevent duplicate tally scores
- 8467ae: Better threshold for allowable lost particles
- 493c6f: Fix type of return argument for h5pget_driver_f
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.7.1¶
This release of OpenMC provides some substantial improvements over version
0.7.0. Non-simple cell regions can now be defined through the |
(union) and
~
(complement) operators. Similar changes in the Python API also allow
complex cell regions to be defined. A true secondary particle bank now exists;
this is crucial for photon transport (to be added in the next minor release). A
rich API for multi-group cross section generation has been added via the
openmc.mgxs
Python module.
Various improvements to tallies have also been made. It is now possible to
explicitly specify that a collision estimator be used in a tally. A new
delayedgroup
filter and delayed-nu-fission
score allow a user to obtain
delayed fission neutron production rates filtered by delayed group. Finally, the
new inverse-velocity
score may be useful for calculating kinetics
parameters.
Caution
In previous versions, depending on how OpenMC was compiled binary output was either given in HDF5 or a flat binary format. With this version, all binary output is now HDF5 which means you must have HDF5 in order to install OpenMC. Please consult the user’s guide for instructions on how to compile with HDF5.
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Support for complex cell regions (union and complement operators)
- Generic quadric surface type
- Improved handling of secondary particles
- Binary output is now solely HDF5
openmc.mgxs
Python module enabling multi-group cross section generation- Collision estimator for tallies
- Delayed fission neutron production tallies with ability to filter by delayed group
- Inverse velocity tally score
- Performance improvements for binary search
- Performance improvements for reaction rate tallies
Bug Fixes¶
- 299322: Bug with material filter when void material present
- d74840: Fix triggers on tallies with multiple filters
- c29a81: Correctly handle maximum transport energy
- 3edc23: Fixes in the nu-scatter score
- 629e3b: Assume unspecified surface coefficients are zero in Python API
- 5dbe8b: Fix energy filters for openmc-plot-mesh-tally
- ff66f4: Fixes in the openmc-plot-mesh-tally script
- 441fd4: Fix bug in kappa-fission score
- 7e5974: Allow fixed source simulations from Python API
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.7.0¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Complete Python API
- Python 3 compatability for all scripts
- All scripts consistently named openmc-* and installed together
- New ‘distribcell’ tally filter for repeated cells
- Ability to specify outer lattice universe
- XML input validation utility (openmc-validate-xml)
- Support for hexagonal lattices
- Material union energy grid method
- Tally triggers
- Remove dependence on PETSc
- Significant OpenMP performance improvements
- Support for Fortran 2008 MPI interface
- Use of Travis CI for continuous integration
- Simplifications and improvements to test suite
Bug Fixes¶
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.6.2¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Meshline plotting capability
- Support for plotting cells/materials on middle universe levels
- Ability to model cells with no surfaces
- Compatibility with PETSc 3.5
- Compatability with OpenMPI 1.7/1.8
- Improved overall performance via logarithmic-mapped energy grid search
- Improved multi-threaded performance with atomic operations
- Support for fixed source problems with fissionable materials
Bug Fixes¶
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.6.1¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Coarse mesh finite difference (CMFD) acceleration no longer requires PETSc
- Statepoint file numbering is now zero-padded
- Python scripts now compatible with Python 2 or 3
- Ability to run particle restarts in fixed source calculations
- Capability to filter box source by fissionable materials
- Nuclide/element names are now case insensitive in input files
- Improved treatment of resonance scattering for heavy nuclides
Bug Fixes¶
- 03e890: Check for energy-dependent multiplicities in ACE files
- 4439de: Fix distance-to-surface calculation for general plane surface
- 5808ed: Account for differences in URR band probabilities at different energies
- 2e60c0: Allow zero atom/weight percents in materials
- 3e0870: Don’t use PWD environment variable when setting path to input files
- dc4776: Handle probability table resampling correctly
- 01178b: Fix metastables nuclides in NNDC cross_sections.xml file
- 62ec43: Don’t read tallies.xml when OpenMC is run in plotting mode
- 2a95ef: Prevent segmentation fault on “current” score without mesh filter
- 93e482: Check for negative values in probability tables
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.6.0¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Legendre and spherical harmonic expansion tally scores
- CMake is now default build system
- Regression test suite based on CTests and NNDC cross sections
- FoX is now a git submodule
- Support for older cross sections (e.g. MCNP 66c)
- Progress bar for plots
- Expanded support for natural elements via <natural_elements> in settings.xml
Bug Fixes¶
- 41f7ca: Fixed erroneous results from survival biasing
- 038736: Fix tallies over void materials
- 46f9e8: Check for negative values in probability tables
- d1ca35: Fixed sampling of angular distribution
- 0291c0: Fixed indexing error in plotting
- d7a7d0: Fix bug with <element> specifying xs attribute
- 85b3cb: Fix out-of-bounds error with OpenMP threading
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.5.4¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Source sites outside geometry are resampled
- XML-Fortran backend replaced by FoX XML
- Ability to write particle track files
- Handle lost particles more gracefully (via particle track files)
- Multiple random number generator streams
- Mesh tally plotting utility converted to use Tkinter rather than PyQt
- Script added to download ACE data from NNDC
- Mixed ASCII/binary cross_sections.xml now allowed
- Expanded options for writing source bank
- Re-enabled ability to use source file as starting source
- S(a,b) recalculation avoided when same nuclide and S(a,b) table are accessed
Bug Fixes¶
- 32c03c: Check for valid data in cross_sections.xml
- c71ef5: Fix bug in statepoint.py
- 8884fb: Check for all ZAIDs for S(a,b) tables
- b38af0: Fix XML reading on multiple levels of input
- d28750: Fix bug in convert_xsdir.py
- cf567c: ENDF/B-VI data checked for compatibility
- 6b9461: Fix p_valid sampling inside of sample_energy
Contributors¶
This release contains new contributions from the following people:
What’s New in 0.5.3¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Output interface enhanced to allow multiple files handles to be opened
- Particle restart file linked to output interface
- Particle restarts and state point restarts are both identified with the -r command line flag.
- Particle instance no longer global, passed to all physics routines
- Physics routines refactored to rely less on global memory, more arguments passed in
- CMFD routines refactored and now can compute dominance ratio on the fly
- PETSc 3.4.2 or higher must be used and compiled with fortran datatype support
- Memory leaks fixed except for ones from xml-fortran package
- Test suite enhanced to test output with different compiler options
- Description of OpenMC development workflow added
- OpenMP shared-memory parallelism added
- Special run mode –tallies removed.
Bug Fixes¶
- 2b1e8a: Normalize direction vector after reflecting particle.
- 5853d2: Set blank default for cross section listing alias.
- e178c7: Fix infinite loop with words greater than 80 characters in write_message.
- c18a6e: Check for valid secondary mode on S(a,b) tables.
- 82c456: Fix bug where last process could have zero particles.
What’s New in 0.5.2¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Python script for mesh tally plotting
- Isotopic abundances based on IUPAC 2009 when using <element>
- Particle restart files for debugging
- Code will abort after certain number of lost particles (defaults to 10)
- Region outside lattice can be filled with material (void by default)
- 3D voxel plots
- Full HDF5/PHDF5 support (including support in statepoint.py)
- Cell overlap checking with -g command line flag (or when plotting)
Bug Fixes¶
- 7632f3: Fixed bug in statepoint.py for multiple generations per batch.
- f85ac4: Fix infinite loop bug in error module.
- 49c36b: Don’t convert surface ids if surface filter is for current tallies.
- 5ccc78: Fix bug in reassignment of bins for mesh filter.
- b1f52f: Fixed bug in plot color specification.
- eae7e5: Fixed many memory leaks.
- 10c1cc: Minor CMFD fixes.
- afdb50: Add compatibility for XML comments without whitespace.
- a3c593: Fixed bug in use of free gas scattering for H-1.
- 3a66e3: Fixed bug in 2D mesh tally implementation.
- ab0793: Corrected PETSC_NULL references to their correct types.
- 182ebd: Use analog estimator with energyout filter.
What’s New in 0.5.1¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Absorption and combined estimators for k-effective.
- Natural elements can now be specified in materials using <element> rather than <nuclide>.
- Support for multiple S(a,b) tables in a single material (e.g. BeO).
- Test suite using Python nosetests.
- Proper install capability with ‘make install’.
- Lattices can now be 2 or 3 dimensions.
- New scatter-PN score type.
- New kappa-fission score type.
- Ability to tally any reaction by specifying MT.
What’s New in 0.5.0¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- All user input options that formerly accepted “off” or “on” should now be “false” or “true” (the proper XML schema datatype).
- The <criticality> element is deprecated and was replaced with <eigenvalue>.
- Added ‘events’ score that returns number of events that scored to a tally.
- Restructured tally filter implementation and user input.
- Source convergence acceleration via CMFD (implemented with PETSc).
- Ability to read source files in parallel when number of particles is greater than that number of source sites.
- Cone surface types.
Bug Fixes¶
- 737b90: Coincident surfaces from separate universes / particle traveling tangent to a surface.
- a819b4: Output of surface neighbors in summary.out file.
- b11696: Reading long attribute lists in XML input.
- 2bd46a: Search for tallying nuclides when no default_xs specified.
- 7a1f08: Fix word wrapping when writing messages.
- c0e3ec: Prevent underflow when compiling with MPI=yes and DEBUG=yes.
- 6f8d9d: Set default tally labels.
- 6a3a5e: Fix problem with corner-crossing in lattices.
What’s New in 0.4.4¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Ability to write state points when using <no_reduce>.
- Real-time XML validation in GNU Emacs with RELAX NG schemata.
- Writing state points every n batches with <state_point interval=”…” />
- Suppress creation of summary.out and cross_sections.out by default with option to turn them on with <output> tag in settings.xml file.
- Ability to create HDF5 state points.
- Binary source file is now part of state point file by default.
- Enhanced state point usage and added state point Python scripts.
- Turning confidence intervals on affects k-effective.
- Option to specify <upper_right> for tally meshes.
What’s New in 0.4.3¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Option to report confidence intervals for tally results.
- Rotation and translation for filled cells.
- Ability to explicitly specify <estimator> for tallies.
- Ability to store state points and use them to restart runs.
- Fixed source calculations (no subcritical multiplication however).
- Expanded options for external source distribution.
- Ability to tally reaction rates for individual nuclides within a material.
- Reduced memory usage by removing redundant storage or some cross-sections.
- 3bd35b: Log-log interpolation for URR probability tables.
- Support to specify labels on tallies (nelsonag).
Bug Fixes¶
- 33f29a: Handle negative values in probability table.
- 1c472d: Fixed survival biasing with probability tables.
- 3c6e80: Fixed writing tallies with no filters.
- 460ef1: Invalid results for duplicate tallies.
- 0069d5: Fixed bug with 0 inactive batches.
- 7af2cf: Fixed bug in score_analog_tallies.
- 85a60e: Pick closest angular distribution for law 61.
- 3212f5: Fixed issue with blank line at beginning of XML files.
What’s New in 0.4.2¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions, Mac OS X, and Microsoft Windows 7. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- Ability to specify void materials.
- Option to not reduce tallies across processors at end of each batch.
- Uniform fission site method for reducing variance on local tallies.
- Reading/writing binary source files.
- Added more messages for <trace> or high verbosity.
- Estimator for diffusion coefficient.
- Ability to specify ‘point’ source type.
- Ability to change random number seed.
- Users can now specify units=’sum’ on a <density> tag. This tells the code that the total material density is the sum of the atom fractions listed for each nuclide on the material.
Bug Fixes¶
- a27f8f: Fixed runtime error bug when using Intel compiler with DEBUG on.
- afe121: Fixed minor bug in fission bank algorithms.
- e0968e: Force re-evaluation of cross-sections when each particle is born.
- 298db8: Fixed bug in surface currents when using energy-in filter.
- 2f3bbe: Fixed subtle bug in S(a,b) cross section calculation.
- 671f30: Fixed surface currents on mesh not encompassing geometry.
- b2c40e: Fixed bug in incoming energy filter for track-length tallies.
- 5524fd: Mesh filter now works with track-length tallies.
- d050c7: Added Bessel’s correction to make estimate of variance unbiased.
- 2a5b9c: Fixed regression in plotting.
What’s New in 0.4.1¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions as well as Mac OS X. However, it has not been tested yet on any releases of Microsoft Windows. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- A batching method has been implemented so that statistics can be calculated based on multiple generations instead of a single generation. This can help to overcome problems with underpredicted variance in problems where there is correlation between successive fission source iterations.
- Users now have the option to select a non-unionized energy grid for problems with many nuclides where the use of a unionized grid is not feasible.
- Improved plotting capability (Nick Horelik). The plotting input is now in
plots.xml
instead ofplot.xml
. - Added multiple estimators for k-effective and added a global tally for leakage.
- Moved cross section-related output into cross_sections.out.
- Improved timing capabilities.
- Can now use more than 2**31 - 1 particles per generation.
- Improved fission bank synchronization method. This also necessitated changing the source bank to be of type Bank rather than of type Particle.
- Added HDF5 output (not complete yet).
- Major changes to tally implementation.
Bug Fixes¶
- b206a8: Fixed subtle error in the sampling of energy distributions.
- 800742: Fixed error in sampling of angle and rotating angles.
- a07c08: Fixed bug in linear-linear interpolation during sampling energy.
- a75283: Fixed energy and energyout tally filters to support many bins.
- 95cfac: Fixed error in cell neighbor searches.
- 83a803: Fixed bug related to probability tables.
What’s New in 0.4.0¶
System Requirements¶
There are no special requirements for running the OpenMC code. As of this release, OpenMC has been tested on a variety of Linux distributions as well as Mac OS X. However, it has not been tested yet on any releases of Microsoft Windows. Memory requirements will vary depending on the size of the problem at hand (mostly on the number of nuclides in the problem).
New Features¶
- The probability table method for treatment of energy self-shielding in the unresolved resonance range has been implemented and is now turned on by default.
- Calculation of Shannon entropy for assessing convergence of the fission source distribution.
- Ability to compile with the PGI Fortran compiler.
- Ability to run on IBM BlueGene/P machines.
- Completely rewrote how nested universes are handled. Geometry is now much more robust.
Bug Fixes¶
- Many geometry errors have been fixed. The Monte Carlo performance benchmark can now be successfully run in OpenMC.
Theory and Methodology¶
Introduction¶
The physical process by which a population of particles evolves over time is governed by a number of probability distributions. For instance, given a particle traveling through some material, there is a probability distribution for the distance it will travel until its next collision (an exponential distribution). Then, when it collides with a nucleus, there is an associated probability of undergoing each possible reaction with that nucleus. While the behavior of any single particle is unpredictable, the average behavior of a large population of particles originating from the same source is well defined.
If the probability distributions that govern the transport of a particle are known, the process of single particles randomly streaming and colliding with nuclei can be simulated directly with computers using a technique known as Monte Carlo simulation. If enough particles are simulated this way, the average behavior can be determined to within arbitrarily small statistical error, a fact guaranteed by the central limit theorem. To be more precise, the central limit theorem tells us that the variance of the sample mean of some physical parameter being estimated with Monte Carlo will be inversely proportional to the number of realizations, i.e. the number of particles we simulate:
where \(\sigma^2\) is the variance of the sample mean and \(N\) is the number of realizations.
Overview of Program Flow¶
OpenMC performs a Monte Carlo simulation one particle at a time – at no point is more than one particle being tracked on a single program instance. Before any particles are tracked, the problem must be initialized. This involves the following steps:
- Read input files and building data structures for the geometry, materials, tallies, and other associated variables.
- Initialize the pseudorandom number generator.
- Read the contiuous-energy or multi-group cross section data specified in the problem.
- If using a special energy grid treatment such as a union energy grid or lethargy bins, that must be initialized as well in a continuous-energy problem.
- In a multi-group problem, individual nuclide cross section information is combined to produce material-specific cross section data.
- In a fixed source problem, source sites are sampled from the specified source. In an eigenvalue problem, source sites are sampled from some initial source distribution or from a source file. The source sites consist of coordinates, a direction, and an energy.
Once initialization is complete, the actual transport simulation can proceed. The life of a single particle will proceed as follows:
The particle’s properties are initialized from a source site previously sampled.
Based on the particle’s coordinates, the current cell in which the particle resides is determined.
The energy-dependent cross sections for the material that the particle is currently in are determined. Note that this includes the total cross section, which is not pre-calculated.
The distance to the nearest boundary of the particle’s cell is determined based on the bounding surfaces to the cell.
The distance to the next collision is sampled. If the total material cross section is \(\Sigma_t\), this can be shown to be
\[d = -\frac{\ln \xi}{\Sigma_t}\]where \(\xi\) is a pseudorandom number sampled from a uniform distribution on \([0,1)\).
If the distance to the nearest boundary is less than the distance to the next collision, the particle is moved forward to this boundary. Then, the process is repeated from step 2. If the distance to collision is closer than the distance to the nearest boundary, then the particle will undergo a collision.
The material at the collision site may consist of multiple nuclides. First, the nuclide with which the collision will happen is sampled based on the total cross sections. If the total cross section of material \(i\) is \(\Sigma_{t,i}\), then the probability that any nuclide is sampled is
\[P(i) = \frac{\Sigma_{t,i}}{\Sigma_t}.\]Note that the above selection of collided nuclide only applies to continuous-energy simulations as multi-group simulations use nuclide data which has already been combined in to material-specific data.
Once the specific nuclide is sampled, a reaction for that nuclide is randomly sampled based on the microscopic cross sections. If the microscopic cross section for some reaction \(x\) is \(\sigma_x\) and the total microscopic cross section for the nuclide is \(\sigma_t\), then the probability that reaction \(x\) will occur is
\[P(x) = \frac{\sigma_x}{\sigma_t}.\]Since multi-group simulations use material-specific data, the above is performed with those material multi-group cross sections (i.e., macroscopic cross sections for the material) instead of microscopic cross sections for the nuclide).
If the sampled reaction is elastic or inelastic scattering, the outgoing energy and angle is sampled from the appropriate distribution. In continuous-energy simulation, reactions of type \((n,xn)\) are treated as scattering and any additional particles which may be created are added to a secondary particle bank to be tracked later. In a multi-group simulation, this secondary bank is not used but the particle weight is increased accordingly. The original particle then continues from step 3. If the reaction is absorption or fission, the particle dies and if necessary, fission sites are created and stored in the fission bank.
After all particles have been simulated, there are a few final tasks that must be performed before the run is finished. This include the following:
- With the accumulated sum and sum of squares for each tally, the sample mean and its variance is calculated.
- All tallies and other results are written to disk.
- If requested, a source file is written to disk.
- Dynamically-allocated memory should be freed.
Geometry¶
Constructive Solid Geometry¶
OpenMC uses a technique known as constructive solid geometry (CSG) to build arbitrarily complex three-dimensional models in Euclidean space. In a CSG model, every unique object is described as the union and/or intersection of half-spaces created by bounding surfaces. Every surface divides all of space into exactly two half-spaces. We can mathematically define a surface as a collection of points that satisfy an equation of the form \(f(x,y,z) = 0\) where \(f(x,y,z)\) is a given function. All coordinates for which \(f(x,y,z) < 0\) are referred to as the negative half-space (or simply the negative side) and coordinates for which \(f(x,y,z) > 0\) are referred to as the positive half-space.
Let us take the example of a sphere centered at the point \((x_0,y_0,z_0)\) with radius \(R\). One would normally write the equation of the sphere as
By subtracting the right-hand term from both sides of equation (1), we can then write the surface equation for the sphere:
One can confirm that any point inside this sphere will correspond to \(f(x,y,z) < 0\) and any point outside the sphere will correspond to \(f(x,y,z) > 0\).
In OpenMC, every surface defined by the user is assigned an integer to uniquely identify it. We can then refer to either of the two half-spaces created by a surface by a combination of the unique ID of the surface and a positive/negative sign. Figure 5 shows an example of an ellipse with unique ID 1 dividing space into two half-spaces.
Figure 5: Example of an ellipse and its associated half-spaces.
References to half-spaces created by surfaces are used to define regions of space of uniform composition, which are then assigned to cells. OpenMC allows regions to be defined using union, intersection, and complement operators. As in MCNP, the intersection operator is implicit as doesn’t need to be written in a region specification. A defined region is then associated with a material composition in a cell. Figure 6 shows an example of a cell region defined as the intersection of an ellipse and two planes.
Figure 6: The shaded region represents a cell bounded by three surfaces.
The ability to form regions based on bounding quadratic surfaces enables OpenMC to model arbitrarily complex three-dimensional objects. In practice, one is limited only by the different surface types available in OpenMC. The following table lists the available surface types, the identifier used to specify them in input files, the corresponding surface equation, and the input parameters needed to fully define the surface.
Surface | Identifier | Equation | Parameters |
---|---|---|---|
Plane perpendicular to \(x\)-axis | x-plane | \(x - x_0 = 0\) | \(x_0\) |
Plane perpendicular to \(y\)-axis | y-plane | \(y - y_0 = 0\) | \(y_0\) |
Plane perpendicular to \(z\)-axis | z-plane | \(z - z_0 = 0\) | \(z_0\) |
Arbitrary plane | plane | \(Ax + By + Cz = D\) | \(A\;B\;C\;D\) |
Infinite cylinder parallel to \(x\)-axis | x-cylinder | \((y-y_0)^2 + (z-z_0)^2 = R^2\) | \(y_0\;z_0\;R\) |
Infinite cylinder parallel to \(y\)-axis | y-cylinder | \((x-x_0)^2 + (z-z_0)^2 = R^2\) | \(x_0\;z_0\;R\) |
Infinite cylinder parallel to \(z\)-axis | z-cylinder | \((x-x_0)^2 + (y-y_0)^2 = R^2\) | \(x_0\;y_0\;R\) |
Sphere | sphere | \((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 = R^2\) | \(x_0 \; y_0 \; z_0 \; R\) |
Cone parallel to the \(x\)-axis | x-cone | \((y-y_0)^2 + (z-z_0)^2 = R^2(x-x_0)^2\) | \(x_0 \; y_0 \; z_0 \; R^2\) |
Cone parallel to the \(y\)-axis | y-cone | \((x-x_0)^2 + (z-z_0)^2 = R^2(y-y_0)^2\) | \(x_0 \; y_0 \; z_0 \; R^2\) |
Cone parallel to the \(z\)-axis | z-cone | \((x-x_0)^2 + (y-y_0)^2 = R^2(z-z_0)^2\) | \(x_0 \; y_0 \; z_0 \; R^2\) |
General quadric surface | quadric | \(Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K\) | \(A \; B \; C \; D \; E \; F \; G \; H \; J \; K\) |
Universes¶
OpenMC supports universe-based geometry similar to the likes of MCNP and Serpent. This capability enables user to model any identical repeated structures once and then fill them in various spots in the geometry. A prototypical example of a repeated structure would be a fuel pin within a fuel assembly or a fuel assembly within a core.
Each cell in OpenMC can either be filled with a normal material or with a universe. If the cell is filled with a universe, only the region of the universe that is within the defined boundaries of the parent cell will be present in the geometry. That is to say, even though a collection of cells in a universe may extend to infinity, not all of the universe will be “visible” in the geometry since it will be truncated by the boundaries of the cell that contains it.
When a cell is filled with a universe, it is possible to specify that the
universe filling the cell should be rotated and translated. This is done through
the rotation
and translation
attributes on a cell (note though that
these can only be specified on a cell that is filled with another universe, not
a material).
It is not necessary to use or assign universes in a geometry if there are no repeated structures. Any cell in the geometry that is not assigned to a specified universe is automatically part of the base universe whose coordinates are just the normal coordinates in Euclidean space.
Lattices¶
Often times, repeated structures in a geometry occur in a regular pattern such as a rectangular or hexagonal lattice. In such a case, it would be cumbersome for a user to have to define the boundaries of each of the cells to be filled with a universe. Thus, OpenMC provides a lattice capability similar to that used in MCNP and Serpent.
The implementation of lattices is similar in principle to universes — instead of a cell being filled with a universe, the user can specify that it is filled with a finite lattice. The lattice is then defined by a two-dimensional array of universes that are to fill each position in the lattice. A good example of the use of lattices and universes can be seen in the OpenMC model for the Monte Carlo Performance benchmark.
Computing the Distance to Nearest Boundary¶
One of the most basic algorithms in any Monte Carlo code is determining the distance to the nearest surface within a cell. Since each cell is defined by the surfaces that bound it, if we compute the distance to all surfaces bounding a cell, we can determine the nearest one.
With the possibility of a particle having coordinates on multiple levels (universes) in a geometry, we must exercise care when calculating the distance to the nearest surface. Each different level of geometry has a set of boundaries with which the particle’s direction of travel may intersect. Thus, it is necessary to check the distance to the surfaces bounding the cell in each level. This should be done starting the highest (most global) level going down to the lowest (most local) level. That ensures that if two surfaces on different levels are coincident, by default the one on the higher level will be selected as the nearest surface. Although they are not explicitly defined, it is also necessary to check the distance to surfaces representing lattice boundaries if a lattice exists on a given level.
The following procedure is used to calculate the distance to each bounding surface. Suppose we have a particle at \((x_0,y_0,z_0)\) traveling in the direction \(u_0,v_0,w_0\). To find the distance \(d\) to a surface \(f(x,y,z) = 0\), we need to solve the equation:
If no solutions to equation (3) exist or the only solutions are complex, then the particle’s direction of travel will not intersect the surface. If the solution to equation (3) is negative, this means that the surface is “behind” the particle, i.e. if the particle continues traveling in its current direction, it will not hit the surface. The complete derivation for different types of surfaces used in OpenMC will be presented in the following sections.
Since \(f(x,y,z)\) in general is quadratic in \(x\), \(y\), and \(z\), this implies that \(f(x_0 + du_0, y + dv_0, z + dw_0)\) is quadratic in \(d\). Thus we expect at most two real solutions to (3). If no solutions to (3) exist or the only solutions are complex, then the particle’s direction of travel will not intersect the surface. If the solution to (3) is negative, this means that the surface is “behind” the particle, i.e. if the particle continues traveling in its current direction, it will not hit the surface.
Once a distance has been computed to a surface, we need to check if it is closer than previously-computed distances to surfaces. Unfortunately, we cannot just use the minimum function because some of the calculated distances, which should be the same in theory (e.g. coincident surfaces), may be slightly different due to the use of floating-point arithmetic. Consequently, we should first check for floating-point equality of the current distance calculated and the minimum found thus far. This is done by checking if
where \(d\) is the distance to a surface just calculated, \(d_{min}\) is the minimum distance found thus far, and \(\epsilon\) is a small number. In OpenMC, this parameter is set to \(\epsilon = 10^{-14}\) since all floating calculations are done on 8-byte floating point numbers.
Plane Perpendicular to an Axis¶
The equation for a plane perpendicular to, for example, the x-axis is simply \(x - x_0 = 0\). As such, we need to solve \(x + du - x_0 = 0\). The solution for the distance is
Note that if the particle’s direction of flight is parallel to the x-axis, i.e. \(u = 0\), the distance to the surface will be infinity. While the example here was for a plane perpendicular to the x-axis, the same formula can be applied for the surfaces \(y = y_0\) and \(z = z_0\).
Generic Plane¶
The equation for a generic plane is \(Ax + By + Cz = D\). Thus, we need to solve the equation \(A(x + du) + B(y + dv) + C(z + dw) = D\). The solution to this equation for the distance is
Again, we need to check whether the denominator is zero. If so, this means that the particle’s direction of flight is parallel to the plane and it will therefore never hit the plane.
Cylinder Parallel to an Axis¶
The equation for a cylinder parallel to, for example, the x-axis is \((y - y_0)^2 + (z - z_0)^2 = R^2\). Thus, we need to solve \((y + dv - y_0)^2 + (z + dw - z_0)^2 = R^2\). Let us define \(\bar{y} = y - y_0\) and \(\bar{z} = z - z_0\). We then have
Expanding equation (7) and rearranging terms, we obtain
This is a quadratic equation for \(d\). To simplify notation, let us define \(a = v^2 + w^2\), \(k = \bar{y}v + \bar{z}w\), and \(c = \bar{y}^2 + \bar{z}^2 - R^2\). Thus, the distance is just the solution to \(ad^2 + 2kd + c = 0\):
A few conditions must be checked for. If \(a = 0\), this means the particle is parallel to the cylinder and will thus never intersect it. Also, if \(k^2 - ac < 0\), this means that both solutions to the quadratic are complex. In physical terms, this means that the ray along which the particle is traveling does not make any intersections with the cylinder.
If we do have intersections and \(c < 0\), this means that the particle is inside the cylinder. Thus, one solution should be positive and one should be negative. Clearly, the positive distance will occur when the sign on the square root of the discriminant is positive since \(a > 0\).
If we have intersections and \(c > 0\) this means that the particle is outside the cylinder. Thus, the solutions to the quadratic are either both positive or both negative. If they are both positive, the smaller (closer) one will be the solution with a negative sign on the square root of the discriminant.
The same equations and logic here can be used for cylinders that are parallel to the y- or z-axis with appropriate substitution of constants.
Sphere¶
The equation for a sphere is \((x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = R^2\). Thus, we need to solve the equation
Let us define \(\bar{x} = x - x_0\), \(\bar{y} = y - y_0\), and \(\bar{z} = z - z_0\). We then have
Expanding equation (11) and rearranging terms, we obtain
This is a quadratic equation for \(d\). To simplify notation, let us define \(k = \bar{x}u + \bar{y}v + \bar{z}w\) and \(c = \bar{x}^2 + \bar{y}^2 + \bar{z}^2 - R^2\). Thus, the distance is just the solution to \(d^2 + 2kd + c = 0\):
If the discriminant \(k^2 - c < 0\), this means that both solutions to the quadratic are complex. In physical terms, this means that the ray along which the particle is traveling does not make any intersections with the sphere.
If we do have intersections and \(c < 0\), this means that the particle is inside the sphere. Thus, one solution should be positive and one should be negative. The positive distance will occur when the sign on the square root of the discriminant is positive. If we have intersections but \(c > 0\) this means that the particle is outside the sphere. The solutions to the quadratic will then be either both positive or both negative. If they are both positive, the smaller (closer) one will be the solution with a negative sign on the square root of the discriminant.
Cone Parallel to an Axis¶
The equation for a cone parallel to, for example, the x-axis is \((y - y_0)^2 + (z - z_0)^2 = R^2(x - x_0)^2\). Thus, we need to solve \((y + dv - y_0)^2 + (z + dw - z_0)^2 = R^2(x + du - x_0)^2\). Let us define \(\bar{x} = x - x_0\), \(\bar{y} = y - y_0\), and \(\bar{z} = z - z_0\). We then have
Expanding equation (14) and rearranging terms, we obtain
Defining the terms
we then have the simple quadratic equation \(ad^2 + 2kd + c = 0\) which can be solved as described in Cylinder Parallel to an Axis.
General Quadric¶
The equation for a general quadric surface is \(Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0\). Thus, we need to solve the equation
Expanding equation (17) and rearranging terms, we obtain
Defining the terms
we then have the simple quadratic equation \(ad^2 + 2kd + c = 0\) which can be solved as described in Cylinder Parallel to an Axis.
Finding a Cell Given a Point¶
Another basic algorithm is to determine which cell contains a given point in the global coordinate system, i.e. if the particle’s position is \((x,y,z)\), what cell is it currently in. This is done in the following manner in OpenMC. With the possibility of multiple levels of coordinates, we must perform a recursive search for the cell. First, we start in the highest (most global) universe, which we call the base universe, and loop over each cell within that universe. For each cell, we check whether the specified point is inside the cell using the algorithm described in Finding a Lattice Tile. If the cell is filled with a normal material, the search is done and we have identified the cell containing the point. If the cell is filled with another universe, we then search all cells within that universe to see if any of them contain the specified point. If the cell is filled with a lattice, the position within the lattice is determined, and then whatever universe fills that lattice position is recursively searched. The search ends once a cell containing a normal material is found that contains the specified point.
Finding a Lattice Tile¶
If a particle is inside a lattice, its position inside the lattice must be determined before assigning it to a cell. Throughout this section, the volumetric units of the lattice will be referred to as “tiles”. Tiles are identified by thier indices, and the process of discovering which tile contains the particle is referred to as “indexing”.
Rectilinear Lattice Indexing¶
Indices are assigned to tiles in a rectilinear lattice based on the tile’s position along the \(x\), \(y\), and \(z\) axes. Figure 7 maps the indices for a 2D lattice. The indices, (1, 1), map to the lower-left tile. (5, 1) and (5, 5) map to the lower-right and upper-right tiles, respectively.
In general, a lattice tile is specified by the three indices, \((i_x, i_y, i_z)\). If a particle’s current coordinates are \((x, y, z)\) then the indices can be determined from these formulas:
where \((x_0, y_0, z_0)\) are the coordinates to the lower-left-bottom corner of the lattice, and \(p_0, p_1, p_2\) are the pitches along the \(x\), \(y\), and \(z\) axes, respectively.
Hexagonal Lattice Indexing¶
A skewed coordinate system is used for indexing hexagonal lattice tiles. Rather than a \(y\)-axis, another axis is used that is rotated 30 degrees counter-clockwise from the \(y\)-axis. This axis is referred to as the \(\alpha\)-axis. Figure 8 shows how 2D hexagonal tiles are mapped with the \((x, \alpha)\) basis. In this system, (0, 0) maps to the center tile, (0, 2) to the top tile, and (2, -1) to the middle tile on the right side.
Unfortunately, the indices cannot be determined with one simple formula as before. Indexing requires a two-step process, a coarse step which determines a set of four tiles that contains the particle and a fine step that determines which of those four tiles actually contains the particle.
In the first step, indices are found using these formulas:
where \(p_0\) is the lattice pitch (in the \(x\)-\(y\) plane). The true index of the particle could be \((i_x^*, i_\alpha^*)\), \((i_x^* + 1, i_\alpha^*)\), \((i_x^*, i_\alpha^* + 1)\), or \((i_x^* + 1, i_\alpha^* + 1)\).
The second step selects the correct tile from that neighborhood of 4. OpenMC does this by calculating the distance between the particle and the centers of each of the 4 tiles, and then picking the closest tile. This works because regular hexagonal tiles form a Voronoi tessellation which means that all of the points within a tile are closest to the center of that same tile.
Indexing along the \(z\)-axis uses the same method from rectilinear lattices, i.e.
Determining if a Coordinate is in a Cell¶
To determine which cell a particle is in given its coordinates, we need to be able to check whether a given cell contains a point. The algorithm for determining if a cell contains a point is as follows. For each surface that bounds a cell, we determine the particle’s sense with respect to the surface. As explained earlier, if we have a point \((x_0,y_0,z_0)\) and a surface \(f(x,y,z) = 0\), the point is said to have negative sense if \(f(x_0,y_0,z_0) < 0\) and positive sense if \(f(x_0,y_0,z_0) > 0\). If for all surfaces, the sense of the particle with respect to the surface matches the specified sense that defines the half-space within the cell, then the point is inside the cell. Note that this algorithm works only for simple cells defined as intersections of half-spaces.
It may help to illustrate this algorithm using a simple example. Let’s say we have a cell defined as
<surface id="1" type="sphere" coeffs="0 0 0 10" />
<surface id="2" type="x-plane" coeffs="-3" />
<surface id="3" type="y-plane" coeffs="2" />
<cell id="1" surfaces="-1 2 -3" />
This means that the cell is defined as the intersection of the negative half space of a sphere, the positive half-space of an x-plane, and the negative half-space of a y-plane. Said another way, any point inside this cell must satisfy the following equations
In order to determine if a point is inside the cell, we would substitute its coordinates into equation (23). If the inequalities are satisfied, than the point is indeed inside the cell.
Handling Surface Crossings¶
A particle will cross a surface if the distance to the nearest surface is closer than the distance sampled to the next collision. A number of things happen when a particle hits a surface. First, we need to check if a non-transmissive boundary condition has been applied to the surface. If a vacuum boundary condition has been applied, the particle is killed and any surface current tallies are scored to as needed. If a reflective boundary condition has been applied to the surface, surface current tallies are scored to and then the particle’s direction is changed according to the procedure in Reflective Boundary Conditions. Note that the white boundary condition can be considered as the special case of reflective boundary condition, where the same processing method will be applied to deal with the surface current tallies scoring, except for determining the changes of particle’s direction according to the procedures in White Boundary Conditions.
Next, we need to determine what cell is beyond the surface in the direction of travel of the particle so that we can evaluate cross sections based on its material properties. At initialization, a list of neighboring cells is created for each surface in the problem as described in Building Neighbor Lists. The algorithm outlined in Finding a Cell Given a Point is used to find a cell containing the particle with one minor modification; rather than searching all cells in the base universe, only the list of neighboring cells is searched. If this search is unsuccessful, then a search is done over every cell in the base universe.
Building Neighbor Lists¶
After the geometry has been loaded and stored in memory from an input file, OpenMC builds a list for each surface containing any cells that are bounded by that surface in order to speed up processing of surface crossings. The algorithm to build these lists is as follows. First, we loop over all cells in the geometry and count up how many times each surface appears in a specification as bounding a negative half-space and bounding a positive half-space. Two arrays are then allocated for each surface, one that lists each cell that contains the negative half-space of the surface and one that lists each cell that contains the positive half-space of the surface. Another loop is performed over all cells and the neighbor lists are populated for each surface.
Reflective Boundary Conditions¶
If the velocity of a particle is \(\mathbf{v}\) and it crosses a surface of the form \(f(x,y,z) = 0\) with a reflective boundary condition, it can be shown based on geometric arguments that the velocity vector will then become
where \(\hat{\mathbf{n}}\) is a unit vector normal to the surface at the point of the surface crossing. The rationale for this can be understood by noting that \((\mathbf{v} \cdot \hat{\mathbf{n}}) \hat{\mathbf{n}}\) is the projection of the velocity vector onto the normal vector. By subtracting two times this projection, the velocity is reflected with respect to the surface normal. Since the magnitude of the velocity of the particle will not change as it undergoes reflection, we can work with the direction of the particle instead, simplifying equation (24) to
where \(\mathbf{v} = || \mathbf{v} || \mathbf{\Omega}\). The direction of the surface normal will be the gradient of the surface at the point of crossing, i.e. \(\mathbf{n} = \nabla f(x,y,z)\). Substituting this into equation (25), we get
If we write the initial and final directions in terms of their vector components, \(\mathbf{\Omega} = (u,v,w)\) and \(\mathbf{\Omega'} = (u', v', w')\), this allows us to represent equation (25) as a series of equations:
One can then use equation (27) to develop equations for transforming a particle’s direction given the equation of the surface.
Plane Perpendicular to an Axis¶
For a plane that is perpendicular to an axis, the rule for reflection is almost so simple that no derivation is needed at all. Nevertheless, we will proceed with the derivation to confirm that the rules of geometry agree with our intuition. The gradient of the surface \(f(x,y,z) = x - x_0 = 0\) is simply \(\nabla f = (1, 0, 0)\). Note that this vector is already normalized, i.e. \(|| \nabla f || = 1\). The second two equations in (27) tell us that \(v\) and \(w\) do not change and the first tell us that
We see that reflection for a plane perpendicular to an axis only entails negating the directional cosine for that axis.
Generic Plane¶
A generic plane has the form \(f(x,y,z) = Ax + By + Cz - D = 0\). Thus, the gradient to the surface is simply \(\nabla f = (A,B,C)\) whose norm squared is \(A^2 + B^2 + C^2\). This implies that
Substituting equation (29) into equation (27) gives us the form of the solution. For example, the x-component of the reflected direction will be
Cylinder Parallel to an Axis¶
A cylinder parallel to, for example, the x-axis has the form \(f(x,y,z) = (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\). Thus, the gradient to the surface is
where we have introduced the constants \(\bar{y}\) and \(\bar{z}\). Taking the square of the norm of the gradient, we find that
This implies that
Substituting equations (33) and (31) into equation (27) gives us the form of the solution. In this case, the x-component will not change. The y- and z-components of the reflected direction will be
Sphere¶
The surface equation for a sphere has the form \(f(x,y,z) = (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\). Thus, the gradient to the surface is
where we have introduced the constants \(\bar{x}, \bar{y}, \bar{z}\). Taking the square of the norm of the gradient, we find that
This implies that
Substituting equations (37) and (35) into equation (27) gives us the form of the solution:
Cone Parallel to an Axis¶
A cone parallel to, for example, the z-axis has the form \(f(x,y,z) = (x - x_0)^2 + (y - y_0)^2 - R^2(z - z_0)^2 = 0\). Thus, the gradient to the surface is
where we have introduced the constants \(\bar{x}\), \(\bar{y}\), and \(\bar{z}\). Taking the square of the norm of the gradient, we find that
This implies that
Substituting equations (41) and (39) into equation (27) gives us the form of the solution:
White Boundary Conditions¶
The white boundary condition is usually applied in deterministic codes, where the particle will hit the surface and travel back with isotropic angular distribution. The change in particle’s direction is sampled from a cosine distribution instead of uniform. Figure 9 shows an example of cosine-distribution reflection on the arbitrary surface relative to the surface normal.

Figure 9: Cosine-distribution reflection on an arbitrary surface.
The probability density function (pdf) for the reflected direction can be expressed as follows,
where \(\mu = \cos \theta\) is the cosine of the polar angle between reflected direction and the normal to the surface; and \(\theta\) is the azimuthal angle in \([0,2\pi]\). We can separate the multivariate probability density into two separate univariate density functions, one for the cosine of the polar angle,
and one for the azimuthal angle,
Each of these density functions can be sampled by analytical inversion of the cumulative distribution distribution, resulting in the following sampling scheme:
where \(\xi_1\) and \(\xi_2\) are uniform random numbers on \([0,1)\). With the sampled values of \(\mu\) and \(\phi\), the final reflected direction vector can be computed via rotation of the surface normal using the equations from Transforming a Particle’s Coordinates. The white boundary condition can be applied to any kind of surface, as long as the normal to the surface is known as in Reflective Boundary Conditions.
Cross Section Representations¶
Continuous-Energy Data¶
In OpenMC, the data governing the interaction of neutrons with various nuclei for continous-energy problems are represented using an HDF5 format that can be produced by converting files in the ACE format, which is used by MCNP and Serpent. ACE-format data can be generated with the NJOY nuclear data processing system, which converts raw ENDF/B data into linearly-interpolable data as required by most Monte Carlo codes. Since ACE-format data can be converted into OpenMC’s HDF5 format, it is possible to perform direct comparison of OpenMC with other codes using the same underlying nuclear data library.
The ACE format contains continuous-energy cross sections for the following types of reactions: elastic scattering, fission (or first-chance fission, second-chance fission, etc.), inelastic scattering, \((n,xn)\), \((n,\gamma)\), and various other absorption reactions. For those reactions with one or more neutrons in the exit channel, secondary angle and energy distributions may be provided. In addition, fissionable nuclides have total, prompt, and/or delayed \(\nu\) as a function of energy and neutron precursor distributions. Many nuclides also have probability tables to be used for accurate treatment of self-shielding in the unresolved resonance range. For bound scatterers, separate tables with \(S(\alpha,\beta,T)\) scattering law data can be used.
Energy Grid Methods¶
The method by which continuous-energy cross sections for each nuclide in a problem are stored as a function of energy can have a substantial effect on the performance of a Monte Carlo simulation. Since the ACE format is based on linearly-interpolable cross sections, each nuclide has cross sections tabulated over a wide range of energies. Some nuclides may only have a few points tabulated (e.g. H-1) whereas other nuclides may have hundreds or thousands of points tabulated (e.g. U-238).
At each collision, it is necessary to sample the probability of having a particular type of interaction whether it be elastic scattering, \((n,2n)\), level inelastic scattering, etc. This requires looking up the microscopic cross sections for these reactions for each nuclide within the target material. Since each nuclide has a unique energy grid, it would be necessary to search for the appropriate index for each nuclide at every collision. This can become a very time-consuming process, especially if there are many nuclides in a problem as there would be for burnup calculations. Thus, there is a strong motive to implement a method of reducing the number of energy grid searches in order to speed up the calculation.
Logarithmic Mapping¶
To speed up energy grid searches, OpenMC uses a logarithmic mapping technique to limit the range of energies that must be searched for each nuclide. The entire energy range is divided up into equal-lethargy segments, and the bounding energies of each segment are mapped to bounding indices on each of the nuclide energy grids. By default, OpenMC uses 8000 equal-lethargy segments as recommended by Brown.
Windowed Multipole Representation¶
In addition to the usual pointwise representation of cross sections, OpenMC offers support for a data format called windowed multipole (WMP). This data format requires less memory than pointwise cross sections, and it allows on-the-fly Doppler broadening to arbitrary temperature.
The multipole method was introduced by Hwang and the faster windowed multipole method by Josey. In the multipole format, cross section resonances are represented by poles, \(p_j\), and residues, \(r_j\), in the complex plane. The 0K cross sections in the resolved resonance region can be computed by summing up a contribution from each pole:
Assuming free-gas thermal motion, cross sections in the multipole form can be analytically Doppler broadened to give the form:
where \(T\) is the temperature of the resonant scatterer, \(k_B\) is the Boltzmann constant, \(A\) is the mass of the target nucleus. For \(E \gg k_b T/A\), the \(C\) integral is approximately zero, simplifying the cross section to:
The \(W_i\) integral simplifies down to an analytic form. We define the Faddeeva function, \(W\) as:
Through this, the integral transforms as follows:
There are freely available algorithms to evaluate the Faddeeva function. For many nuclides, the Faddeeva function needs to be evaluated thousands of times to calculate a cross section. To mitigate that computational cost, the WMP method only evaluates poles within a certain energy “window” around the incident neutron energy and accounts for the effect of resonances outside that window with a polynomial fit. This polynomial fit is then broadened exactly. This exact broadening can make up for the removal of the \(C\) integral, as typically at low energies, only curve fits are used.
Note that the implementation of WMP in OpenMC currently assumes that inelastic scattering does not occur in the resolved resonance region. This is usually, but not always the case. Future library versions may eliminate this issue.
The data format used by OpenMC to represent windowed multipole data is specified in Windowed Multipole Library Format with a publicly available WMP library.
Temperature Treatment¶
At the beginning of a simulation, OpenMC collects a list of all temperatures that are present in a model. It then uses this list to determine what cross sections to load. The data that is loaded depends on what temperature method has been selected. There are three methods available:
Nearest: | Cross sections are loaded only if they are within a specified tolerance of the actual temperatures in the model. |
---|---|
Interpolation: | Cross sections are loaded at temperatures that bound the actual temperatures in the model. During transport, cross sections for each material are calculated using statistical linear-linear interpolation between bounding temperature. Suppose cross sections are available at temperatures \(T_1, T_2, ..., T_n\) and a material is assigned a temperature \(T\) where \(T_i < T < T_{i+1}\). Statistical interpolation is applied as follows: a uniformly-distributed random number of the unit interval, \(\xi\), is sampled. If \(\xi < (T - T_i)/(T_{i+1} - T_i)\), then cross sections at temperature \(T_{i+1}\) are used. Otherwise, cross sections at \(T_i\) are used. This procedure is applied for pointwise cross sections in the resolved resonance range, unresolved resonance probability tables, and \(S(\alpha,\beta)\) thermal scattering tables. |
Multipole: | Resolved resonance cross sections are calculated on-the-fly using techniques/data described in Windowed Multipole Representation. Cross section data is loaded for a single temperature and is used in the unresolved resonance and fast energy ranges. |
Multi-Group Data¶
The data governing the interaction of particles with various nuclei or materials are represented using a multi-group library format specific to the OpenMC code. The format is described in the MGXS Library Specification. The data itself can be prepared via traditional paths or directly from a continuous-energy OpenMC calculation by use of the Python API as is shown in an example notebook. This multi-group library consists of meta-data (such as the energy group structure) and multiple xsdata objects which contains the required microscopic or macroscopic multi-group data.
At a minimum, the library must contain the absorption cross section (\(\sigma_{a,g}\)) and a scattering matrix. If the problem is an eigenvalue problem then all fissionable materials must also contain either a fission production matrix cross section (\(\nu\sigma_{f,g\rightarrow g'}\)), or both the fission spectrum data (\(\chi_{g'}\)) and a fission production cross section (\(\nu\sigma_{f,g}\)), or, . The library must also contain the fission cross section (\(\sigma_{f,g}\)) or the fission energy release cross section (\(\kappa\sigma_{f,g}\)) if the associated tallies are required by the model using the library.
After a scattering collision, the outgoing particle experiences a change in both energy and angle. The probability of a particle resulting in a given outgoing energy group (g’) given a certain incoming energy group (g) is provided by the scattering matrix data. The angular information can be expressed either via Legendre expansion of the particle’s change-in-angle (\(\mu\)), a tabular representation of the probability distribution function of \(\mu\), or a histogram representation of the same PDF. The formats used to represent these are described in the MGXS Library Specification.
Unlike the continuous-energy mode, the multi-group mode does not explicitly track particles produced from scattering multiplication (i.e., \((n,xn)\)) reactions. These are instead accounted for by adjusting the weight of the particle after the collision such that the correct total weight is maintained. The weight adjustment factor is optionally provided by the multiplicity data which is required to be provided in the form of a group-wise matrix. This data is provided as a group-wise matrix since the probability of producing multiple particles in a scattering reaction depends on both the incoming energy, g, and the sampled outgoing energy, g’. This data represents the average number of particles emitted from a scattering reaction, given a scattering reaction has occurred:
If this scattering multiplication information is not provided in the library then no weight adjustment will be performed. This is equivalent to neglecting any additional particles produced in scattering multiplication reactions. However, this assumption will result in a loss of accuracy since the total particle population would not be conserved. This reduction in accuracy due to the loss in particle conservation can be mitigated by reducing the absorption cross section as needed to maintain particle conservation. This adjustment can be done when generating the library, or by OpenMC. To have OpenMC perform the adjustment, the total cross section (\(\sigma_{t,g}\)) must be provided. With this information, OpenMC will then adjust the absorption cross section as follows:
The above method is the same as is usually done with most deterministic solvers. Note that this method is less accurate than using the scattering multiplication weight adjustment since simply reducing the absorption cross section does not include any information about the outgoing energy of the particles produced in these reactions.
All of the data discussed in this section can be provided to the code independent of the particle’s direction of motion (i.e., isotropic), or the data can be provided as a tabular distribution of the polar and azimuthal particle direction angles. The isotropic representation is the most commonly used, however inaccuracies are to be expected especially near material interfaces where a material has a very large cross sections relative to the other material (as can be expected in the resonance range). The angular representation can be used to minimize this error.
Finally, the above options for representing the physics do not have to be consistent across the problem. The number of groups and the structure, however, does have to be consistent across the data sets. That is to say that each microscopic or macroscopic data set does not have to apply the same scattering expansion, treatment of multiplicity or angular representation of the cross sections. This allows flexibility for the model to use highly anisotropic scattering information in the water while the fuel can be simulated with linear or even isotropic scattering.
Random Number Generation¶
In order to sample probability distributions, one must be able to produce random numbers. The standard technique to do this is to generate numbers on the interval \([0,1)\) from a deterministic sequence that has properties that make it appear to be random, e.g. being uniformly distributed and not exhibiting correlation between successive terms. Since the numbers produced this way are not truly “random” in a strict sense, they are typically referred to as pseudorandom numbers, and the techniques used to generate them are pseudorandom number generators (PRNGs). Numbers sampled on the unit interval can then be transformed for the purpose of sampling other continuous or discrete probability distributions.
Linear Congruential Generators¶
There are a great number of algorithms for generating random numbers. One of the simplest and commonly used algorithms is called a linear congruential generator. We start with a random number seed \(\xi_0\) and a sequence of random numbers can then be generated using the following recurrence relation:
where \(g\), \(c\), and \(M\) are constants. The choice of these constants will have a profound effect on the quality and performance of the generator, so they should not be chosen arbitrarily. As Donald Knuth stated in his seminal work The Art of Computer Programming, “random numbers should not be generated with a method chosen at random. Some theory should be used.” Typically, \(M\) is chosen to be a power of two as this enables \(x \mod M\) to be performed using the bitwise AND operator with a bit mask. The constants for the linear congruential generator used by default in OpenMC are \(g = 2806196910506780709\), \(c = 1\), and \(M = 2^{63}\) (see L’Ecuyer).
Skip-ahead Capability¶
One of the important capabilities for a random number generator is to be able to skip ahead in the sequence of random numbers. Without this capability, it would be very difficult to maintain reproducibility in a parallel calculation. If we want to skip ahead \(N\) random numbers and \(N\) is large, the cost of sampling \(N\) random numbers to get to that position may be prohibitively expensive. Fortunately, algorithms have been developed that allow us to skip ahead in \(O(\log_2 N)\) operations instead of \(O(N)\). One algorithm to do so is described in a paper by Brown. This algorithm relies on the following relationship:
Note that equation (2) has the same general form as equation (1), so the idea is to determine the new multiplicative and additive constants in \(O(\log_2 N)\) operations.
References
Neutron Physics¶
There are limited differences between physics treatments used in the continuous-energy and multi-group modes. If distinctions are necessary, each of the following sections will provide an explanation of the differences. Otherwise, replacing any references of the particle’s energy (E) with references to the particle’s energy group (g) will suffice.
Sampling Distance to Next Collision¶
As a particle travels through a homogeneous material, the probability distribution function for the distance to its next collision \(\ell\) is
where \(\Sigma_t\) is the total macroscopic cross section of the material. Equation (1) tells us that the further the distance is to the next collision, the less likely the particle will travel that distance. In order to sample the probability distribution function, we first need to convert it to a cumulative distribution function
By setting the cumulative distribution function equal to \(\xi\), a random number on the unit interval, and solving for the distance \(\ell\), we obtain a formula for sampling the distance to next collision:
Since \(\xi\) is uniformly distributed on \([0,1)\), this implies that \(1 - \xi\) is also uniformly distributed on \([0,1)\) as well. Thus, the formula usually used to calculate the distance to next collision is
\((n,\gamma)\) and Other Disappearance Reactions¶
All absorption reactions other than fission do not produce any secondary neutrons. As a result, these are the easiest type of reactions to handle. When a collision occurs, the first step is to sample a nuclide within a material. Once the nuclide has been sampled, then a specific reaction for that nuclide is sampled. Since the total absorption cross section is pre-calculated at the beginning of a simulation, the first step in sampling a reaction is to determine whether a “disappearance” reaction occurs where no secondary neutrons are produced. This is done by sampling a random number \(\xi\) on the interval \([0,1)\) and checking whether
where \(\sigma_t\) is the total cross section, \(\sigma_a\) is the absorption cross section (this includes fission), and \(\sigma_f\) is the total fission cross section. If this condition is met, then the neutron is killed and we proceed to simulate the next neutron from the source bank.
Note that photons arising from \((n,\gamma)\) and other neutron reactions are not produced in a microscopically correct manner. Instead, photons are sampled probabilistically at each neutron collision, regardless of what reaction actually takes place. This is described in more detail in Photon Production.
Elastic Scattering¶
Note that the multi-group mode makes no distinction between elastic or inelastic scattering reactions. The specific multi-group scattering implementation is discussed in the Multi-Group Scattering section.
Elastic scattering refers to the process by which a neutron scatters off a nucleus and does not leave it in an excited. It is referred to as “elastic” because in the center-of-mass system, the neutron does not actually lose energy. However, in lab coordinates, the neutron does indeed lose energy. Elastic scattering can be treated exactly in a Monte Carlo code thanks to its simplicity.
Let us discuss how OpenMC handles two-body elastic scattering kinematics. The first step is to determine whether the target nucleus has any associated motion. Above a certain energy threshold (400 kT by default), all scattering is assumed to take place with the target at rest. Below this threshold though, we must account for the thermal motion of the target nucleus. Methods to sample the velocity of the target nucleus are described later in section Effect of Thermal Motion on Cross Sections. For the time being, let us assume that we have sampled the target velocity \(\mathbf{v}_t\). The velocity of the center-of-mass system is calculated as
where \(\mathbf{v}_n\) is the velocity of the neutron and \(A\) is the atomic mass of the target nucleus measured in neutron masses (commonly referred to as the atomic weight ratio). With the velocity of the center-of-mass calculated, we can then determine the neutron’s velocity in the center-of-mass system:
where we have used uppercase \(\mathbf{V}\) to denote the center-of-mass system. The direction of the neutron in the center-of-mass system is
At low energies, elastic scattering will be isotropic in the center-of-mass system, but for higher energies, there may be p-wave and higher order scattering that leads to anisotropic scattering. Thus, in general, we need to sample a cosine of the scattering angle which we will refer to as \(\mu\). For elastic scattering, the secondary angle distribution is always given in the center-of-mass system and is sampled according to the procedure outlined in Sampling Angular Distributions. After the cosine of the angle of scattering has been sampled, we need to determine the neutron’s new direction \(\mathbf{\Omega}'_n\) in the center-of-mass system. This is done with the procedure in Transforming a Particle’s Coordinates. The new direction is multiplied by the speed of the neutron in the center-of-mass system to obtain the new velocity vector in the center-of-mass:
Finally, we transform the velocity in the center-of-mass system back to lab coordinates:
In OpenMC, the angle and energy of the neutron are stored rather than the velocity vector itself, so the post-collision angle and energy can be inferred from the post-collision velocity of the neutron in the lab system.
For tallies that require the scattering cosine, it is important to store the scattering cosine in the lab system. If we know the scattering cosine in the center-of-mass, the scattering cosine in the lab system can be calculated as
However, equation (11) is only valid if the target was at rest. When the target nucleus does have thermal motion, the cosine of the scattering angle can be determined by simply taking the dot product of the neutron’s initial and final direction in the lab system.
Inelastic Scattering¶
Note that the multi-group mode makes no distinction between elastic or inelastic scattering reactions. The spceific multi-group scattering implementation is discussed in the Multi-Group Scattering section.
The major algorithms for inelastic scattering were described in previous sections. First, a scattering cosine is sampled using the algorithms in Sampling Angular Distributions. Then an outgoing energy is sampled using the algorithms in Sampling Energy Distributions. If the outgoing energy and scattering cosine were given in the center-of-mass system, they are transformed to laboratory coordinates using the algorithm described in Transforming a Particle’s Coordinates. Finally, the direction of the particle is changed also using the procedure in Transforming a Particle’s Coordinates.
Although inelastic scattering leaves the target nucleus in an excited state, no secondary photons from nuclear de-excitation are tracked in OpenMC.
\((n,xn)\) Reactions¶
Note that the multi-group mode makes no distinction between elastic or inelastic scattering reactions. The specific multi-group scattering implementation is discussed in the Multi-Group Scattering section.
These types of reactions are just treated as inelastic scattering and as such are subject to the same procedure as described in Inelastic Scattering. For reactions with integral multiplicity, e.g., \((n,2n)\), an appropriate number of secondary neutrons are created. For reactions that have a multiplicity given as a function of the incoming neutron energy (which occasionally occurs for MT=5), the weight of the outgoing neutron is multiplied by the multiplicity.
Multi-Group Scattering¶
In multi-group mode, a scattering collision requires that the outgoing energy group of the simulated particle be selected from a probability distribution, the change-in-angle selected from a probability distribution according to the outgoing energy group, and finally the particle’s weight adjusted again according to the outgoing energy group.
The first step in selecting an outgoing energy group for a particle in a given incoming energy group is to select a random number (\(\xi\)) between 0 and 1. This number is then compared to the cumulative distribution function produced from the outgoing group (g’) data for the given incoming group (g):
If the scattering data is represented as a Legendre expansion, then the value of \(\Sigma_{s,g \rightarrow g'}\) above is the 0th order for the given group transfer. If the data is provided as tabular or histogram data, then \(\Sigma_{s,g \rightarrow g'}\) is the sum of all bins of data for a given g and g’ pair.
Now that the outgoing energy is known the change-in-angle, \(\mu\) can be determined. If the data is provided as a Legendre expansion, this is done by rejection sampling of the probability distribution represented by the Legendre series. For efficiency, the selected values of the PDF (\(f(\mu)\)) are chosen to be between 0 and the maximum value of \(f(\mu)\) in the domain of -1 to 1. Note that this sampling scheme automatically forces negative values of the \(f(\mu)\) probability distribution function to be treated as zero probabilities.
If the angular data is instead provided as a tabular representation, then the value of \(\mu\) is selected as described in the Tabular Angular Distribution section with a linear-linear interpolation scheme.
If the angular data is provided as a histogram representation, then the value of \(\mu\) is selected in a similar fashion to that described for the selection of the outgoing energy (since the energy group representation is simply a histogram representation) except the CDF is composed of the angular bins and not the energy groups. However, since we are interested in a specific value of \(\mu\) instead of a group, then an angle is selected from a uniform distribution within from the chosen angular bin.
The final step in the scattering treatment is to adjust the weight of the neutron to account for any production of neutrons due to \((n,xn)\) reactions. This data is obtained from the multiplicity data provided in the multi-group cross section library for the material of interest. The scaled value will default to 1.0 if no value is provided in the library.
Fission¶
While fission is normally considered an absorption reaction, as far as it concerns a Monte Carlo simulation it actually bears more similarities to inelastic scattering since fission results in secondary neutrons in the exit channel. Other absorption reactions like \((n,\gamma)\) or \((n,\alpha)\), on the contrary, produce no neutrons. There are a few other idiosyncrasies in treating fission. In an eigenvalue calculation, secondary neutrons from fission are only “banked” for use in the next generation rather than being tracked as secondary neutrons from elastic and inelastic scattering would be. On top of this, fission is sometimes broken into first-chance fission, second-chance fission, etc. The nuclear data file either lists the partial fission reactions with secondary energy distributions for each one, or a total fission reaction with a single secondary energy distribution.
When a fission reaction is sampled in OpenMC (either total fission or, if data exists, first- or second-chance fission), the following algorithm is used to create and store fission sites for the following generation. First, the average number of prompt and delayed neutrons must be determined to decide whether the secondary neutrons will be prompt or delayed. This is important because delayed neutrons have a markedly different spectrum from prompt neutrons, one that has a lower average energy of emission. The total number of neutrons emitted \(\nu_t\) is given as a function of incident energy in the ENDF format. Two representations exist for \(\nu_t\). The first is a polynomial of order \(N\) with coefficients \(c_0,c_1,\dots,c_N\). If \(\nu_t\) has this format, we can evaluate it at incoming energy \(E\) by using the equation
The other representation is just a tabulated function with a specified interpolation law. The number of prompt neutrons released per fission event \(\nu_p\) is also given as a function of incident energy and can be specified in a polynomial or tabular format. The number of delayed neutrons released per fission event \(\nu_d\) can only be specified in a tabular format. In practice, we only need to determine \(nu_t\) and \(nu_d\). Once these have been determined, we can calculated the delayed neutron fraction
We then need to determine how many total neutrons should be emitted from fission. If no survival biasing is being used, then the number of neutrons emitted is
where \(w\) is the statistical weight and \(k_{eff}\) is the effective multiplication factor from the previous generation. The number of neutrons produced is biased in this manner so that the expected number of fission neutrons produced is the number of source particles that we started with in the generation. Since \(\nu\) is not an integer, we use the following procedure to obtain an integral number of fission neutrons to produce. If \(\xi > \nu - \lfloor \nu \rfloor\), then we produce \(\lfloor \nu \rfloor\) neutrons. Otherwise, we produce \(\lfloor \nu \rfloor + 1\) neutrons. Then, for each fission site produced, we sample the outgoing angle and energy according to the algorithms given in Sampling Angular Distributions and Sampling Energy Distributions respectively. If the neutron is to be born delayed, then there is an extra step of sampling a delayed neutron precursor group since they each have an associated secondary energy distribution.
The sampled outgoing angle and energy of fission neutrons along with the position of the collision site are stored in an array called the fission bank. In a subsequent generation, these fission bank sites are used as starting source sites.
The above description is similar for the multi-group mode except the data are provided as group-wise data instead of in a continuous-energy format. In this case, the outgoing energy of the fission neutrons are represented as histograms by way of either the nu-fission matrix or chi vector.
Secondary Angle-Energy Distributions¶
Note that this section is specific to continuous-energy mode since the multi-group scattering process has already been described including the secondary energy and angle sampling.
For a reaction with secondary products, it is necessary to determine the outgoing angle and energy of the products. For any reaction other than elastic and level inelastic scattering, the outgoing energy must be determined based on tabulated or parameterized data. The ENDF-6 Format specifies a variety of ways that the secondary energy distribution can be represented. ENDF File 5 contains uncorrelated energy distribution whereas ENDF File 6 contains correlated energy-angle distributions. The ACE format specifies its own representations based loosely on the formats given in ENDF-6. OpenMC’s HDF5 nuclear data files use a combination of ENDF and ACE distributions; in this section, we will describe how the outgoing angle and energy of secondary particles are sampled.
One of the subtleties in the nuclear data format is the fact that a single reaction product can have multiple angle-energy distributions. This is mainly useful for reactions with multiple products of the same type in the exit channel such as \((n,2n)\) or \((n,3n)\). In these types of reactions, each neutron is emitted corresponding to a different excitation level of the compound nucleus, and thus in general the neutrons will originate from different energy distributions. If multiple angle-energy distributions are present, they are assigned incoming-energy-dependent probabilities that can then be used to randomly select one.
Once a distribution has been selected, the procedure for determining the outgoing angle and energy will depend on the type of the distribution.
Product Angle-Energy Distributions¶
If the secondary distribution for a product was given in file 6 in ENDF, the angle and energy are correlated with one another and cannot be sampled separately. Several representations exist in ENDF/ACE for correlated angle-energy distributions.
N-Body Phase Space Distribution¶
Reactions in which there are more than two products of similar masses are sometimes best treated by using what’s known as an N-body phase distribution. This distribution has the following probability density function for outgoing energy and angle of the \(i\)-th particle in the center-of-mass system:
where \(n\) is the number of outgoing particles, \(C_n\) is a normalization constant, \(E_i^{max}\) is the maximum center-of-mass energy for particle \(i\), and \(E'\) is the outgoing energy. We see in equation (47) that the angle is simply isotropic in the center-of-mass system. The algorithm for sampling the outgoing energy is based on algorithms R28, C45, and C64 in the Monte Carlo Sampler. First we calculate the maximum energy in the center-of-mass using the following equation:
where \(A_p\) is the total mass of the outgoing particles in neutron masses, \(A\) is the mass of the original target nucleus in neutron masses, and \(Q\) is the Q-value of the reaction. Next we sample a value \(x\) from a Maxwell distribution with a nuclear temperature of one using the algorithm outlined in Maxwell Fission Spectrum. We then need to determine a value \(y\) that will depend on how many outgoing particles there are. For \(n = 3\), we simply sample another Maxwell distribution with unity nuclear temperature. For \(n = 4\), we use the equation
where \(\xi_i\) are random numbers sampled on the interval \([0,1)\). For \(n = 5\), we use the equation
After \(x\) and \(y\) have been determined, the outgoing energy is then calculated as
There are two important notes to make regarding the N-body phase space distribution. First, the documentation (and code) for MCNP5-1.60 has a mistake in the algorithm for \(n = 4\). That being said, there are no existing nuclear data evaluations which use an N-body phase space distribution with \(n = 4\), so the error would not affect any calculations. In the ENDF/B-VII.1 nuclear data evaluation, only one reaction uses an N-body phase space distribution at all, the \((n,2n)\) reaction with H-2.
Transforming a Particle’s Coordinates¶
Since all the multi-group data exists in the laboratory frame of reference, this section does not apply to the multi-group mode.
Once the cosine of the scattering angle \(\mu\) has been sampled either from a angle distribution or a correlated angle-energy distribution, we are still left with the task of transforming the particle’s coordinates. If the outgoing energy and scattering cosine were given in the center-of-mass system, then we first need to transform these into the laboratory system. The relationship between the outgoing energy in center-of-mass and laboratory is
where \(E'_{cm}\) is the outgoing energy in the center-of-mass system, \(\mu_{cm}\) is the scattering cosine in the center-of-mass system, \(E'\) is the outgoing energy in the laboratory system, and \(E\) is the incident neutron energy. The relationship between the scattering cosine in center-of-mass and laboratory is
where \(\mu\) is the scattering cosine in the laboratory system. The scattering cosine still only tells us the cosine of the angle between the original direction of the particle and the new direction of the particle. If we express the pre-collision direction of the particle as \(\mathbf{\Omega} = (u,v,w)\) and the post-collision direction of the particle as \(\mathbf{\Omega}' = (u',v',w')\), it is possible to relate the pre- and post-collision components. We first need to uniformly sample an azimuthal angle \(\phi\) in \([0, 2\pi)\). After the azimuthal angle has been sampled, the post-collision direction is calculated as
Effect of Thermal Motion on Cross Sections¶
Since all the multi-group data should be generated with thermal scattering treatments already, this section does not apply to the multi-group mode.
When a neutron scatters off of a nucleus, it may often be assumed that the target nucleus is at rest. However, the target nucleus will have motion associated with its thermal vibration, even at absolute zero (This is due to the zero-point energy arising from quantum mechanical considerations). Thus, the velocity of the neutron relative to the target nucleus is in general not the same as the velocity of the neutron entering the collision.
The effect of the thermal motion on the interaction probability can be written as
where \(v_n\) is the magnitude of the velocity of the neutron, \(\bar{\sigma}\) is an effective cross section, \(T\) is the temperature of the target material, \(\mathbf{v}_T\) is the velocity of the target nucleus, \(v_r = || \mathbf{v}_n - \mathbf{v}_T ||\) is the magnitude of the relative velocity, \(\sigma\) is the cross section at 0 K, and \(M (\mathbf{v}_T)\) is the probability distribution for the target nucleus velocity at temperature \(T\) (a Maxwellian). In a Monte Carlo code, one must account for the effect of the thermal motion on both the integrated cross section as well as secondary angle and energy distributions. For integrated cross sections, it is possible to calculate thermally-averaged cross sections by applying a kernel Doppler broadening algorithm to data at 0 K (or some temperature lower than the desired temperature). The most ubiquitous algorithm for this purpose is the SIGMA1 method developed by Red Cullen and subsequently refined by others. This method is used in the NJOY and PREPRO data processing codes.
The effect of thermal motion on secondary angle and energy distributions can be accounted for on-the-fly in a Monte Carlo simulation. We must first qualify where it is actually used however. All threshold reactions are treated as being independent of temperature, and therefore they are not Doppler broadened in NJOY and no special procedure is used to adjust the secondary angle and energy distributions. The only non-threshold reactions with secondary neutrons are elastic scattering and fission. For fission, it is assumed that the neutrons are emitted isotropically (this is not strictly true, but is nevertheless a good approximation). This leaves only elastic scattering that needs a special thermal treatment for secondary distributions.
Fortunately, it is possible to directly sample the velocity of the target nuclide and then use it directly in the kinematic calculations. However, this calculation is a bit more nuanced than it might seem at first glance. One might be tempted to simply sample a Maxwellian distribution for the velocity of the target nuclide. Careful inspection of equation (55) however tells us that target velocities that produce relative velocities which correspond to high cross sections will have a greater contribution to the effective reaction rate. This is most important when the velocity of the incoming neutron is close to a resonance. For example, if the neutron’s velocity corresponds to a trough in a resonance elastic scattering cross section, a very small target velocity can cause the relative velocity to correspond to the peak of the resonance, thus making a disproportionate contribution to the reaction rate. The conclusion is that if we are to sample a target velocity in the Monte Carlo code, it must be done in such a way that preserves the thermally-averaged reaction rate as per equation (55).
The method by which most Monte Carlo codes sample the target velocity for use in elastic scattering kinematics is outlined in detail by [Gelbard]. The derivation here largely follows that of Gelbard. Let us first write the reaction rate as a function of the velocity of the target nucleus:
where \(R\) is the reaction rate. Note that this is just the right-hand side of equation (55). Based on the discussion above, we want to construct a probability distribution function for sampling the target velocity to preserve the reaction rate – this is different from the overall probability distribution function for the target velocity, \(M ( \mathbf{v}_T )\). This probability distribution function can be found by integrating equation (56) to obtain a normalization factor:
Let us call the normalization factor in the denominator of equation (57) \(C\).
Constant Cross Section Model¶
It is often assumed that \(\sigma (v_r)\) is constant over the range of relative velocities of interest. This is a good assumption for almost all cases since the elastic scattering cross section varies slowly with velocity for light nuclei, and for heavy nuclei where large variations can occur due to resonance scattering, the moderating effect is rather small. Nonetheless, this assumption may cause incorrect answers in systems with low-lying resonances that can cause a significant amount of up-scatter that would be ignored by this assumption (e.g. U-238 in commercial light-water reactors). We will revisit this assumption later in Energy-Dependent Cross Section Model. For now, continuing with the assumption, we write \(\sigma (v_r) = \sigma_s\) which simplifies (57) to
The Maxwellian distribution in velocity is
where \(m\) is the mass of the target nucleus and \(k\) is Boltzmann’s constant. Notice here that the term in the exponential is dependent only on the speed of the target, not on the actual direction. Thus, we can change the Maxwellian into a distribution for speed rather than velocity. The differential element of velocity is
Let us define the Maxwellian distribution in speed as
To simplify things a bit, we’ll define a parameter
Substituting equation (62) into equation (61), we obtain
Now, changing variables in equation (58) by using the result from equation (61), our new probability distribution function is
Again, the Maxwellian distribution for the speed of the target nucleus has no dependence on the angle between the neutron and target velocity vectors. Thus, only the term \(|| \mathbf{v}_n - \mathbf{v}_T ||\) imposes any constraint on the allowed angle. Our last task is to take that term and write it in terms of magnitudes of the velocity vectors and the angle rather than the vectors themselves. We can establish this relation based on the law of cosines which tells us that
Thus, we can infer that
Inserting equation (66) into (64), we obtain
This expression is still quite formidable and does not lend itself to any natural sampling scheme. We can divide this probability distribution into two parts as such:
In general, any probability distribution function of the form \(p(x) = f_1(x) f_2(x)\) with \(f_1(x)\) bounded can be sampled by sampling \(x'\) from the distribution
and accepting it with probability
The reason for dividing and multiplying the terms by \(v_n + v_T\) is to ensure that the first term is bounded. In general, \(|| \mathbf{v}_n - \mathbf{v}_T ||\) can take on arbitrarily large values, but if we divide it by its maximum value \(v_n + v_T\), then it ensures that the function will be bounded. We now must come up with a sampling scheme for equation (69). To determine \(q(v_T)\), we need to integrate \(f_2\) in equation (68). Doing so we find that
Thus, we need to sample the probability distribution function
Now, let us do a change of variables with the following definitions
Substituting equation (73) into equation (72) along with \(dx = \beta dv_T\) and doing some crafty rearranging of terms yields
It’s important to make note of the following two facts. First, the terms outside the parentheses are properly normalized probability distribution functions that can be sampled directly. Secondly, the terms inside the parentheses are always less than unity. Thus, the sampling scheme for \(q(x)\) is as follows. We sample a random number \(\xi_1\) on the interval \([0,1)\) and if
then we sample the probability distribution \(2x^3 e^{-x^2}\) for \(x\) using rule C49 in the Monte Carlo Sampler which we can then use to determine the speed of the target nucleus \(v_T\) from equation (73). Otherwise, we sample the probability distribution \(\frac{4}{\sqrt{\pi}} x^2 e^{-x^2}\) for \(x\) using rule C61 in the Monte Carlo Sampler.
With a target speed sampled, we must then decide whether to accept it based on the probability in equation (70). The cosine can be sampled isotropically as \(\mu = 2\xi_2 - 1\) where \(\xi_2\) is a random number on the unit interval. Since the maximum value of \(f_1(v_T, \mu)\) is \(4\sigma_s / \sqrt{\pi} C'\), we then sample another random number \(\xi_3\) and accept the sampled target speed and cosine if
If is not accepted, then we repeat the process and resample a target speed and cosine until a combination is found that satisfies equation (76).
Energy-Dependent Cross Section Model¶
As was noted earlier, assuming that the elastic scattering cross section is constant in (56) is not strictly correct, especially when low-lying resonances are present in the cross sections for heavy nuclides. To correctly account for energy dependence of the scattering cross section entails performing another rejection step. The most common method is to sample \(\mu\) and \(v_T\) as in the constant cross section approximation and then perform a rejection on the ratio of the 0 K elastic scattering cross section at the relative velocity to the maximum 0 K elastic scattering cross section over the range of velocities considered:
where it should be noted that the maximum is taken over the range \([v_n - 4/\beta, 4_n + 4\beta]\). This method is known as Doppler broadening rejection correction (DBRC) and was first introduced by Becker et al.. OpenMC has an implementation of DBRC as well as an accelerated sampling method that samples the relative velocity directly.
S(\(\alpha,\beta,T\)) Tables¶
Note that S(\(\alpha,\beta,T\)) tables are only applicable to continuous-energy transport.
For neutrons with thermal energies, generally less than 4 eV, the kinematics of scattering can be affected by chemical binding and crystalline effects of the target molecule. If these effects are not accounted for in a simulation, the reported results may be highly inaccurate. There is no general analytic treatment for the scattering kinematics at low energies, and thus when nuclear data is processed for use in a Monte Carlo code, special tables are created that give cross sections and secondary angle/energy distributions for thermal scattering that account for thermal binding effects. These tables are mainly used for moderating materials such as light or heavy water, graphite, hydrogen in ZrH, beryllium, etc.
The theory behind S(\(\alpha,\beta,T\)) is rooted in quantum mechanics and is quite complex. Those interested in first principles derivations for formulae relating to S(\(\alpha,\beta,T\)) tables should be referred to the excellent books by [Williams] and [Squires]. For our purposes here, we will focus only on the use of already processed data as it appears in the ACE format.
Each S(\(\alpha,\beta,T\)) table can contain the following:
- Thermal inelastic scattering cross section;
- Thermal elastic scattering cross section;
- Correlated energy-angle distributions for thermal inelastic and elastic scattering.
Note that when we refer to “inelastic” and “elastic” scattering now, we are actually using these terms with respect to the scattering system. Thermal inelastic scattering means that the scattering system is left in an excited state; no particular nucleus is left in an excited state as would be the case for inelastic level scattering. In a crystalline material, the excitation of the scattering could correspond to the production of phonons. In a molecule, it could correspond to the excitation of rotational or vibrational modes.
Both thermal elastic and thermal inelastic scattering are generally divided into incoherent and coherent parts. Coherent elastic scattering refers to scattering in crystalline solids like graphite or beryllium. These cross sections are characterized by the presence of Bragg edges that relate to the crystal structure of the scattering material. Incoherent elastic scattering refers to scattering in hydrogenous solids such as polyethylene. As it occurs in ACE data, thermal inelastic scattering includes both coherent and incoherent effects and is dominant for most other materials including hydrogen in water.
Calculating Integrated Cross Sections¶
The first aspect of using S(\(\alpha,\beta,T\)) tables is calculating cross sections to replace the data that would normally appear on the incident neutron data, which do not account for thermal binding effects. For incoherent inelastic scattering, the cross section is stored as a linearly interpolable function on a specified energy grid. For coherent elastic data, the cross section can be expressed as
where \(E_i\) are the energies of the Bragg edges and \(s_i\) are related to crystallographic structure factors. Since the functional form of the cross section is just 1/E and the proportionality constant changes only at Bragg edges, the proportionality constants are stored and then the cross section can be calculated analytically based on equation (78). For incoherent elastic data, the cross section can be expressed as
where \(\sigma_b\) is the characteristic bound cross section and \(W'\) is the Debye-Waller integral divided by the atomic mass.
Outgoing Angle for Coherent Elastic Scattering¶
Another aspect of using S(\(\alpha,\beta,T\)) tables is determining the outgoing energy and angle of the neutron after scattering. For incoherent and coherent elastic scattering, the energy of the neutron does not actually change, but the angle does change. For coherent elastic scattering, the angle will depend on which Bragg edge scattered the neutron. The probability that edge \(i\) will scatter then neutron is given by
After a Bragg edge has been sampled, the cosine of the angle of scattering is given analytically by
where \(E_i\) is the energy of the Bragg edge that scattered the neutron.
Outgoing Angle for Incoherent Elastic Scattering¶
For incoherent elastic scattering, OpenMC has two methods for calculating the cosine of the angle of scattering. The first method uses the Debye-Waller integral, \(W'\), and the characteristic bound cross section as given directly in an ENDF-6 formatted file. In this case, the cosine of the angle of scattering can be sampled by inverting equation 7.4 from the ENDF-6 Format Manual:
where \(\xi\) is a random number sampled on unit interval and \(c = 2EW'\). In the second method, the probability distribution for the cosine of the angle of scattering is represented as a series of equally-likely discrete cosines \(\mu_{i,j}\) for each incoming energy \(E_i\) on the thermal elastic energy grid. First the outgoing angle bin \(j\) is sampled. Then, if the incoming energy of the neutron satisfies \(E_i < E < E_{i+1}\) the cosine of the angle of scattering is
where the interpolation factor is defined as
To better represent the true, continuous nature of the cosine distribution, the sampled value of \(mu'\) is then “smeared” based on the neighboring values. First, values of \(\mu\) are calculated for outgoing angle bins \(j-1\) and \(j+1\):
Then, a final cosine is calculated as:
where \(\xi\) is again a random number sampled on the unit interval. Care must be taken to ensure that \(\mu\) does not fall outside the interval \([-1,1]\).
Outgoing Energy and Angle for Inelastic Scattering¶
Each S(\(\alpha,\beta,T\)) table provides a correlated angle-energy secondary distribution for neutron thermal inelastic scattering. There are three representations used in the ACE thermal scattering data: equiprobable discrete outgoing energies, non-uniform yet still discrete outgoing energies, and continuous outgoing energies with corresponding probability and cumulative distribution functions provided in tabular format. These three representations all represent the angular distribution in a common format, using a series of discrete equiprobable outgoing cosines.
Equi-Probable Outgoing Energies¶
If the thermal data was processed with \(iwt = 1\) in NJOY, then the outgoing energy spectra is represented in the ACE data as a set of discrete and equiprobable outgoing energies. The procedure to determine the outgoing energy and angle is as such. First, the interpolation factor is determined from equation (84). Then, an outgoing energy bin is sampled from a uniform distribution and then interpolated between values corresponding to neighboring incoming energies:
where \(E_{i,j}\) is the j-th outgoing energy corresponding to the i-th incoming energy. For each combination of incoming and outgoing energies, there is a series equiprobable outgoing cosines. An outgoing cosine bin is sampled uniformly and then the final cosine is interpolated on the incoming energy grid:
where \(\mu_{i,j,k}\) is the k-th outgoing cosine corresponding to the j-th outgoing energy and the i-th incoming energy.
Skewed Equi-Probable Outgoing Energies¶
If the thermal data was processed with \(iwt=0\) in NJOY, then the outgoing energy spectra is represented in the ACE data according to the following: the first and last outgoing energies have a relative probability of 1, the second and second-to-last energies have a relative probability of 4, and all other energies have a relative probability of 10. The procedure to determine the outgoing energy and angle is similar to the method discussed above, except that the sampled probability distribution is now skewed accordingly.
Continuous Outgoing Energies¶
If the thermal data was processed with \(iwt=2\) in NJOY, then the outgoing energy spectra is represented by a continuous outgoing energy spectra in tabular form with linear-linear interpolation. The sampling of the outgoing energy portion of this format is very similar to Correlated Energy and Angle Distribution, but the sampling of the correlated angle is performed as it was in the other two representations discussed in this sub-section. In the Law 61 algorithm, we found an interpolation factor \(f\), statistically sampled an incoming energy bin \(\ell\), and sampled an outgoing energy bin \(j\) based on the tabulated cumulative distribution function. Once the outgoing energy has been determined with equation (34), we then need to decide which angular distribution data to use. Like the linear-linear interpolation case in Law 61, the angular distribution closest to the sampled value of the cumulative distribution function for the outgoing energy is utilized. The actual algorithm utilized to sample the outgoing angle is shown in equation (88). As in the case of incoherent elastic scattering with discrete cosine bins, the sampled cosine is smeared over neighboring angle bins to better approximate a continuous distribution.
Unresolved Resonance Region Probability Tables¶
Note that unresolved resonance treatments are only applicable to continuous-energy transport.
In the unresolved resonance energy range, resonances may be so closely spaced that it is not possible for experimental measurements to resolve all resonances. To properly account for self-shielding in this energy range, OpenMC uses the probability table method. For most thermal reactors, the use of probability tables will not significantly affect problem results. However, for some fast reactors and other problems with an appreciable flux spectrum in the unresolved resonance range, not using probability tables may lead to incorrect results.
Probability tables in the ACE format are generated from the UNRESR module in NJOY following the method of Levitt. A similar method employed for the RACER and MC21 Monte Carlo codes is described in a paper by Sutton and Brown. For the discussion here, we will focus only on use of the probability table table as it appears in the ACE format.
Each probability table for a nuclide contains the following information at a number of incoming energies within the unresolved resonance range:
- Cumulative probabilities for cross section bands;
- Total cross section (or factor) in each band;
- Elastic scattering cross section (or factor) in each band;
- Fission cross section (or factor) in each band;
- \((n,\gamma)\) cross section (or factor) in each band; and
- Neutron heating number (or factor) in each band.
It should be noted that unresolved resonance probability tables affect only integrated cross sections and no extra data need be given for secondary angle/energy distributions. Secondary distributions for elastic and inelastic scattering would be specified whether or not probability tables were present.
The procedure for determining cross sections in the unresolved range using probability tables is as follows. First, the bounding incoming energies are determined, i.e. find \(i\) such that \(E_i < E < E_{i+1}\). We then sample a cross section band \(j\) using the cumulative probabilities for table \(i\). This allows us to then calculate the elastic, fission, and capture cross sections from the probability tables interpolating between neighboring incoming energies. If interpolation is specified, then the cross sections are calculated as
where \(\sigma_{i,j}\) is the j-th band cross section corresponding to the i-th incoming neutron energy and \(f\) is the interpolation factor defined in the same manner as (84). If logarithmic interpolation is specified, the cross sections are calculated as
where the interpolation factor is now defined as
A flag is also present in the probability table that specifies whether an inelastic cross section should be calculated. If so, this is done from a normal reaction cross section (either MT=51 or a special MT). Finally, if the cross sections defined are above are specified to be factors and not true cross sections, they are multiplied by the underlying smooth cross section in the unresolved range to get the actual cross sections. Lastly, the total cross section is calculated as the sum of the elastic, fission, capture, and inelastic cross sections.
Variance Reduction Techniques¶
Survival Biasing¶
In problems with highly absorbing materials, a large fraction of neutrons may be killed through absorption reactions, thus leading to tallies with very few scoring events. To remedy this situation, an algorithm known as survival biasing or implicit absorption (or sometimes implicit capture, even though this is a misnomer) is commonly used.
In survival biasing, absorption reactions are prohibited from occurring and instead, at every collision, the weight of neutron is reduced by probability of absorption occurring, i.e.
where \(w'\) is the weight of the neutron after adjustment and \(w\) is the weight of the neutron before adjustment. A few other things need to be handled differently if survival biasing is turned on. Although fission reactions never actually occur with survival biasing, we still need to create fission sites to serve as source sites for the next generation in the method of successive generations. The algorithm for sampling fission sites is the same as that described in Fission. The only difference is in equation (14). We now need to produce
fission sites, where \(w\) is the weight of the neutron before being adjusted. One should note this is just the expected number of neutrons produced per collision rather than the expected number of neutrons produced given that fission has already occurred.
Additionally, since survival biasing can reduce the weight of the neutron to very low values, it is always used in conjunction with a weight cutoff and Russian rouletting. Two user adjustable parameters \(w_c\) and \(w_s\) are given which are the weight below which neutrons should undergo Russian roulette and the weight should they survive Russian roulette. The algorithm for Russian rouletting is as follows. After a collision if \(w < w_c\), then the neutron is killed with probability \(1 - w/w_s\). If it survives, the weight is set equal to \(w_s\). One can confirm that the average weight following Russian roulette is simply \(w\), so the game can be considered “fair”. By default, the cutoff weight in OpenMC is \(w_c = 0.25\) and the survival weight is \(w_s = 1.0\). These parameters vary from one Monte Carlo code to another.
References
[Gelbard] | Ely M. Gelbard, “Epithermal Scattering in VIM,” FRA-TM-123, Argonne National Laboratory (1979). |
[Squires] | G. L. Squires, Introduction to the Theory of Thermal Neutron Scattering, Cambridge University Press (1978). |
[Williams] | M. M. R. Williams, The Slowing Down and Thermalization of Neutrons, North-Holland Publishing Co., Amsterdam (1966). Note: This book can be obtained for free from the OECD. |
Photon Physics¶
Photons, being neutral particles, behave much in the same manner as neutrons, traveling in straight lines and experiencing occasional collisions that change their energy and direction. Photons undergo four basic interactions as they pass through matter: coherent (Rayleigh) scattering, incoherent (Compton) scattering, photoelectric effect, and pair/triplet production. Photons with energy in the MeV range may also undergo photonuclear reactions with an atomic nucleus. In addition to these primary interaction mechanisms, all processes other than coherent scattering can result in the excitation/ionization of atoms. The de-excitation of these atoms can result in the emission of electrons and photons. Electrons themselves also can produce photons by means of bremsstrahlung radiation.
Photon Interactions¶
Coherent (Rayleigh) Scattering¶
The elastic scattering of a photon off a free charged particle is known as Thomson scattering. The differential cross section is independent of the energy of the incident photon. For scattering off a free electron, the differential cross section is
where \(\mu\) is the cosine of the scattering angle and \(r_e\) is the classical electron radius. Thomson scattering can generally occur when the photon energy is much less than the rest mass energy of the particle.
In practice, most elastic scattering of photons off electrons happens not with free electrons but those bound in atoms. This process is known as Rayleigh scattering. The radiation scattered off of individual bound electrons combines coherently, and thus Rayleigh scattering is also known as coherent scattering. Even though conceptually we think of the photon interacting with a single electron, because the wave functions combine constructively it is really as though the photon is interacting with the entire atom.
The differential cross section for Rayleigh scattering is given by
where \(F(x,Z)\) is a form factor as a function of the momentum transfer \(x\) and the atomic number \(Z\) and the term \(F' + iF''\) accounts for anomalous scattering which can occur near absorption edges. In a Monte Carlo simulation, when coherent scattering occurs, we only need to sample the scattering angle using the differential cross section in (2) since the energy of the photon does not change. In OpenMC, anomalous scattering is ignored such that the differential cross section becomes
To construct a proper probability density, we need to normalize the differential cross section in (3) by the integrated coherent scattering cross section:
Since the form factor is given in terms of the momentum transfer, it is more convenient to change variables of the probability density to \(x^2\). The momentum transfer is traditionally expressed as
where \(k\) is the ratio of the photon energy to the electron rest mass, and the coefficient \(a\) can be shown to be
where \(m_e\) is the mass of the electron, \(c\) is the speed of light in a vacuum, and \(h\) is Planck’s constant. Using (5), we have \(\mu = 1 - [x/(ak)]^2\) and \(d\mu/dx^2 = -1/(ak)^2\). The probability density in \(x^2\) is
where \(\bar{x}\) is the maximum value of \(x\) that occurs for \(\mu=-1\),
and \(A(x^2, Z)\) is the integral of the square of the form factor:
As you see, we have multiplied and divided the probability density by the integral of the squared form factor so that the density in (7) is expressed as the product of two separate densities in parentheses. In OpenMC, a table of \(A(x^2, Z)\) versus \(x^2\) is pre-generated and used at run-time to do a table search on the cumulative distribution function:
Once a trial \(x^2\) value has been selected, we can calculate \(\mu\) and perform rejection sampling using the Thomson scattering differential cross section. The complete algorithm is as follows:
- Determine \(\bar{x}^2\) using (8).
- Determine \(A_{max} = A(\bar{x}^2, Z)\) using the pre-generated tabulated data.
- Sample the cumulative density by calculating \(A' = \xi_1 A_{max}\) where \(\xi_1\) is a uniformly distributed random number.
- Perform a binary search to determine the value of \(x^2\) which satisfies \(A(x^2, Z) = A'\).
- By combining (5) and (8), calculate \(\mu = 1 - 2x^2/\bar{x}^2\).
- If \(\xi_2 < (1 + \mu^2)/2\), accept \(\mu\). Otherwise, repeat the sampling at step 3.
Incoherent (Compton) Scattering¶
Before we noted that the Thomson cross section gives the behavior for photons scattering off of free electrons valid at low energies. The formula for photon scattering off of free electrons that is valid for all energies can be found using quantum electrodynamics and is known as the Klein-Nishina formula after the two authors who discovered it:
where \(k\) and \(k'\) are the ratios of the incoming and exiting photon energies to the electron rest mass energy equivalent (0.511 MeV), respectively. Although it appears that the outgoing energy and angle are separate, there is actually a one-to-one relationship between them such that only one needs to be sampled:
Note that when \(k'/k\) goes to one, i.e., scattering is elastic, the Klein-Nishina cross section becomes identical to the Thomson cross section. In general though, the scattering is inelastic and is known as Compton scattering. When a photon interacts with a bound electron in an atom, the Klein-Nishina formula must be modified to account for the binding effects. As in the case of coherent scattering, this is done by means of a form factor. The differential cross section for incoherent scattering is given by
where \(S(x,Z)\) is the form factor. The approach in OpenMC is to first sample the Klein-Nishina cross section and then perform rejection sampling on the form factor. As in other codes, Kahn’s rejection method is used for \(k < 3\) and a direct method by Koblinger is used for \(k \ge 3\). The complete algorithm is as follows:
- If \(k < 3\), sample \(\mu\) from the Klein-Nishina cross section using Kahn’s rejection method. Otherwise, use Koblinger’s direct method.
- Calculate \(x\) and \(\bar{x}\) using (5) and (8), respectively.
- If \(\xi < S(x, Z)/S(\bar{x}, Z)\), accept \(\mu\). Otherwise repeat from step 1.
Doppler Energy Broadening¶
Bound electrons are not at rest but have a momentum distribution that will cause the energy of the scattered photon to be Doppler broadened. More tightly bound electrons have a wider momentum distribution, so the energy spectrum of photons scattering off inner shell electrons will be broadened the most. In addition, scattering from bound electrons places a limit on the maximum scattered photon energy:
where \(E_{b,i}\) is the binding energy of the \(i\)-th subshell.
Compton profiles \(J_i(p_z)\) are used to account for the binding effects. The quantity \(p_z = {\bf p} \cdot {\bf q}/q\) is the projection of the initial electron momentum on \({\bf q}\), where the scattering vector \({\bf q} = {\bf p} - {\bf p'}\) is the momentum gained by the photon, \({\bf p}\) is the initial momentum of the electron, and \({\bf p'}\) is the momentum of the scattered electron. Applying the conservation of energy and momentum, \(p_z\) can be written in terms of the photon energy and scattering angle:
where \(\alpha\) is the fine structure constant. The maximum momentum transferred, \(p_{z,\text{max}}\), can be calculated from (15) using \(E' = E'_{\text{max}}\). The Compton profile of the \(i\)-th electron subshell is defined as
where \(\rho_i({\bf p})\) is the initial electron momentum distribution. \(J_i(p_z)\) can be interpreted as the probability density function of \(p_z\).
The Doppler broadened energy of the Compton-scattered photon can be sampled by selecting an electron shell, sampling a value of \(p_z\) using the Compton profile, and calculating the scattered photon energy. The theory and methods used to do this are described in detail in LA-UR-04-0487 and LA-UR-04-0488. The sampling algorithm is summarized below:
- Sample \(\mu\) from (13) using the algorithm described in Incoherent (Compton) Scattering.
- Sample the electron subshell \(i\) using the number of electrons per shell as the probability mass function.
- Sample \(p_z\) using \(J_i(p_z)\) as the PDF.
- Calculate \(E'\) by solving (15) for \(E'\) using the sampled value of \(p_z\).
- If \(p_z < p_{z,\text{max}}\) for shell \(i\), accept \(E'\). Otherwise repeat from step 2.
Compton Electrons¶
Because the Compton-scattered photons can transfer a large fraction of their energy to the kinetic energy of the recoil electron, which may in turn go on to lose its energy as bremsstrahlung radiation, it is necessary to accurately model the angular and energy distributions of Compton electrons. The energy of the recoil electron ejected from the \(i\)-th subshell is given by
The direction of the electron is assumed to be in the direction of the momentum transfer, with the cosine of the polar angle given by
and the azimuthal angle \(\phi_{-} = \phi + \pi\), where \(\phi\) is the azimuthal angle of the photon. The vacancy left by the ejected electron is filled through atomic relaxation.
Photoelectric Effect¶
In the photoelectric effect, the incident photon is absorbed by an atomic electron, which is then emitted from the \(i\)-th shell with kinetic energy
Photoelectric emission is only possible when the photon energy exceeds the binding energy of the shell. These binding energies are often referred to as edge energies because the otherwise continuously decreasing cross section has discontinuities at these points, creating the characteristic sawtooth shape. The photoelectric effect dominates at low energies and is more important for heavier elements.
When simulating the photoelectric effect, the first step is to sample the electron shell. The shell \(i\) where the ionization occurs can be considered a discrete random variable with probability mass function
where \(\sigma_{\text{pe},i}\) is the cross section of the \(i\)-th shell, and the total photoelectric cross section of the atom, \(\sigma_{\text{pe}}\), is the sum over the shell cross sections. Once the shell has been sampled, the energy of the photoelectron is calculated using (19).
To determine the direction of the photoelectron, we implement the method described in Kaltiaisenaho, which models the angular distribution of the photoelectrons using the K-shell cross section derived by Sauter (K-shell electrons are the most tightly bound, and they contribute the most to \(\sigma_{\text{pe}}\)). The non-relativistic Sauter distribution for unpolarized photons can be approximated as
where \(\beta_{-}\) is the ratio of the velocity of the electron to the speed of light,
To sample \(\mu_{-}\) from the Sauter distribution, we first express (21) in the form:
where
In the interval \([-1, 1]\), \(g(\mu_{-})\) is a normalized PDF and \(\psi(\mu_{-})\) satisfies the condition \(0 < \psi(\mu_{-}) < 1\). The following algorithm can now be used to sample \(\mu_{-}\):
Using the inverse transform method, sample \(\mu_{-}\) from \(g(\mu_{-})\) using the sampling formula
\[\mu_{-} = \frac{2\xi_1 + \beta_{-} - 1}{2\beta_{-}\xi_1 - \beta_{-} + 1}.\]If \(\xi_2 \le \psi(\mu_{-})\), accept \(\mu_{-}\). Otherwise, repeat the sampling from step 1.
The azimuthal angle is sampled uniformly on \([0, 2\pi)\).
The atom is left in an excited state with a vacancy in the \(i\)-th shell and decays to its ground state through a cascade of transitions that produce fluorescent photons and Auger electrons.
Pair Production¶
In electron-positron pair production, a photon is absorbed in the vicinity of an atomic nucleus or an electron and an electron and positron are created. Pair production is the dominant interaction with matter at high photon energies and is more important for high-Z elements. When it takes place in the field of a nucleus, energy is essentially conserved among the incident photon and the resulting charged particles. Therefore, in order for pair production to occur, the photon energy must be greater than the sum of the rest mass energies of the electron and positron, i.e., \(E_{\text{threshold,pp}} = 2 m_e c^2 = 1.022\) MeV.
The photon can also interact in the field of an atomic electron. This process is referred to as “triplet production” because the target electron is ejected from the atom and three charged particles emerge from the interaction. In this case, the recoiling electron also absorbs some energy, so the energy threshold for triplet production is greater than that of pair production from atomic nuclei, with \(E_{\text{threshold,tp}} = 4 m_e c^2 = 2.044\) MeV. The ratio of the triplet production cross section to the pair production cross section is approximately 1/Z, so triplet production becomes increasingly unimportant for high-Z elements. Though it can be significant in lighter elements, the momentum of the recoil electron becomes negligible in the energy regime where pair production dominates. For our purposes, it is a good approximation to treat triplet production as pair production and only simulate the electron-positron pair.
Accurately modeling the creation of electron-positron pair is important because the charged particles can go on to lose much of their energy as bremsstrahlung radiation, and the subsequent annihilation of the positron with an electron produces two additional photons. We sample the energy and direction of the charged particles using a semiempirical model described in Salvat. The Bethe-Heitler differential cross section, given by
is used as a starting point, where \(\alpha\) is the fine structure constant, \(f_C\) is the Coulomb correction function, \(\Phi_1\) and \(\Phi_2\) are screening functions, and \(\epsilon = (E_{-} + m_e c^2)/E\) is the electron reduced energy (i.e., the fraction of the photon energy given to the electron). \(\epsilon\) can take values between \(\epsilon_{\text{min}} = k^{-1}\) (when the kinetic energy of the electron is zero) and \(\epsilon_{\text{max}} = 1 - k^{-1}\) (when the kinetic energy of the positron is zero).
The Coulomb correction, given by
is introduced to correct for the fact that the Bethe-Heitler differential cross section was derived using the Born approximation, which treats the Coulomb interaction as a small perturbation.
The screening functions \(\Phi_1\) and \(\Phi_2\) account for the screening of the Coulomb field of the atomic nucleus by outer electrons. Since they are given by integrals which include the atomic form factor, they must be computed numerically for a realistic form factor. However, by assuming exponential screening and using a simplified form factor, analytical approximations of the screening functions can be derived:
where
and \(R\) is the screening radius.
The differential cross section in (25) with the approximations described above will not be accurate at low energies: the lower boundary of \(\epsilon\) will be shifted above \(\epsilon_{\text{min}}\) and the upper boundary of \(\epsilon\) will be shifted below \(\epsilon_{\text{max}}\). To offset this behavior, a correcting factor \(F_0(k, Z)\) is used:
To aid sampling, the differential cross section used to sample \(\epsilon\) (minus the normalization constant) can now be expressed in the form
where
and
The functions in (32) are non-negative and maximum at \(\epsilon = 1/2\). In the interval \((\epsilon_{\text{min}}, \epsilon_{\text{max}})\), the functions in (33) are normalized PDFs and \(\phi_i(\epsilon)/\phi_i(1/2)\) satisfies the condition \(0 < \phi_i(\epsilon)/\phi_i(1/2) < 1\). The following algorithm can now be used to sample the reduced electron energy \(\epsilon\):
Sample \(i\) according to the point probabilities \(p(i=1) = u_1/(u_1 + u_2)\) and \(p(i=2) = u_2/(u_1 + u_2)\).
Using the inverse transform method, sample \(\epsilon\) from \(\pi_i(\epsilon)\) using the sampling formula
\[\begin{aligned} \epsilon &= \frac{1}{2} + \left(\frac{1}{2} - \frac{1}{k}\right) (2\xi_1 - 1)^{1/3} ~~~~&\text{if}~~ i = 1 \\ \epsilon &= \frac{1}{k} + \left(\frac{1}{2} - \frac{1}{k}\right) 2\xi_1 ~~~~&\text{if}~~ i = 2. \end{aligned}\]If \(\xi_2 \le \phi_i(\epsilon)/\phi_i(1/2)\), accept \(\epsilon\). Otherwise, repeat the sampling from step 1.
Because charged particles have a much smaller range than the mean free path of photons and because they immediately undergo multiple scattering events which randomize their direction, it is sufficient to use a simplified model to sample the direction of the electron and positron. The cosines of the polar angles are sampled using the leading order term of the Sauter–Gluckstern–Hull distribution,
where \(C\) is a normalization constant and \(\beta_{\pm}\) is the ratio of the velocity of the charged particle to the speed of light given in (22).
The inverse transform method is used to sample \(\mu_{-}\) and \(\mu_{+}\) from (34), using the sampling formula
The azimuthal angles for the electron and positron are sampled independently and uniformly on \([0, 2\pi)\).
Secondary Processes¶
New photons may be produced in secondary processes related to the main photon interactions discussed above. A Compton-scattered photon transfers a portion of its energy to the kinetic energy of the recoil electron, which in turn may lose the energy as bremsstrahlung radiation. The vacancy left in the shell by the ejected electron is filled through atomic relaxation, creating a shower of electrons and fluorescence photons. Similarly, the vacancy left by the electron emitted in the photoelectric effect is filled through atomic relaxation. Pair production generates an electron and a positron, both of which can emit bremsstrahlung radiation before the positron eventually collides with an electron, resulting in annihilation of the pair and the creation of two additional photons.
Atomic Relaxation¶
When an electron is ejected from an atom and a vacancy is left in an inner shell, an electron from a higher energy level will fill the vacancy. This results in either a radiative transition, in which a photon with a characteristic energy (fluorescence photon) is emitted, or non-radiative transition, in which an electron from a shell that is farther out (Auger electron) is emitted. If a non-radiative transition occurs, the new vacancy is filled in the same manner, and as the process repeats a shower of photons and electrons can be produced.
The energy of a fluorescence photon is the equal to the energy difference between the transition states, i.e.,
where \(E_{b,v}\) is the binding energy of the vacancy shell and \(E_{b,i}\) is the binding energy of the shell from which the electron transitioned. The energy of an Auger electron is given by
where \(E_{b,a}\) is the binding energy of the shell from which the Auger electron is emitted. While Auger electrons are low-energy so their range and bremsstrahlung yield is small, fluorescence photons can travel far before depositing their energy, so the relaxation process should be modeled in detail.
Transition energies and probabilities are needed for each subshell to simulate atomic relaxation. Starting with the initial shell vacancy, the following recursive algorithm is used to fill vacancies and create fluorescence photons and Auger electrons:
- If there are no transitions for the vacancy shell, create a fluorescence photon assuming it is from a captured free electron and terminate.
- Sample a transition using the transition probabilities for the vacancy shell as the probability mass function.
- Create either a fluorescence photon or Auger electron, sampling the direction of the particle isotropically.
- If a non-radiative transition occurred, repeat from step 1 for the vacancy left by the emitted Auger electron.
- Repeat from step 1 for vacancy left by the transition electron.
Electron-Positron Annihilation¶
When a positron collides with an electron, both particles are annihilated and generally two photons with equal energy are created. If the kinetic energy of the positron is high enough, the two photons can have different energies, and the higher-energy photon is emitted preferentially in the direction of flight of the positron. It is also possible to produce a single photon if the interaction occurs with a bound electron, and in some cases three (or, rarely, even more) photons can be emitted. However, the annihilation cross section is largest for low-energy positrons, and as the positron energy decreases, the angular distribution of the emitted photons becomes isotropic.
In OpenMC, we assume the most likely case in which a low-energy positron (which has already lost most of its energy to bremsstrahlung radiation) interacts with an electron which is free and at rest. Two photons with energy equal to the electron rest mass energy \(m_e c^2 = 0.511\) MeV are emitted isotropically in opposite directions.
Bremsstrahlung¶
When a charged particle is decelerated in the field of an atom, some of its kinetic energy is converted into electromagnetic radiation known as bremsstrahlung, or ‘braking radiation’. In each event, an electron or positron with kinetic energy \(T\) generates a photon with an energy \(E\) between \(0\) and \(T\). Bremsstrahlung is described by a cross section that is differential in photon energy, in the direction of the emitted photon, and in the final direction of the charged particle. However, in Monte Carlo simulations it is typical to integrate over the angular variables to obtain a single differential cross section with respect to photon energy, which is often expressed in the form
where \(\kappa = E/T\) is the reduced photon energy and \(\chi(Z, T, \kappa)\) is the scaled bremsstrahlung cross section, which is experimentally measured.
Because electrons are attracted to atomic nuclei whereas positrons are repulsed, the cross section for positrons is smaller, though it approaches that of electrons in the high energy limit. To obtain the positron cross section, we multiply (38) by the \(\kappa\)-independent factor used in Salvat,
where
\(F_{\text{p}}(Z,T)\) is the ratio of the radiative stopping powers for positrons and electrons. Stopping power describes the average energy loss per unit path length of a charged particle as it passes through matter:
where \(n\) is the number density of the material and \(d\sigma/dE\) is the cross section differential in energy loss. The total stopping power \(S(T)\) can be separated into two components: the radiative stopping power \(S_{\text{rad}}(T)\), which refers to energy loss due to bremsstrahlung, and the collision stopping power \(S_{\text{col}}(T)\), which refers to the energy loss due to inelastic collisions with bound electrons in the material that result in ionization and excitation. The radiative stopping power for electrons is given by
To obtain the radiative stopping power for positrons, (42) is multiplied by (39).
While the models for photon interactions with matter described above can safely assume interactions occur with free atoms, sampling the target atom based on the macroscopic cross sections, molecular effects cannot necessarily be disregarded for charged particle treatment. For compounds and mixtures, the bremsstrahlung cross section is calculated using Bragg’s additivity rule as
where the sum is over the constituent elements and \(\gamma_i\) is the atomic fraction of the \(i\)-th element. Similarly, the radiative stopping power is calculated using Bragg’s additivity rule as
where \(w_i\) is the mass fraction of the \(i\)-th element and \(S_{\text{rad},i}(T)\) is found for element \(i\) using (42). The collision stopping power, however, is a function of certain quantities such as the mean excitation energy \(I\) and the density effect correction \(\delta_F\) that depend on molecular properties. These quantities cannot simply be summed over constituent elements in a compound, but should instead be calculated for the material. The Bethe formula can be used to find the collision stopping power of the material:
where \(N_A\) is Avogadro’s number, \(A_M\) is the molar mass, \(\tau = T/m_e\), and \(F(\tau)\) depends on the particle type. For electrons,
while for positrons
The density effect correction \(\delta_F\) takes into account the reduction of the collision stopping power due to the polarization of the material the charged particle is passing through by the electric field of the particle. It can be evaluated using the method described by Sternheimer, where the equation for \(\delta_F\) is
Here, \(f_i\) is the oscillator strength of the \(i\)-th transition, given by \(f_i = n_i/Z\), where \(n_i\) is the number of electrons in the \(i\)-th subshell. The frequency \(l\) is the solution of the equation
where \(\bar{v}_i\) is defined as
The plasma energy \(h\nu_p\) of the medium is given by
where \(A\) is the atomic weight and \(\rho_m\) is the density of the material. In (50), \(h\nu_i\) is the oscillator energy, and \(\rho\) is an adjustment factor introduced to give agreement between the experimental values of the oscillator energies and the mean excitation energy. The \(l_i\) in (48) are defined as
where the second case applies to conduction electrons. For a conductor, \(f_n\) is given by \(n_c/Z\), where \(n_c\) is the effective number of conduction electrons, and \(v_n = 0\). The adjustment factor \(\rho\) is determined using the equation for the mean excitation energy:
Thick-Target Bremsstrahlung Approximation¶
Since charged particles lose their energy on a much shorter distance scale than neutral particles, not much error should be introduced by neglecting to transport electrons. However, the bremsstrahlung emitted from high energy electrons and positrons can travel far from the interaction site. Thus, even without a full electron transport mode it is necessary to model bremsstrahlung. We use a thick-target bremsstrahlung (TTB) approximation based on the models in Salvat and Kaltiaisenaho for generating bremsstrahlung photons, which assumes the charged particle loses all its energy in a single homogeneous material region.
To model bremsstrahlung using the TTB approximation, we need to know the number of photons emitted by the charged particle and the energy distribution of the photons. These quantities can be calculated using the continuous slowing down approximation (CSDA). The CSDA assumes charged particles lose energy continuously along their trajectory with a rate of energy loss equal to the total stopping power, ignoring fluctuations in the energy loss. The approximation is useful for expressing average quantities that describe how charged particles slow down in matter. For example, the CSDA range approximates the average path length a charged particle travels as it slows to rest:
Actual path lengths will fluctuate around \(R(T)\). The average number of photons emitted per unit path length is given by the inverse bremsstrahlung mean free path:
The lower limit of the integral in (55) is non-zero because the bremsstrahlung differential cross section diverges for small photon energies but is finite for photon energies above some cutoff energy \(E_{\text{cut}}\). The mean free path \(\lambda_{\text{br}}^{-1}(T,E_{\text{cut}})\) is used to calculate the photon number yield, defined as the average number of photons emitted with energy greater than \(E_{\text{cut}}\) as the charged particle slows down from energy \(T\) to \(E_{\text{cut}}\). The photon number yield is given by
\(Y(T,E_{\text{cut}})\) can be used to construct the energy spectrum of bremsstrahlung photons: the number of photons created with energy between \(E_1\) and \(E_2\) by a charged particle with initial kinetic energy \(T\) as it comes to rest is given by \(Y(T,E_1) - Y(T,E_2)\).
To simulate the emission of bremsstrahlung photons, the total stopping power and bremsstrahlung differential cross section for positrons and electrons must be calculated for a given material using (43) and (44). These quantities are used to build the tabulated bremsstrahlung energy PDF and CDF for that material for each incident energy \(T_k\) on the energy grid. The following algorithm is then applied to sample the photon energies:
For an incident charged particle with energy \(T\), sample the number of emitted photons as
\[N = \lfloor Y(T,E_{\text{cut}}) + \xi_1 \rfloor.\]Rather than interpolate the PDF between indices \(k\) and \(k+1\) for which \(T_k < T < T_{k+1}\), which is computationally expensive, use the composition method and sample from the PDF at either \(k\) or \(k+1\). Using linear interpolation on a logarithmic scale, the PDF can be expressed as
\[p_{\text{br}}(T,E) = \pi_k p_{\text{br}}(T_k,E) + \pi_{k+1} p_{\text{br}}(T_{k+1},E),\]where the interpolation weights are
\[\pi_k = \frac{\ln T_{k+1} - \ln T}{\ln T_{k+1} - \ln T_k},~~~ \pi_{k+1} = \frac{\ln T - \ln T_k}{\ln T_{k+1} - \ln T_k}.\]Sample either the index \(i = k\) or \(i = k+1\) according to the point probabilities \(\pi_{k}\) and \(\pi_{k+1}\).
Determine the maximum value of the CDF \(P_{\text{br,max}}\).
Sample the photon energies using the inverse transform method with the tabulated CDF \(P_{\text{br}}(T_i, E)\) i.e.,
\[E = E_j \left[ (1 + a_j) \frac{\xi_2 P_{\text{br,max}} - P_{\text{br}}(T_i, E_j)} {E_j p_{\text{br}}(T_i, E_j)} + 1 \right]^{\frac{1}{1 + a_j}}\]where the interpolation factor \(a_j\) is given by
\[a_j = \frac{\ln p_{\text{br}}(T_i,E_{j+1}) - \ln p_{\text{br}}(T_i,E_j)} {\ln E_{j+1} - \ln E_j}\]and \(P_{\text{br}}(T_i, E_j) \le \xi_2 P_{\text{br,max}} \le P_{\text{br}}(T_i, E_{j+1})\).
We ignore the range of the electron or positron, i.e., the bremsstrahlung photons are produced in the same location that the charged particle was created. The direction of the photons is assumed to be the same as the direction of the incident charged particle, which is a reasonable approximation at higher energies when the bremsstrahlung radiation is emitted at small angles.
Photon Production¶
In coupled neutron-photon transport, a source neutron is tracked, and photons produced from neutron reactions are transported after the neutron’s history has terminated. Since these secondary photons form the photon source for the problem, it is important to correctly describe their energy and angular distributions as the accuracy of the calculation relies on the accuracy of this source. The photon production cross section for a particular reaction \(i\) and incident neutron energy \(E\) is defined as
where \(y_i(E)\) is the photon yield corresponding to an incident neutron reaction having cross section \(\sigma_i(E)\).
The yield of photons during neutron transport is determined as the sum of the photon yields from each individual reaction. In OpenMC, production of photons is treated in an average sense. That is, the total photon production cross section is used at a collision site to determine how many photons to produce rather than the photon production from the reaction that actually took place. This is partly done for convenience but also because the use of variance reduction techniques such as implicit capture make it difficult in practice to directly sample photon production from individual reactions.
In OpenMC, secondary photons are created after a nuclide has been sampled in a neutron collision. The expected number of photons produced is
where \(w\) is the weight of the neutron, \(\sigma_{\gamma}\) is the photon production cross section for the sampled nuclide, and \(\sigma_T\) is the total cross section for the nuclide. \(\lfloor n \rfloor\) photons are created with an additional photon produced with probability \(n - \lfloor n \rfloor\). Next, a reaction is sampled for each secondary photon. The probability of sampling the \(i\)-th reaction is given by \(\sigma_{\gamma, i}(E)/\sum_j\sigma_{\gamma, j}(E)\), where \(\sum_j\sigma_{\gamma, j} = \sigma_{\gamma}\) is the total photon production cross section. The secondary angle and energy distributions associated with the reaction are used to sample the angle and energy of the emitted photon.
Tallies¶
Note that the methods discussed in this section are written specifically for continuous-energy mode but equivalent apply to the multi-group mode if the particle’s energy is replaced with the particle’s group
Filters and Scores¶
The tally capability in OpenMC takes a similar philosophy as that employed in the MC21 Monte Carlo code to give maximum flexibility in specifying tallies while still maintaining scalability. Any tally in a Monte Carlo simulation can be written in the following form:
A user can specify one or more filters which identify which regions of phase space should score to a given tally (the limits of integration as shown in equation (1)) as well as the scoring function (\(f\) in equation (1)). For example, if the desired tally was the \((n,\gamma)\) reaction rate in a fuel pin, the filter would specify the cell which contains the fuel pin and the scoring function would be the radiative capture macroscopic cross section. The following quantities can be scored in OpenMC: flux, total reaction rate, scattering reaction rate, neutron production from scattering, higher scattering moments, \((n,xn)\) reaction rates, absorption reaction rate, fission reaction rate, neutron production rate from fission, and surface currents. The following variables can be used as filters: universe, material, cell, birth cell, surface, mesh, pre-collision energy, post-collision energy, polar angle, azimuthal angle, and the cosine of the change-in-angle due to a scattering event.
With filters for pre- and post-collision energy and scoring functions for scattering and fission production, it is possible to use OpenMC to generate cross sections with user-defined group structures. These multigroup cross sections can subsequently be used in deterministic solvers such as coarse mesh finite difference (CMFD) diffusion.
Using Maps for Filter-Matching¶
Some Monte Carlo codes suffer severe performance penalties when tallying a large number of quantities. Care must be taken to ensure that a tally system scales well with the total number of tally bins. In OpenMC, a mapping technique is used that allows for a fast determination of what tally/bin combinations need to be scored to a given particle’s phase space coordinates. For each discrete filter variable, a list is stored that contains the tally/bin combinations that could be scored to for each value of the filter variable. If a particle is in cell \(n\), the mapping would identify what tally/bin combinations specify cell \(n\) for the cell filter variable. In this manner, it is not necessary to check the phase space variables against each tally. Note that this technique only applies to discrete filter variables and cannot be applied to energy, angle, or change-in-angle bins. For these filters, it is necessary to perform a binary search on the specified energy grid.
Volume-Integrated Flux and Reaction Rates¶
One quantity we may wish to compute during the course of a Monte Carlo simulation is the flux or a reaction rate integrated over a finite volume. The volume may be a particular cell, a collection of cells, or the entire geometry. There are various methods by which we can estimate reaction rates
Analog Estimator¶
The analog estimator is the simplest type of estimator for reaction rates. The basic idea is that we simply count the number of actual reactions that take place and use that as our estimate for the reaction rate. This can be written mathematically as
where \(R_x\) is the reaction rate for reaction \(x\), \(i\) denotes an index for each event, \(A\) is the set of all events resulting in reaction \(x\), and \(W\) is the total starting weight of the particles, and \(w_i\) is the pre-collision weight of the particle as it enters event \(i\). One should note that equation (2) is volume-integrated so if we want a volume-averaged quantity, we need to divided by the volume of the region of integration. If survival biasing is employed, the analog estimator cannot be used for any reactions with zero neutrons in the exit channel.
Collision Estimator¶
While the analog estimator is conceptually very simple and easy to implement, it can suffer higher variance due to the fact low probability events will not occur often enough to get good statistics if they are being tallied. Thus, it is desirable to use a different estimator that allows us to score to the tally more often. One such estimator is the collision estimator. Instead of tallying a reaction only when it happens, the idea is to make a contribution to the tally at every collision.
We can start by writing a formula for the collision estimate of the flux. Since \(R = \Sigma_t \phi\) where \(R\) is the total reaction rate, \(\Sigma_t\) is the total macroscopic cross section, and \(\phi\) is the scalar flux, it stands to reason that we can estimate the flux by taking an estimate of the total reaction rate and dividing it by the total macroscopic cross section. This gives us the following formula:
where \(W\) is again the total starting weight of the particles, \(C\) is the set of all events resulting in a collision with a nucleus, and \(\Sigma_t (E)\) is the total macroscopic cross section of the target material at the incoming energy of the particle \(E_i\).
If we multiply both sides of equation (3) by the macroscopic cross section for some reaction \(x\), then we get the collision estimate for the reaction rate for that reaction:
where \(\Sigma_x (E_i)\) is the macroscopic cross section for reaction \(x\) at the incoming energy of the particle \(E_i\). In comparison to equation (2), we see that the collision estimate will result in a tally with a larger number of events that score to it with smaller contributions (since we have multiplied it by \(\Sigma_x / \Sigma_t\)).
Track-length Estimator¶
One other method we can use to increase the number of events that scores to tallies is to use an estimator the scores contributions to a tally at every track for the particle rather than every collision. This is known as a track-length estimator, sometimes also called a path-length estimator. We first start with an expression for the volume integrated flux, which can be written as
where \(V\) is the volume, \(\psi\) is the angular flux, \(\mathbf{r}\) is the position of the particle, \(\mathbf{\hat{\Omega}}\) is the direction of the particle, \(E\) is the energy of the particle, and \(t\) is the time. By noting that \(\psi(\mathbf{r}, \mathbf{\hat{\Omega}}, E, t) = v n(\mathbf{r}, \mathbf{\hat{\Omega}}, E, t)\) where \(n\) is the angular neutron density, we can rewrite equation (5) as
Using the relations \(N(\mathbf{r}, E, t) = \int d\mathbf{\Omega} n(\mathbf{r}, \mathbf{\hat{\Omega}}, E, t)\) and \(d\ell = v \, dt\) where \(d\ell\) is the differential unit of track length, we then obtain
Equation (7) indicates that we can use the length of a particle’s trajectory as an estimate for the flux, i.e. the track-length estimator of the flux would be
where \(T\) is the set of all the particle’s trajectories within the desired volume and \(\ell_i\) is the length of the \(i\)-th trajectory. In the same vein as equation (4), the track-length estimate of a reaction rate is found by multiplying equation (8) by a macroscopic reaction cross section:
One important fact to take into consideration is that the use of a track-length estimator precludes us from using any filter that requires knowledge of the particle’s state following a collision because by definition, it will not have had a collision at every event. Thus, for tallies with outgoing-energy filters (which require the post-collision energy), scattering change-in-angle filters, or for tallies of scattering moments (which require the scattering cosine of the change-in-angle), we must use an analog estimator.
Statistics¶
As was discussed briefly in Introduction, any given result from a Monte Carlo calculation, colloquially known as a “tally”, represents an estimate of the mean of some random variable of interest. This random variable typically corresponds to some physical quantity like a reaction rate, a net current across some surface, or the neutron flux in a region. Given that all tallies are produced by a stochastic process, there is an associated uncertainty with each value reported. It is important to understand how the uncertainty is calculated and what it tells us about our results. To that end, we will introduce a number of theorems and results from statistics that should shed some light on the interpretation of uncertainties.
Law of Large Numbers¶
The law of large numbers is an important statistical result that tells us that the average value of the result a large number of repeated experiments should be close to the expected value. Let \(X_1, X_2, \dots, X_n\) be an infinite sequence of independent, identically-distributed random variables with expected values \(E(X_1) = E(X_2) = \mu\). One form of the law of large numbers states that the sample mean \(\bar{X_n} = \frac{X_1 + \dots + X_n}{n}\) converges in probability to the true mean, i.e. for all \(\epsilon > 0\)
Central Limit Theorem¶
The central limit theorem (CLT) is perhaps the most well-known and ubiquitous statistical theorem that has far-reaching implications across many disciplines. The CLT is similar to the law of large numbers in that it tells us the limiting behavior of the sample mean. Whereas the law of large numbers tells us only that the value of the sample mean will converge to the expected value of the distribution, the CLT says that the distribution of the sample mean will converge to a normal distribution. As we defined before, let \(X_1, X_2, \dots, X_n\) be an infinite sequence of independent, identically-distributed random variables with expected values \(E(X_i) = \mu\) and variances \(\text{Var} (X_i) = \sigma^2 < \infty\). Note that we don’t require that these random variables take on any particular distribution – they can be normal, log-normal, Weibull, etc. The central limit theorem states that as \(n \rightarrow \infty\), the random variable \(\sqrt{n} (\bar{X}_n - \mu)\) converges in distribution to the standard normal distribution:
Estimating Statistics of a Random Variable¶
Mean¶
Given independent samples drawn from a random variable, the sample mean is simply an estimate of the average value of the random variable. In a Monte Carlo simulation, the random variable represents physical quantities that we want tallied. If \(X\) is the random variable with \(N\) observations \(x_1, x_2, \dots, x_N\), then an unbiased estimator for the population mean is the sample mean, defined as
Variance¶
The variance of a population indicates how spread out different members of the population are. For a Monte Carlo simulation, the variance of a tally is a measure of how precisely we know the tally value, with a lower variance indicating a higher precision. There are a few different estimators for the population variance. One of these is the second central moment of the distribution also known as the biased sample variance:
This estimator is biased because its expected value is actually not equal to the population variance:
where \(\sigma^2\) is the actual population variance. As a result, this estimator should not be used in practice. Instead, one can use Bessel’s correction to come up with an unbiased sample variance estimator:
This is the estimator normally used to calculate sample variance. The final form in equation (14) is especially suitable for computation since we do not need to store the values at every realization of the random variable as the simulation proceeds. Instead, we can simply keep a running sum and sum of squares of the values at each realization of the random variable and use that to calculate the variance.
Variance of the Mean¶
The previous sections discussed how to estimate the mean and variance of a random variable using statistics on a finite sample. However, we are generally not interested in the variance of the random variable itself; we are more interested in the variance of the estimated mean. The sample mean is the result of our simulation, and the variance of the sample mean will tell us how confident we should be in our answers.
Fortunately, it is quite easy to estimate the variance of the mean if we are able to estimate the variance of the random variable. We start with the observation that if we have a series of uncorrelated random variables, we can write the variance of their sum as the sum of their variances:
This result is known as the Bienaymé formula. We can use this result to determine a formula for the variance of the sample mean. Assuming that the realizations of our random variable are again identical, independently-distributed samples, then we have that
We can combine this result with equation (14) to come up with an unbiased estimator for the variance of the sample mean:
At this point, an important distinction should be made between the estimator for the variance of the population and the estimator for the variance of the mean. As the number of realizations increases, the estimated variance of the population based on equation (14) will tend to the true population variance. On the other hand, the estimated variance of the mean will tend to zero as the number of realizations increases. A practical interpretation of this is that the longer you run a simulation, the better you know your results. Therefore, by running a simulation long enough, it is possible to reduce the stochastic uncertainty to arbitrarily low levels.
Confidence Intervals¶
While the sample variance and standard deviation gives us some idea about the variability of the estimate of the mean of whatever quantities we’ve tallied, it does not help us interpret how confidence we should be in the results. To quantify the reliability of our estimates, we can use confidence intervals based on the calculated sample variance.
A \(1-\alpha\) confidence interval for a population parameter is defined as such: if we repeat the same experiment many times and calculate the confidence interval for each experiment, then \(1 - \alpha\) percent of the calculated intervals would encompass the true population parameter. Let \(x_1, x_2, \dots, x_N\) be samples from a set of independent, identically-distributed random variables each with population mean \(\mu\) and variance \(\sigma^2\). The t-statistic is defined as
where \(\bar{x}\) is the sample mean from equation (11) and \(s\) is the standard deviation based on equation (14). If the random variables \(X_i\) are normally-distributed, then the t-statistic has a Student’s t-distribution with \(N-1\) degrees of freedom. This implies that
where \(t_{1-\alpha/2, N-1}\) is the \(1 - \alpha/2\) percentile of a t-distribution with \(N-1\) degrees of freedom. Thus, the \(1 - \alpha\) two sided confidence interval for the sample mean is
One should be cautioned that equation (20) only applies if the underlying random variables are normally-distributed. In general, this may not be true for a tally random variable — the central limit theorem guarantees only that the sample mean is normally distributed, not the underlying random variable. If batching is used, then the underlying random variable, which would then be the averages from each batch, will be normally distributed as long as the conditions of the central limit theorem are met.
Let us now outline the method used to calculate the percentile of the Student’s t-distribution. For one or two degrees of freedom, the percentile can be written analytically. For one degree of freedom, the t-distribution becomes a standard Cauchy distribution whose cumulative distribution function is
Thus, inverting the cumulative distribution function, we find the \(x\) percentile of the standard Cauchy distribution to be
For two degrees of freedom, the cumulative distribution function is the second-degree polynomial
Solving for \(x\), we find the \(x\) percentile to be
For degrees of freedom greater than two, it is not possible to obtain an analytical formula for the inverse of the cumulative distribution function. We must resort to either numerically solving for the inverse or to an approximation. Approximations for percentiles of the t-distribution have been found with high levels of accuracy. OpenMC uses the following approximation:
where \(z_x\) is the \(x\) percentile of the standard normal distribution. In order to determine an arbitrary percentile of the standard normal distribution, we use an unpublished rational approximation. After using the rational approximation, one iteration of Newton’s method is applied to improve the estimate of the percentile.
References
Eigenvalue Calculations¶
An eigenvalue calculation, also referred to as a criticality calculation, is a transport simulation wherein the source of neutrons includes a fissionable material. Some common eigenvalue calculations include the simulation of nuclear reactors, spent fuel pools, nuclear weapons, and other fissile systems. The reason they are called eigenvalue calculations is that the transport equation becomes an eigenvalue equation if a fissionable source is present since then the source of neutrons will depend on the flux of neutrons itself. Eigenvalue simulations using Monte Carlo methods are becoming increasingly common with the advent of high-performance computing.
This section will explore the theory behind and implementation of eigenvalue calculations in a Monte Carlo code.
Method of Successive Generations¶
The method used to converge on the fission source distribution in an eigenvalue calculation, known as the method of successive generations, was first introduced by [Lieberoth]. In this method, a finite number of neutron histories, \(N\), are tracked through their lifetime iteratively. If fission occurs, rather than tracking the resulting fission neutrons, the spatial coordinates of the fission site, the sampled outgoing energy and direction of the fission neutron, and the weight of the neutron are stored for use in the subsequent generation. In OpenMC, the array used for storing the fission site information is called the fission bank. At the end of each fission generation, \(N\) source sites for the next generation must be randomly sampled from the \(M\) fission sites that were stored to ensure that the neutron population does not grow exponentially. The sampled source sites are stored in an array called the source bank and can be retrieved during the subsequent generation.
It’s important to recognize that in the method of successive generations, we must start with some assumption on how the fission source sites are distributed since the distribution is not known a priori. Typically, a user will make a guess as to what the distribution is – this guess could be a uniform distribution over some region of the geometry or simply a point source. Fortunately, regardless of the choice of initial source distribution, the method is guaranteed to converge to the true source distribution. Until the source distribution converges, tallies should not be scored to since they will otherwise include contributions from an unconverged source distribution.
The method by which the fission source iterations are parallelized can have a large impact on the achievable parallel scaling. This topic is discussed at length in Fission Bank Algorithms.
Source Convergence Issues¶
Diagnosing Convergence with Shannon Entropy¶
As discussed earlier, it is necessary to converge both \(k_{eff}\) and the source distribution before any tallies can begin. Moreover, the convergence rate of the source distribution is in general slower than that of \(k_{eff}\). One should thus examine not only the convergence of \(k_{eff}\) but also the convergence of the source distribution in order to make decisions on when to start active batches.
However, the representation of the source distribution makes it a bit more difficult to analyze its convergence. Since \(k_{eff}\) is a scalar quantity, it is easy to simply look at a line plot of \(k_{eff}\) versus the number of batches and this should give the user some idea about whether it has converged. On the other hand, the source distribution at any given batch is a finite set of coordinates in Euclidean space. In order to analyze the convergence, we would either need to use a method for assessing convergence of an N-dimensional quantity or transform our set of coordinates into a scalar metric. The latter approach has been developed considerably over the last decade and a method now commonly used in Monte Carlo eigenvalue calculations is to use a metric called the Shannon entropy, a concept borrowed from information theory.
To compute the Shannon entropy of the source distribution, we first need to discretize the source distribution rather than having a set of coordinates in Euclidean space. This can be done by superimposing a structured mesh over the geometry (containing at least all fissionable materials). Then, the fraction of source sites that are present in each mesh element is counted:
The Shannon entropy is then computed as
where \(N\) is the number of mesh elements. With equation (2), we now have a scalar metric that we can use to assess the convergence of the source distribution by observing line plots of the Shannon entropy versus the number of batches.
In recent years, researchers have started looking at ways of automatically assessing source convergence to relieve the burden on the user of having to look at plots of \(k_{eff}\) and the Shannon entropy. A number of methods have been proposed (see e.g. [Romano], [Ueki]), but each of these is not without problems.
Uniform Fission Site Method¶
Generally speaking, the variance of a Monte Carlo tally will be inversely proportional to the number of events that score to the tally. In a reactor problem, this implies that regions with low relative power density will have higher variance that regions with high relative power density. One method to circumvent the uneven distribution of relative errors is the uniform fission site (UFS) method introduced by [Sutton]. In this method, the portion of the problem containing fissionable material is subdivided into a number of cells (typically using a structured mesh). Rather than producing
fission sites at each collision where \(w\) is the weight of the neutron, \(k\) is the previous-generation estimate of the neutron multiplication factor, \(\nu\Sigma_f\) is the neutron production cross section, and \(\Sigma_t\) is the total cross section, in the UFS method we produce
fission sites at each collision where \(v_i\) is the fraction of the total volume occupied by cell \(i\) and \(s_i\) is the fraction of the fission source contained in cell \(i\). To ensure that no bias is introduced, the weight of each fission site stored in the fission bank is \(s_i/v_i\) rather than unity. By ensuring that the expected number of fission sites in each mesh cell is constant, the collision density across all cells, and hence the variance of tallies, is more uniform than it would be otherwise.
[Lieberoth] | J. Lieberoth, “A Monte Carlo Technique to Solve the Static Eigenvalue Problem of the Boltzmann Transport Equation,” Nukleonik, 11, 213-219 (1968). |
[Romano] | Paul K. Romano, “Application of the Stochastic Oscillator to Assess Source Convergence in Monte Carlo Criticality Calculations,” Proc. International Conference on Mathematics, Computational Methods, and Reactor Physics, Saratoga Springs, New York (2009). |
[Sutton] | Daniel J. Kelly, Thomas M. Sutton, and Stephen C. Wilson, “MC21 Analysis of the Nuclear Energy Agency Monte Carlo Performance Benchmark Problem,” Proc. PHYSOR 2012, Knoxville, Tennessee, Apr. 15–20 (2012). |
[Ueki] | Taro Ueki, “On-the-Fly Judgments of Monte Carlo Fission Source Convergence,” Trans. Am. Nucl. Soc., 98, 512 (2008). |
Depletion¶
When materials in a system are subject to irradiation over a long period of time, nuclides within the material will transmute due to nuclear reactions as well as spontaneous radioactive decay. The time-dependent process by which nuclides transmute under irradiation is known as depletion or burnup. To accurately analyze nuclear systems, it is often necessary to predict how the composition of materials will change since this change results in a corresponding change in the solution of the transport equation. The equation that governs the transmutation and decay of nuclides inside of an irradiated environment can be written as
where \(N_i\) is the density of nuclide \(i\) at time \(t\), \(\sigma_i\) is the transmutation cross section for nuclide \(i\) at energy \(E\), \(f_{j \rightarrow i}\) is the fraction of transmutation reactions in nuclide \(j\) that produce nuclide \(i\), and \(\lambda_{j \rightarrow i}\) is the decay constant for decay modes in nuclide \(j\) that produce nuclide \(i\). Note that we have not included the spatial dependence of the flux or cross sections. As one can see, the equation simply states that the rate of change of \(N_i\) is equal to the production rate minus the loss rate. Because the equation for nuclide \(i\) depends on the nuclide density for possibly many other nuclides, we have a system of first-order differential equations. To form a proper initial value problem, we also need the nuclide densities at time 0:
These equations can be written more compactly in matrix notation as
where \(\mathbf{n} \in \mathbb{R}^n\) is the nuclide density vector, \(\mathbf{A}(\mathbf{n},t) \in \mathbb{R}^{n\times n}\) is the burnup matrix containing the decay and transmutation coefficients, and \(\mathbf{n}_0\) is the initial density vector. Note that the burnup matrix depends on \(\mathbf{n}\) because the solution to the transport equation depends on the nuclide densities.
Numerical Integration¶
A variety of numerical methods exist for solving Eq. (1). The simplest such method, known as the “predictor” method, is to divide the overall time interval of interest \([0,t]\) into smaller timesteps over which it is assumed that the burnup matrix is constant. Let \(t \in [t_i, t_i + h]\) be one such timestep. Over the timestep, the solution to Eq. (1) can be written analytically using the matrix exponential
where \(\mathbf{n}_i \equiv \mathbf{n}(t_i)\). The exponential of a matrix \(\mathbf{X}\) is defined by the power series expansion
where \(\mathbf{X}^0 = \mathbf{I}\). A series of so-called predictor-corrector methods that use multiple stages offer improved accuracy over the predictor method. The simplest of these methods, the CE/CM algorithm, is defined as
Here, the value of \(\mathbf{n}\) at the midpoint is estimated using \(\mathbf{A}\) evaluated at the beginning of the timestep. Then, \(\mathbf{A}\) is evaluated using the densities at the midpoint and used to 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 \(\mathbf{A}\) for a given \(\mathbf{n}\). Thus, the cost of a method scales with the number of \(\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 \(\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 \(\mathcal{O}(h^2)\). An exhaustive description of time integration methods and their merits can be found in the thesis of Colin Josey.
OpenMC does not rely on a single time integration method but rather has several
classes that implement different algorithms. For example, the
openmc.deplete.PredictorIntegrator
class implements the predictor
method, and the openmc.deplete.CECMIntegrator
class implements the
CE/CM method. A full list of the integrator classes available can be found in
the documentation for the openmc.deplete
module.
Matrix Exponential¶
As we saw in the previous section, numerically integrating Eq. (1) requires evaluating one or more matrix exponentials. OpenMC uses the Chebyshev rational approximation method (CRAM), which was introduced in a series of papers by Pusa (1, 2), to evaluate matrix exponentials. In particular, OpenMC utilizes an incomplete partial fraction (IPF) form of CRAM that provides a good balance of numerical stability and efficiency. In this representation the matrix exponential is approximated as
where \(k\) is the order of the approximation and \(\alpha_0\), \(\widetilde{\alpha}_\ell\), and \(\theta_\ell\) are coefficients that have been tabulated for orders up to \(k=48\). Rather than computing the full approximation and then multiplying it by a vector, the following algorithm is used to incrementally apply the terms within the product (note that the original description of the algorithm presented by Pusa contains a typo):
- \(\mathbf{n} \gets \mathbf{n_0}\)
- For \(\ell = 1, 2, \dots, k/2\)
- \(\mathbf{n} \gets \mathbf{n} + 2\text{Re}(\widetilde{\alpha}_\ell (\mathbf{A}t - \theta_\ell)^{-1})\mathbf{n}\)
- \(\mathbf{n} \gets \alpha_0 \mathbf{n}\)
The \(k\)th order approximation for CRAM requires solving \(k/2\)
sparse linear systems. OpenMC relies on functionality from
scipy.sparse.linalg
for solving the linear systems.
Data Considerations¶
In principle, solving Eq. (1) 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:
- What transmutation reactions are possible, their Q values, and their products;
- If a nuclide is not stable, what decay modes are possible, their branching ratios, and their products; and
- If a nuclide is fissionable, the fission products yields at any number of incident neutron energies.
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 https://openmc.org include the following transmutation reactions: fission, (n,\(\gamma\)), (n,2n), (n,3n), (n,4n), (n,p), and (n,\(\alpha\)).
Capture Branching Ratios¶
Some (n,\(\gamma\)) reactions may result in a product being in either the ground or a metastable state. The most well-known example is capture in Am241, which can produce either Am242 or Am242m. Because the metastable state of Am242m has a significantly longer half-life than the ground state, it is important to 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.
Fission Product Yields¶
Fission product yields (FPY) are also energy-dependent in general. ENDF fission product yield sublibraries typically include yields tabulated at 2 or 3 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:
- Use FPY data corresponding to a specified energy.
- 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.
- Compute the average energy at which fission events occur and use an effective FPY by linearly interpolating between FPY provided at neighboring energies.
The method can be selected through the fission_yield_mode
argument to the
openmc.deplete.Operator
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 Normalization of Tally Results. Here, we simply
note that the main depletion class, openmc.deplete.Operator
, allows the
user to choose one of two methods for estimating the heating rate, including:
- Using fixed Q values from a depletion chain file (useful for comparisons to other codes that use fixed Q values), or
- Using the
heating
orheating-local
scores to obtain an nuclide- and energy-dependent estimate of the true heating rate.
The method for normalization can be chosen through the normalization_mode
argument to the openmc.deplete.Operator
class.
Heating and Energy Deposition¶
As particles traverse a problem, some portion of their energy is deposited at
collision sites. This energy is deposited when charged particles, including
electrons and recoil nuclei, undergo electromagnetic interactions with
surrounding electons and ions. The information describing how much energy
is deposited for a specific reaction is referred to as
“heating numbers” and can be computed using a program like NJOY with the
heatr
module.
The heating rate is the product of reaction-specific coefficients and a reaction cross section
and has units energy per time, typically eV/s. Here, \(k_{i, r}\) are the KERMA (Kinetic Energy Release in Materials) [Mack97] coefficients for reaction \(r\) of isotope \(i\). The KERMA coefficients have units of energy \(\times\) cross-section (e.g., eV-barn) and can be used much like a reaction cross section for the purpose of tallying energy deposition.
KERMA coefficients can be computed using the energy-balance method with a nuclear data processing code like NJOY, which performs the following iteration over all reactions \(r\) for all isotopes \(i\) requested
removing the energy of neutral particles (neutrons and photons) that are transported away from the reaction site \(\bar{E}\), and the reaction \(Q\) value.
Fission¶
During a fission event, there are potentially many secondary particles, and all must be considered. The total energy released in a fission event is typically broken up into the following categories:
- \(E_{fr}\) - kinetic energy of fission fragments
- \(E_{n,p}\) - energy of prompt fission neutrons
- \(E_{n,d}\) - energy of delayed fission neutrons
- \(E_{\gamma,p}\) - energy of prompt fission photons
- \(E_{\gamma,d}\) - energy of delayed fission photons
- \(E_{\beta}\) - energy of released \(\beta\) particles
- \(E_{\nu}\) - energy of neutrinos
These components are defined in MF=1, MT=458 data in a standard ENDF-6 formatted file. All these quantities may depend upon incident neutron energy, but this dependence is not shown to make the following demonstrations cleaner. As neutrinos scarcely interact with matter, the recoverable energy from fission is defined as
Furthermore, the energy of the secondary neutrons and photons is given as \(E_{n, p}\) and \(E_{\gamma, p}\), respectively.
NJOY computes the fission KERMA coefficient using this energy-balance method to be
Note
The energy from delayed neutrons and photons and beta particles is intentionally left out from the NJOY calculations.
OpenMC Implementation¶
For fissile isotopes, OpenMC makes modifications to the heating reaction to include all relevant components of fission energy release. These modifications are made to the total heating reaction, MT=301. Breaking the total heating KERMA into a fission and non-fission section, one can write
OpenMC seeks to modify the total heating data to include energy from \(\beta\) particles and, conditionally, delayed photons. This conditional inclusion depends on the simulation mode: neutron transport, or coupled neutron-photon transport. The heating due to fission is removed using MT=318 data, and then re-built using the desired components of fission energy release from MF=1,MT=458 data.
Neutron Transport¶
For this case, OpenMC instructs heatr
to produce heating coefficients
assuming that energy from photons, \(E_{\gamma, p}\) and
\(E_{\gamma, d}\), is deposited at the fission site.
Let \(N901\) represent the total heating number returned from this heatr
run with \(N918\) reflecting fission heating computed from NJOY.
\(M901\) represent the following modification
This modified heating data is stored as the MT=901 reaction and will be scored
if heating-local
is included in openmc.Tally.scores
.
Coupled neutron-photon transport¶
Here, OpenMC instructs heatr
to assume that energy from photons is not
deposited locally. However, the definitions provided in the NJOY manual
indicate that, regardless of this mode, the prompt photon energy is still
included in \(k_{i, f}\), and therefore must be manually removed.
Let \(N301\) represent the total heating number returned from this
heatr
run and \(M301\) be
This modified heating data is stored as the MT=301 reaction and will be scored
if heating
is included in openmc.Tally.scores
.
Parallelization¶
Due to the computationally-intensive nature of Monte Carlo methods, there has been an ever-present interest in parallelizing such simulations. Even in the first paper on the Monte Carlo method, John Metropolis and Stanislaw Ulam recognized that solving the Boltzmann equation with the Monte Carlo method could be done in parallel very easily whereas the deterministic counterparts for solving the Boltzmann equation did not offer such a natural means of parallelism. With the introduction of vector computers in the early 1970s, general-purpose parallel computing became a reality. In 1972, Troubetzkoy et al. designed a Monte Carlo code to be run on the first vector computer, the ILLIAC-IV [Troubetzkoy]. The general principles from that work were later refined and extended greatly through the work of Forrest Brown in the 1980s. However, as Brown’s work shows, the single-instruction multiple-data (SIMD) parallel model inherent to vector processing does not lend itself to the parallelism on particles in Monte Carlo simulations. Troubetzkoy et al. recognized this, remarking that “the order and the nature of these physical events have little, if any, correlation from history to history,” and thus following independent particle histories simultaneously using a SIMD model is difficult.
The difficulties with vector processing of Monte Carlo codes led to the adoption of the single program multiple data (SPMD) technique for parallelization. In this model, each different process tracks a particle independently of other processes, and between fission source generations the processes communicate data through a message-passing interface. This means of parallelism was enabled by the introduction of message-passing standards in the late 1980s and early 1990s such as PVM and MPI. The SPMD model proved much easier to use in practice and took advantage of the inherent parallelism on particles rather than instruction-level parallelism. As a result, it has since become ubiquitous for Monte Carlo simulations of transport phenomena.
Thanks to the particle-level parallelism using SPMD techniques, extremely high parallel efficiencies could be achieved in Monte Carlo codes. Until the last decade, even the most demanding problems did not require transmitting large amounts of data between processors, and thus the total amount of time spent on communication was not significant compared to the amount of time spent on computation. However, today’s computing power has created a demand for increasingly large and complex problems, requiring a greater number of particles to obtain decent statistics (and convergence in the case of criticality calculations). This results in a correspondingly higher amount of communication, potentially degrading the parallel efficiency. Thus, while Monte Carlo simulations may seem embarrassingly parallel, obtaining good parallel scaling with large numbers of processors can be quite difficult to achieve in practice.
Fission Bank Algorithms¶
Master-Slave Algorithm¶
Monte Carlo particle transport codes commonly implement a SPMD model by having one master process that controls the scheduling of work and the remaining processes wait to receive work from the master, process the work, and then send their results to the master at the end of the simulation (or a source iteration in the case of an eigenvalue calculation). This idea is illustrated in Communication pattern in master-slave algorithm..

Figure 10: Communication pattern in master-slave algorithm.
Eigenvalue calculations are slightly more difficult to parallelize than fixed source calculations since it is necessary to converge on the fission source distribution and eigenvalue before tallying. In the Method of Successive Generations, to ensure that the results are reproducible, one must guarantee that the process by which fission sites are randomly sampled does not depend on the number of processors. What is typically done is the following:
- Each compute node sends its fission bank sites to a master process;
2. The master process sorts or orders the fission sites based on a unique identifier;
3. The master process samples \(N\) fission sites from the ordered array of \(M\) sites; and
4. The master process broadcasts all the fission sites to the compute nodes.
The first and last steps of this process are the major sources of communication overhead between cycles. Since the master process must receive \(M\) fission sites from the compute nodes, the first step is necessarily serial. This step can be completed in \(O(M)\) time. The broadcast step can benefit from parallelization through a tree-based algorithm. Despite this, the communication overhead is still considerable.
To see why this is the case, it is instructive to look at a hypothetical example. Suppose that a calculation is run with \(N = 10,000,000\) neutrons across 64 compute nodes. On average, \(M = 10,000,000\) fission sites will be produced. If the data for each fission site consists of a spatial location (three 8 byte real numbers) and a unique identifier (one 4 byte integer), the memory required per site is 28 bytes. To broadcast 10,000,000 source sites to 64 nodes will thus require transferring 17.92 GB of data. Since each compute node does not need to keep every source site in memory, one could modify the algorithm from a broadcast to a scatter. However, for practical reasons (e.g. work self-scheduling), this is normally not done in production Monte Carlo codes.
Nearest Neighbors Algorithm¶
To reduce the amount of communication required in a fission bank synchronization algorithm, it is desirable to move away from the typical master-slave algorithm to an algorithm whereby the compute nodes communicate with one another only as needed. This concept is illustrated in Communication pattern in nearest neighbor algorithm..

Figure 11: Communication pattern in nearest neighbor algorithm.
Since the source sites for each cycle are sampled from the fission sites banked from the previous cycle, it is a common occurrence for a fission site to be banked on one compute node and sent back to the master only to get sent back to the same compute node as a source site. As a result, much of the communication inherent in the algorithm described previously is entirely unnecessary. By keeping the fission sites local, having each compute node sample fission sites, and sending sites between nodes only as needed, one can cut down on most of the communication. One algorithm to achieve this is as follows:
1. An exclusive scan is performed on the number of sites banked, and the total number of fission bank sites is broadcasted to all compute nodes. By picturing the fission bank as one large array distributed across multiple nodes, one can see that this step enables each compute node to determine the starting index of fission bank sites in this array. Let us call the starting and ending indices on the \(i\)-th node \(a_i\) and \(b_i\), respectively;
2. Each compute node samples sites at random from the fission bank using the same starting seed. A separate array on each compute node is created that consists of sites that were sampled local to that node, i.e. if the index of the sampled site is between \(a_i\) and \(b_i\), it is set aside;
3. If any node sampled more than \(N/p\) fission sites where \(p\) is the number of compute nodes, the extra sites are put in a separate array and sent to all other compute nodes. This can be done efficiently using the allgather collective operation;
4. The extra sites are divided among those compute nodes that sampled fewer than \(N/p\) fission sites.
However, even this algorithm exhibits more communication than necessary since the allgather will send fission bank sites to nodes that don’t necessarily need any extra sites.
One alternative is to replace the allgather with a series of sends. If \(a_i\) is less than \(iN/p\), then send \(iN/p - a_i\) sites to the left adjacent node. Similarly, if \(a_i\) is greater than \(iN/p\), then receive \(a_i - iN/p\) from the left adjacent node. This idea is applied to the fission bank sites at the end of each node’s array as well. If \(b_i\) is less than \((i+1)N/p\), then receive \((i+1)N/p - b_i\) sites from the right adjacent node. If \(b_i\) is greater than \((i+1)N/p\), then send \(b_i - (i+1)N/p\) sites to the right adjacent node. Thus, each compute node sends/receives only two messages under normal circumstances.
The following example illustrates how this algorithm works. Let us suppose we are simulating \(N = 1000\) neutrons across four compute nodes. For this example, it is instructive to look at the state of the fission bank and source bank at several points in the algorithm:
- The beginning of a cycle where each node has \(N/p\) source sites;
- The end of a cycle where each node has accumulated fission sites;
3. After sampling, where each node has some amount of source sites usually not equal to \(N/p\);
4. After redistribution, each node again has \(N/p\) source sites for the next cycle;
At the end of each cycle, each compute node needs 250 fission bank sites to continue on the next cycle. Let us suppose that \(p_0\) produces 270 fission banks sites, \(p_1\) produces 230, \(p_2\) produces 290, and \(p_3\) produces 250. After each node samples from its fission bank sites, let’s assume that \(p_0\) has 260 source sites, \(p_1\) has 215, \(p_2\) has 280, and \(p_3\) has 245. Note that the total number of sampled sites is 1000 as needed. For each node to have the same number of source sites, \(p_0\) needs to send its right-most 10 sites to \(p_1\), and \(p_2\) needs to send its left-most 25 sites to \(p_1\) and its right-most 5 sites to \(p_3\). A schematic of this example is shown in Example of nearest neighbor algorithm.. The data local to each node is given a different hatching, and the cross-hatched regions represent source sites that are communicated between adjacent nodes.

Figure 12: Example of nearest neighbor algorithm.
Cost of Master-Slave Algorithm¶
While the prior considerations may make it readily apparent that the novel algorithm should outperform the traditional algorithm, it is instructive to look at the total communication cost of the novel algorithm relative to the traditional algorithm. This is especially so because the novel algorithm does not have a constant communication cost due to stochastic fluctuations. Let us begin by looking at the cost of communication in the traditional algorithm
As discussed earlier, the traditional algorithm is composed of a series of sends and typically a broadcast. To estimate the communication cost of the algorithm, we can apply a simple model that captures the essential features. In this model, we assume that the time that it takes to send a message between two nodes is given by \(\alpha + (sN)\beta\), where \(\alpha\) is the time it takes to initiate the communication (commonly called the latency), \(\beta\) is the transfer time per unit of data (commonly called the bandwidth), \(N\) is the number of fission sites, and \(s\) is the size in bytes of each fission site.
The first step of the traditional algorithm is to send \(p\) messages to the master node, each of size \(sN/p\). Thus, the total time to send these messages is
Generally, the best parallel performance is achieved in a weak scaling scheme where the total number of histories is proportional to the number of processors. However, we see that when \(N\) is proportional to \(p\), the time to send these messages increases proportionally with \(p\).
Estimating the time of the broadcast is complicated by the fact that different MPI implementations may use different algorithms to perform collective communications. Worse yet, a single implementation may use a different algorithm depending on how many nodes are communicating and the size of the message. Using multiple algorithms allows one to minimize latency for small messages and minimize bandwidth for long messages.
We will focus here on the implementation of broadcast in the MPICH implementation. For short messages, MPICH uses a binomial tree algorithm. In this algorithm, the root process sends the data to one node in the first step, and then in the subsequent, both the root and the other node can send the data to other nodes. Thus, it takes a total of \(\lceil \log_2 p \rceil\) steps to complete the communication. The time to complete the communication is
This algorithm works well for short messages since the latency term scales logarithmically with the number of nodes. However, for long messages, an algorithm that has lower bandwidth has been proposed by Barnett and implemented in MPICH. Rather than using a binomial tree, the broadcast is divided into a scatter and an allgather. The time to complete the scatter is :math:` log_2 p : alpha + frac{p-1}{p} Nbeta` using a binomial tree algorithm. The allgather is performed using a ring algorithm that completes in \(p-1) \alpha + \frac{p-1}{p} N\beta\). Thus, together the time to complete the broadcast is
The fission bank data will generally exceed the threshold for switching from short to long messages (typically 8 kilobytes), and thus we will use the equation for long messages. Adding equations (1) and (3), the total cost of the series of sends and the broadcast is
Cost of Nearest Neighbor Algorithm¶
With the communication cost of the traditional fission bank algorithm quantified, we now proceed to discuss the communicatin cost of the proposed algorithm. Comparing the cost of communication of this algorithm with the traditional algorithm is not trivial due to fact that the cost will be a function of how many fission sites are sampled on each node. If each node samples exactly \(N/p\) sites, there will not be communication between nodes at all. However, if any one node samples more or less than \(N/p\) sites, the deviation will result in communication between logically adjacent nodes. To determine the expected deviation, one can analyze the process based on the fundamentals of the Monte Carlo process.
The steady-state neutron transport equation for a multiplying medium can be written in the form of an eigenvalue problem,
where \(\mathbf{r}\) is the spatial coordinates of the neutron, \(S(\mathbf{r})\) is the source distribution defined as the expected number of neutrons born from fission per unit phase-space volume at \(\mathbf{r}\), \(F( \mathbf{r}' \rightarrow \mathbf{r})\) is the expected number of neutrons born from fission per unit phase space volume at \(\mathbf{r}\) caused by a neutron at \(\mathbf{r}\), and \(k\) is the eigenvalue. The fundamental eigenvalue of equation (5) is known as \(k_{eff}\), but for simplicity we will simply refer to it as \(k\).
In a Monte Carlo criticality simulation, the power iteration method is applied iteratively to obtain stochastic realizations of the source distribution and estimates of the \(k\)-eigenvalue. Let us define \(\hat{S}^{(m)}\) to be the realization of the source distribution at cycle \(m\) and \(\hat{\epsilon}^{(m)}\) be the noise arising from the stochastic nature of the tracking process. We can write the stochastic realization in terms of the fundamental source distribution and the noise component as (see Brissenden and Garlick):
where \(N\) is the number of particle histories per cycle. Without loss of generality, we shall drop the superscript notation indicating the cycle as it is understood that the stochastic realization is at a particular cycle. The expected value of the stochastic source distribution is simply
since \(E \left[ \hat{\epsilon}(\mathbf{r})\right] = 0\). The noise in the source distribution is due only to \(\hat{\epsilon}(\mathbf{r})\) and thus the variance of the source distribution will be
Lastly, the stochastic and true eigenvalues can be written as integrals over all phase space of the stochastic and true source distributions, respectively, as
noting that \(S(\mathbf{r})\) is \(O(1)\). One should note that the expected value \(k\) calculated by Monte Carlo power iteration (i.e. the method of successive generations) will be biased from the true fundamental eigenvalue of equation (5) by \(O(1/N)\) (see Brissenden and Garlick), but we will assume henceforth that the number of particle histories per cycle is sufficiently large to neglect this bias.
With this formalism, we now have a framework within which we can determine the properties of the distribution of expected number of fission sites. The explicit form of the source distribution can be written as
where \(\mathbf{r}_i\) is the spatial location of the \(i\)-th fission site, \(w_i\) is the statistical weight of the fission site at \(\mathbf{r}_i\), and \(M\) is the total number of fission sites. It is clear that the total weight of the fission sites is simply the integral of the source distribution. Integrating equation (6) over all space, we obtain
Substituting the expressions for the stochastic and true eigenvalues from equation (9), we can relate the stochastic eigenvalue to the integral of the noise component of the source distribution as
Since the expected value of \(\hat{\epsilon}\) is zero, the expected value of its integral will also be zero. We thus see that the variance of the integral of the source distribution, i.e. the variance of the total weight of fission sites produced, is directly proportional to the variance of the integral of the noise component. Let us call this term \(\sigma^2\) for simplicity:
The actual value of \(\sigma^2\) will depend on the physical nature of the problem, whether variance reduction techniques are employed, etc. For instance, one could surmise that for a highly scattering problem, \(\sigma^2\) would be smaller than for a highly absorbing problem since more collisions will lead to a more precise estimate of the source distribution. Similarly, using implicit capture should in theory reduce the value of \(\sigma^2\).
Let us now consider the case where the \(N\) total histories are divided up evenly across \(p\) compute nodes. Since each node simulates \(N/p\) histories, we can write the source distribution as
Integrating over all space and simplifying, we can obtain an expression for the eigenvalue on the \(i\)-th node:
It is easy to show from this expression that the stochastic realization of the global eigenvalue is merely the average of these local eigenvalues:
As was mentioned earlier, at the end of each cycle one must sample \(N\) sites from the \(M\) sites that were created. Thus, the source for the next cycle can be seen as the fission source from the current cycle divided by the stochastic realization of the eigenvalue since it is clear from equation (9) that \(\hat{k} = M/N\). Similarly, the number of sites sampled on each compute node that will be used for the next cycle is
While we know conceptually that each compute node will under normal circumstances send two messages, many of these messages will overlap. Rather than trying to determine the actual communication cost, we will instead attempt to determine the maximum amount of data being communicated from one node to another. At any given cycle, the number of fission sites that the \(j\)-th compute node will send or receive (\(\Lambda_j\)) is
Noting that \(jN/p\) is the expected value of the summation, we can write the expected value of \(\Lambda_j\) as the mean absolute deviation of the summation:
where \(\text{MD}\) indicates the mean absolute deviation of a random variable. The mean absolute deviation is an alternative measure of variability.
In order to ascertain any information about the mean deviation of \(M_i\), we need to know the nature of its distribution. Thus far, we have said nothing of the distributions of the random variables in question. The total number of fission sites resulting from the tracking of \(N\) neutrons can be shown to be normally distributed via the Central Limit Theorem (provided that \(N\) is sufficiently large) since the fission sites resulting from each neutron are “sampled” from independent, identically-distributed random variables. Thus, \(\hat{k}\) and \(\int \hat{S} (\mathbf{r}) \: d\mathbf{r}\) will be normally distributed as will the individual estimates of these on each compute node.
Next, we need to know what the distribution of \(M_i\) in equation (17) is or, equivalently, how \(\hat{k}_i / \hat{k}\) is distributed. The distribution of a ratio of random variables is not easy to calculate analytically, and it is not guaranteed that the ratio distribution is normal if the numerator and denominator are normally distributed. For example, if \(X\) is a standard normal distribution and \(Y\) is also standard normal distribution, then the ratio \(X/Y\) has the standard Cauchy distribution. The reader should be reminded that the Cauchy distribution has no defined mean or variance. That being said, Geary has shown that, for the case of two normal distributions, if the denominator is unlikely to assume values less than zero, then the ratio distribution is indeed approximately normal. In our case, \(\hat{k}\) absolutely cannot assume a value less than zero, so we can be reasonably assured that the distribution of \(M_i\) will be normal.
For a normal distribution with mean \(\mu\) and distribution function \(f(x)\), it can be shown that
and thus the mean absolute deviation is \(\sqrt{2/\pi}\) times the standard deviation. Therefore, to evaluate the mean absolute deviation of \(M_i\), we need to first determine its variance. Substituting equation (16) into equation (17), we can rewrite \(M_i\) solely in terms of \(\hat{k}_1, \dots, \hat{k}_p\):
Since we know the variance of \(\hat{k}_i\), we can use the error propagation law to determine the variance of \(M_i\):
where the partial derivatives are evaluated at \(\hat{k}_j = k\). Since \(\hat{k}_j\) and \(\hat{k}_m\) are independent if \(j \neq m\), their covariance is zero and thus the second term cancels out. Evaluating the partial derivatives, we obtain
Through a similar analysis, one can show that the variance of \(\sum_{i=1}^j M_i\) is
Thus, the expected amount of communication on node \(j\), i.e. the mean absolute deviation of \(\sum_{i=1}^j M_i\) is proportional to
This formula has all the properties that one would expect based on intuition:
1. As the number of histories increases, the communication cost on each node increases as well;
2. If \(p=1\), i.e. if the problem is run on only one compute node, the variance will be zero. This reflects the fact that exactly \(N\) sites will be sampled if there is only one node.
3. For \(j=p\), the variance will be zero. Again, this says that when you sum the number of sites from each node, you will get exactly \(N\) sites.
We can determine the node that has the highest communication cost by differentiating equation (25) with respect to \(j\), setting it equal to zero, and solving for \(j\). Doing so yields \(j_{\text{max}} = p/2\). Interestingly, substituting \(j = p/2\) in equation (25) shows us that the maximum communication cost is actually independent of the number of nodes:
References
[Troubetzkoy] | E. Troubetzkoy, H. Steinberg, and M. Kalos, “Monte Carlo Radiation Penetration Calculations on a Parallel Computer,” Trans. Am. Nucl. Soc., 17, 260 (1973). |
Nonlinear Diffusion Acceleration - Coarse Mesh Finite Difference¶
This page section discusses how nonlinear diffusion acceleration (NDA) using coarse mesh finite difference (CMFD) is implemented into OpenMC. Before we get into the theory, general notation for this section is discussed.
Note that the methods discussed in this section are written specifically for continuous-energy mode but equivalent apply to the multi-group mode if the particle’s energy is replaced with the particle’s group
Notation¶
Before deriving NDA relationships, notation is explained. If a parameter has a \(\overline{\cdot}\), it is surface area-averaged and if it has a \(\overline{\overline\cdot}\), it is volume-averaged. When describing a specific cell in the geometry, indices \((i,j,k)\) are used which correspond to directions \((x,y,z)\). In most cases, the same operation is performed in all three directions. To compactly write this, an arbitrary direction set \((u,v,w)\) that corresponds to cell indices \((l,m,n)\) is used. Note that \(u\) and \(l\) do not have to correspond to \(x\) and \(i\). However, if \(u\) and \(l\) correspond to \(y\) and \(j\), \(v\) and \(w\) correspond to \(x\) and \(z\) directions. An example of this is shown in the following expression:
Here, \(u\) takes on each direction one at a time. The parameter \(J\) is surface area-averaged over the transverse indices \(m\) and \(n\) located at \(l+1/2\). Usually, spatial indices are listed as subscripts and the direction as a superscript. Energy group indices represented by \(g\) and \(h\) are also listed as superscripts here. The group \(g\) is the group of interest and, if present, \(h\) is all groups. Finally, any parameter surrounded by \(\left\langle\cdot\right\rangle\) represents a tally quantity that can be edited from a Monte Carlo (MC) solution.
Theory¶
NDA is a diffusion model that has equivalent physics to a transport model. There are many different methods that can be classified as NDA. The CMFD method is a type of NDA that represents second order multigroup diffusion equations on a coarse spatial mesh. Whether a transport model or diffusion model is used to represent the distribution of neutrons, these models must satisfy the neutron balance equation. This balance is represented by the following formula for a specific energy group \(g\) in cell \((l,m,n)\):
In eq. (2) the parameters are defined as:
- \(\left\langle\overline{J}^{u,g}_{l\pm 1/2,m,n}\Delta_m^v\Delta_n^w\right\rangle\) — surface area-integrated net current over surface \((l\pm 1/2,m,n)\) with surface normal in direction \(u\) in energy group \(g\). By dividing this quantity by the transverse area, \(\Delta_m^v\Delta_n^w\), the surface area-averaged net current can be computed.
- \(\left\langle\overline{\overline\Sigma}_{t_{l,m,n}}^g \overline{\overline\phi}_{l,m,n}^g\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) — volume-integrated total reaction rate over energy group \(g\).
- \(\left\langle\overline{\overline{\nu_s\Sigma}}_{s_{l,m,n}}^{h\rightarrow g} \overline{\overline\phi}_{l,m,n}^h\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) — volume-integrated scattering production rate of neutrons that begin with energy in group \(h\) and exit reaction in group \(g\). This reaction rate also includes the energy transfer of reactions (except fission) that produce multiple neutrons such as (n, 2n); hence, the need for \(\nu_s\) to represent neutron multiplicity.
- \(k_{eff}\) — core multiplication factor.
- \(\left\langle\overline{\overline{\nu_f\Sigma}}_{f_{l,m,n}}^{h\rightarrow g}\overline{\overline\phi}_{l,m,n}^h\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) — volume-integrated fission production rate of neutrons from fissions in group \(h\) that exit in group \(g\).
Each quantity in \(\left\langle\cdot\right\rangle\) represents a scalar value that is obtained from an MC tally. A good verification step when using an MC code is to make sure that tallies satisfy this balance equation within statistics. No NDA acceleration can be performed if the balance equation is not satisfied.
There are three major steps to consider when performing NDA: (1) calculation of macroscopic cross sections and nonlinear parameters, (2) solving an eigenvalue problem with a system of linear equations, and (3) modifying MC source distribution to align with the NDA solution on a chosen mesh. This process is illustrated as a flow chart below. After a batch of neutrons is simulated, NDA can take place. Each of the steps described above is described in detail in the following sections.

Figure 1: Flow chart of NDA process. Note “XS” is used for cross section and “DC” is used for diffusion coefficient.
Calculation of Macroscopic Cross Sections¶
A diffusion model needs macroscopic cross sections and diffusion coefficients to solve for multigroup fluxes. Cross sections are derived by conserving reaction rates predicted by MC tallies. From Eq. (2), total, scattering production and fission production macroscopic cross sections are needed. They are defined from MC tallies as follows:
and
In order to fully conserve neutron balance, leakage rates also need to be preserved. In standard diffusion theory, leakage rates are represented by diffusion coefficients. Unfortunately, it is not easy in MC to calculate a single diffusion coefficient for a cell that describes leakage out of each surface. Luckily, it does not matter what definition of diffusion coefficient is used because nonlinear equivalence parameters will correct for this inconsistency. However, depending on the diffusion coefficient definition chosen, different convergence properties of NDA equations are observed. Here, we introduce a diffusion coefficient that is derived for a coarse energy transport reaction rate. This definition can easily be constructed from MC tallies provided that angular moments of scattering reaction rates can be obtained. The diffusion coefficient is defined as follows:
where
Note that the transport reaction rate is calculated from the total reaction rate reduced by the \(P_1\) scattering production reaction rate. Equation (6) does not represent the best definition of diffusion coefficients from MC; however, it is very simple and usually fits into MC tally frameworks easily. Different methods to calculate more accurate diffusion coefficients can found in [Herman].
CMFD Equations¶
The first part of this section is devoted to discussing second-order finite volume discretization of multigroup diffusion equations. This will be followed up by the formulation of CMFD equations that are used in this NDA scheme. When performing second-order finite volume discretization of the diffusion equation, we need information that relates current to flux. In this numerical scheme, each cell is coupled only to its direct neighbors. Therefore, only two types of coupling exist: (1) cell-to-cell coupling and (2) cell-to-boundary coupling. The derivation of this procedure is referred to as finite difference diffusion equations and can be found in literature such as [Hebert]. These current/flux relationships are as follows:
- cell-to-cell coupling
- cell-to-boundary coupling
In Eqs. (8) and (9), the \(\pm\) refers to left (\(-x\)) or right (\(+x\)) surface in the \(x\) direction, back (\(-y\)) or front (\(+y\)) surface in the \(y\) direction and bottom (\(-z\)) or top (\(+z\)) surface in the \(z\) direction. For cell-to-boundary coupling, a general albedo, \(\beta_{l\pm1/2,m,n}^{u,g}\), is used. The albedo is defined as the ratio of incoming (\(-\) superscript) to outgoing (\(+\) superscript) partial current on any surface represented as
Common boundary conditions are: vacuum (\(\beta=0\)), reflective (\(\beta=1\)) and zero flux (\(\beta=-1\)). Both eq. (8) and eq. (9) can be written in this generic form,
The parameter \(\widetilde{D}_{l,m,n}^{u,g}\) represents the linear coupling term between current and flux. These current relationships can be sustituted into eq. (2) to produce a linear system of multigroup diffusion equations for each spatial cell and energy group. However, a solution to these equations is not consistent with a higher order transport solution unless equivalence factors are present. This is because both the diffusion approximation, governed by Fick’s Law, and spatial trunction error will produce differences. Therefore, a nonlinear parameter, \(\widehat{D}_{l,m,n}^{u,g}\), is added to eqs. (8) and (9). These equations are, respectively,
and
The only unknown in each of these equations is the equivalence parameter. The current, linear coupling term and flux can either be obtained or derived from MC tallies. Thus, it is called nonlinear because it is dependent on the flux which is updated on the next iteration.
Equations (12) and (13) can be substituted into eq. (2) to create a linear system of equations that is consistent with transport physics. One example of this equation is written for an interior cell,
It should be noted that before substitution, eq. (2) was divided by the volume of the cell, \(\Delta_l^u\Delta_m^v\Delta_n^w\). Equation (14) can be represented in operator form as
where \(\mathbb{M}\) is the neutron loss matrix operator, \(\mathbb{F}\) is the neutron production matrix operator, \(\mathbf{\Phi}\) is the multigroup flux vector and \(k\) is the eigenvalue. This generalized eigenvalue problem is solved to obtain fundamental mode multigroup fluxes and eigenvalue. In order to produce consistent results with transport theory from these equations, the neutron balance equation must have been satisfied by MC tallies. The desire is that CMFD equations will produce a more accurate source than MC after each fission source generation.
CMFD Feedback¶
Now that a more accurate representation of the expected source distribution is estimated from CMFD, it needs to be communicated back to MC. The first step in this process is to generate a probability mass function that provides information about how probable it is for a neutron to be born in a given cell and energy group. This is represented as
This equation can be multiplied by the number of source neutrons to obtain an estimate of the expected number of neutrons to be born in a given cell and energy group. This distribution can be compared to the MC source distribution to generate weight adjusted factors defined as
The MC source distribution is represented on the same coarse mesh as CMFD by summing all neutrons’ weights, \(w_s\), in a given cell and energy group. MC source weights can then be modified by this weight adjustment factor so that it matches the CMFD solution on the coarse mesh,
It should be noted that heterogeneous information about local coordinates and energy remain constant throughout this modification process.
Implementation in OpenMC¶
The section describes how CMFD was implemented in OpenMC. Before the simulation begins, a user sets up a CMFD input file that contains the following basic information:
- CMFD mesh (space and energy),
- boundary conditions at edge of mesh (albedos),
- acceleration region (subset of mesh, optional),
- fission source generation (FSG)/batch that CMFD should begin, and
- whether CMFD feedback should be applied.
It should be noted that for more difficult simulations (e.g., light water
reactors), there are other options available to users such as tally resetting
parameters, effective down-scatter usage, tally estimator, etc. For more
information please see the openmc.cmfd.CMFDRun
class.
Of the options described above, the optional acceleration subset region is an uncommon feature. Because OpenMC only has a structured Cartesian mesh, mesh cells may overlay regions that don’t contain fissionable material and may be so far from the core that the neutron flux is very low. If these regions were included in the CMFD solution, bad estimates of diffusion parameters may result and affect CMFD feedback. To deal with this, a user can carve out an active acceleration region from their structured Cartesian mesh. This is illustrated in diagram below. When placing a CMFD mesh over a geometry, the boundary conditions must be known at the global edges of the mesh. If the geometry is complex like the one below, one may have to cover the whole geometry including the reactor pressure vessel because we know that there is a zero incoming current boundary condition at the outer edge of the pressure vessel. This is not viable in practice because neutrons in simulations may not reach mesh cells that are near the pressure vessel. To circumvent this, one can shrink the mesh to cover just the core region as shown in the diagram. However, one must still estimate the boundary conditions at the global boundaries, but at these locations, they are not readily known. In OpenMC, one can carve out the active core region from the entire structured Cartesian mesh. This is shown in the diagram below by the darkened region over the core. The albedo boundary conditions at the active core/reflector boundary can be tallied indirectly during the MC simulation with incoming and outgoing partial currents. This allows the user to not have to worry about neutrons producing adequate tallies in mesh cells far away from the core.

Figure 2: Diagram of CMFD acceleration mesh
During an MC simulation, CMFD tallies are accumulated. The basic tallies needed are listed in Table OpenMC CMFD tally list. Each tally is performed on a spatial and energy mesh basis. The surface area-integrated net current is tallied on every surface of the mesh. OpenMC tally objects are created by the CMFD code internally, and cross sections are calculated at each CMFD feedback iteration. The first CMFD iteration, controlled by the user, occurs just after tallies are communicated to the master processor. Once tallies are collapsed, cross sections, diffusion coefficients and equivalence parameters are calculated. This is performed only on the acceleration region if that option has been activated by the user. Once all diffusion parameters are calculated, CMFD matrices are formed where energy groups are the inner most iteration index. In OpenMC, compressed row storage sparse matrices are used due to the sparsity of CMFD operators. An example of this sparsity is shown for the 3-D BEAVRS model in figures 3 and 4 [BEAVRS]. These matrices represent an assembly radial mesh, 24 cell mesh in the axial direction and two energy groups. The loss matrix is 99.92% sparse and the production matrix is 99.99% sparse. Although the loss matrix looks like it is tridiagonal, it is really a seven banded matrix with a block diagonal matrix for scattering. The production matrix is a \(2\times 2\) block diagonal; however, zeros are present because no fission neutrons appear with energies in the thermal group.
tally | score | filter |
---|---|---|
\(\left\langle\overline{\overline\phi}_{l,m,n}^g \Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) | flux | mesh, energy |
\(\left\langle\overline{\overline\Sigma}_{t_{l,m,n}}^g \overline{\overline\phi}_{l,m,n}^g\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) | total | mesh, energy |
\(\left\langle\overline{\overline{\nu_s\Sigma}}_{s1_{l,m,n}}^g \overline{\overline\phi}_{l,m,n}^g\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) | nu-scatter-1 | mesh, energy |
\(\left\langle\overline{\overline{\nu_s\Sigma}}_{s_{l,m,n}}^{h\rightarrow g} \overline{\overline\phi}_{l,m,n}^h\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) | nu-scatter | mesh, energy, energyout |
\(\left\langle\overline{\overline{\nu_f\Sigma}}_{f_{l,m,n}}^{h\rightarrow g} \overline{\overline\phi}_{l,m,n}^h\Delta_l^u\Delta_m^v\Delta_n^w\right\rangle\) | nu-fission | mesh, energy, energyout |
\(\left\langle\overline{J}^{u,g}_{l\pm 1/2,m,n}\Delta_m^v\Delta_n^w\right\rangle\) | current | mesh, energy |
To solve the eigenvalue problem with these matrices, different source iteration and linear solvers can be used. The most common source iteration solver used is standard power iteration as described in [Gill]. To accelerate these source iterations, a Wielandt shift scheme can be used as discussed in [Park]. PETSc solvers were first implemented to perform the linear solution in parallel that occurs once per source iteration. When using PETSc, different types of parallel linear solvers and preconditioners can be used. By default, OpenMC uses an incomplete LU preconditioner and a GMRES Krylov solver. After some initial studies of parallelization with PETSc, it was observed that because CMFD matrices are very sparse, solution times do not scale well. An additional Gauss-Seidel linear solver with Chebyshev acceleration was added that is similar to the one used for CMFD in CASMO [Rhodes] and [Smith]. This solver was implemented with a custom section for two energy groups. Because energy group is the inner most index, a block diagonal is formed when using more than one group. For two groups, it is easy to invert this diagonal analytically inside the Gauss-Seidel iterative solver. For more than two groups, this analytic inversion can still be performed, but with more computational effort. A standard Gauss-Seidel solver is used for more than two groups.
Besides a power iteration, a Jacobian-free Newton-Krylov method was also implemented to obtain eigenvalue and multigroup fluxes as described in [Gill] and [Knoll]. This method is not the primary one used, but has gotten recent attention due to its coupling advantages to other physics such as thermal hydraulics. Once multigroup fluxes are obtained, a normalized fission source is calculated in the code using eq. (16) directly.
The next step in the process is to compute weight adjustment factors. These are calculated by taking the ratio of the expected number of neutrons from the CMFD source distribution to the current number of neutrons in each mesh. It is straightforward to compute the CMFD number of neutrons because it is the product between the total starting initial weight of neutrons and the CMFD normalized fission source distribution. To compute the number of neutrons from the current MC source, OpenMC sums the statistical weights of neutrons from the source bank on a given spatial and energy mesh. Once weight adjustment factors were calculated, each neutron’s statistical weight in the source bank was modified according to its location and energy. Examples of CMFD simulations using OpenMC can be found in [HermanThesis].
References
[BEAVRS] | Nick Horelik, Bryan Herman. Benchmark for Evaluation And Verification of Reactor Simulations. Massachusetts Institute of Technology, https://crpg.mit.edu/research/beavrs , 2013. |
[Gill] | (1, 2) Daniel F. Gill. Newton-Krylov methods for the solution of the k-eigenvalue problem in multigroup neutronics calculations. Ph.D. thesis, Pennsylvania State University, 2010. |
[Hebert] | Alain Hebert. Applied reactor physics. Presses Internationales Polytechnique, Montreal, 2009. |
[Herman] | Bryan R. Herman, Benoit Forget, Kord Smith, and Brian N. Aviles. Improved diffusion coefficients generated from Monte Carlo codes. In Proceedings of M&C 2013, Sun Valley, ID, USA, May 5 - 9, 2013. |
[HermanThesis] | Bryan R. Herman. Monte Carlo and Thermal Hydraulic Coupling using Low-Order Nonlinear Diffusion Acceleration. Sc.D. thesis, Massachusetts Institute of Technology, 2014. |
[Knoll] | D.A. Knoll, H. Park, and C. Newman. Acceleration of k-eigenvalue/criticality calculations using the Jacobian-free Newton-Krylov method. Nuclear Science and Engineering, 167:133–140, 2011. |
[Park] | H. Park, D.A. Knoll, and C.K. Newman. Nonlinear acceleration of transport criticality problems. Nuclear Science and Engineering, 172:52–65, 2012. |
[Rhodes] | Joel Rhodes and Malte Edenius. CASMO-4 — A Fuel Assembly Burnup Program. User’s Manual. Studsvik of America, ssp-09/443-u rev 0, proprietary edition, 2001. |
[Smith] | Kord S Smith and Joel D Rhodes III. Full-core, 2-D, LWR core calculations with CASMO-4E. In Proceedings of PHYSOR 2002, Seoul, Korea, October 7 - 10, 2002. |
User’s Guide¶
Welcome to the OpenMC User’s Guide! This tutorial will guide you through the essential aspects of using OpenMC to perform simulations.
A Beginner’s Guide to OpenMC¶
What does OpenMC do?¶
In a nutshell, OpenMC simulates neutral particles (presently neutrons and photons) moving stochastically through an arbitrarily defined model that represents an real-world experimental setup. The experiment could be as simple as a sphere of metal or as complicated as a full-scale nuclear reactor. This is what’s known as Monte Carlo simulation. In the case of a nuclear reactor model, neutrons are especially important because they are the particles that induce fission in isotopes of uranium and other elements. Knowing the behavior of neutrons allows one to determine how often and where fission occurs. The amount of energy released is then directly proportional to the fission reaction rate since most heat is produced by fission. By simulating many neutrons (millions or billions), it is possible to determine the average behavior of these neutrons (or the behavior of the energy produced, or any other quantity one is interested in) very accurately.
Using Monte Carlo methods to determine the average behavior of various physical quantities in a system is quite different from other means of solving the same problem. The other class of methods for determining the behavior of neutrons and reactions rates is so-called deterministic methods. In these methods, the starting point is not randomly simulating particles but rather writing an equation that describes the average behavior of the particles. The equation that describes the average behavior of neutrons is called the neutron transport equation. This equation is a seven-dimensional equation (three for space, three for velocity, and one for time) and is very difficult to solve directly. For all but the simplest problems, it is necessary to make some sort of discretization. As an example, we can divide up all space into small sections which are homogeneous and then solve the equation on those small sections. After these discretizations and various approximations, one can arrive at forms that are suitable for solution on a computer. Among these are discrete ordinates, method of characteristics, finite-difference diffusion, and nodal methods.
So why choose Monte Carlo over deterministic methods? Each method has its pros and cons. Let us first take a look at few of the salient pros and cons of deterministic methods:
- Pro: Depending on what method is used, solution can be determined very quickly.
- Pro: The solution is a global solution, i.e. we know the average behavior everywhere.
- Pro: Once the problem is converged, the solution is known.
- Con: If the model is complex, it is necessary to do sophisticated mesh generation.
- Con: It is necessary to generate multi-group cross sections which requires knowing the solution a priori.
Now let’s look at the pros and cons of Monte Carlo methods:
- Pro: No mesh generation is required to build geometry. By using constructive solid geometry, it’s possible to build complex models with curved surfaces.
- Pro: Monte Carlo methods can be used with either continuous-energy or multi-group cross sections.
- Pro: Running simulations in parallel is conceptually very simple.
- Con: Because they rely on repeated random sampling, they are computationally very expensive.
- Con: A simulation doesn’t automatically give you the global solution everywhere – you have to specifically ask for those quantities you want.
- Con: Even after the problem is converged, it is necessary to simulate many particles to reduce stochastic uncertainty.
Because fewer approximations are made in solving a problem by the Monte Carlo method, it is often seen as a “gold standard” which can be used as a benchmark for a solution of the same problem by deterministic means. However, it comes at the expense of a potentially longer simulation.
How does it work?¶
In order to do anything, the code first needs to have a model of some problem of interest. This could be a nuclear reactor or any other physical system with fissioning material. You, as the code user, will need to describe the model so that the code can do something with it. A basic model consists of a few things:
- A description of the geometry – the problem must be split up into regions of homogeneous material composition.
- For each different material in the problem, a description of what nuclides are in the material and at what density.
- Various parameters telling the code how many particles to simulate and what options to use.
- A list of different physical quantities that the code should return at the end of the simulation. In a Monte Carlo simulation, if you don’t ask for anything, it will not give you any answers (other than a few default quantities).
What do I need to know?¶
If you are starting to work with OpenMC, there are a few things you should be familiar with. Whether you plan on working in Linux, macOS, or Windows, you should be comfortable working in a command line environment. There are many resources online for learning command line environments. If you are using Linux or Mac OS X (also Unix-derived), this tutorial will help you get acquainted with commonly-used commands.
To reap the full benefits of OpenMC, you should also have basic proficiency in the use of Python, as OpenMC includes a rich Python API that offers many usability improvements over dealing with raw XML input files.
OpenMC uses a version control software called git to keep track of changes to the code, document bugs and issues, and other development tasks. While you don’t necessarily have to have git installed in order to download and run OpenMC, it makes it much easier to receive updates if you do have it installed and have a basic understanding of how it works. There are a list of good git tutorials at the git documentation website. The OpenMC source code and documentation are hosted at GitHub. In order to receive updates to the code directly, submit bug reports, and perform other development tasks, you may want to sign up for a free account on GitHub. Once you have an account, you can follow these instructions on how to set up your computer for using GitHub.
If you are new to nuclear engineering, you may want to review the NRC’s Reactor Concepts Manual. This manual describes the basics of nuclear power for electricity generation, the fission process, and the overall systems in a pressurized or boiling water reactor. Another resource that is a bit more technical than the Reactor Concepts Manual but still at an elementary level is the DOE Fundamentals Handbook on Nuclear Physics and Reactor Theory Volume I and Volume II. You may also find it helpful to review the following terms:
Installation and Configuration¶
Installing on Linux/Mac with conda-forge¶
Conda is an open source package management system and environment management system for installing multiple versions of software packages and their dependencies and switching easily between them. If you have conda installed on your system, OpenMC can be installed via the conda-forge channel. First, add the conda-forge channel with:
conda config --add channels conda-forge
To list the versions of OpenMC that are available on the conda-forge channel, in your terminal window or an Anaconda Prompt run:
conda search openmc
OpenMC can then be installed with:
conda create -n openmc-env openmc
This will install OpenMC in a conda environment called openmc-env. To activate the environment, run:
conda activate openmc-env
Installing on Linux/Mac/Windows with Docker¶
OpenMC can be easily deployed using Docker on any Windows, Mac, or Linux system. With Docker running, execute the following command in the shell to download and run a Docker image with the most recent release of OpenMC from DockerHub:
docker run openmc/openmc:latest
This will take several minutes to run depending on your internet download speed. The command will place you in an interactive shell running in a Docker container with OpenMC installed.
Note
The docker run
command supports many options for spawning
containers including mounting volumes from the host filesystem,
which many users will find useful.
Installing from Source using Spack¶
Spack is a package management tool designed to support multiple versions and configurations of software on a wide variety of platforms and environments. Please follow Spack’s setup guide to configure the Spack system.
The OpenMC Spack recipe has been configured with variants that match most options provided in the CMakeLists.txt file. To see a list of these variants and other information use:
spack info openmc
Note
It should be noted that by default OpenMC builds with -O2 -g
flags which
are equivalent to a CMake build type of RelwithDebInfo. In addition, MPI
is OFF while OpenMP is ON.
It is recommended to install OpenMC with the Python API. Information about this Spack recipe can be found with the following command:
spack info py-openmc
Note
The only variant for the Python API is mpi
.
The most basic installation of OpenMC can be accomplished by entering the following command:
Caution
When installing any Spack package, dependencies are assumed to be at
configured defaults unless otherwise specfied in the specification on the
command line. In the above example, assuming the default options weren’t
changed in Spack’s package configuration, py-openmc will link against a
non-optimized non-MPI openmc. Even if an optimized openmc was built
separately, it will rebuild openmc with optimization OFF. Thus, if you are
trying to link against dependencies that were configured different than
defaults, ^openmc[variants]
will have to be present in the command.
For a more performant build of OpenMC with optimization turned ON and MPI provided by OpenMPI, the following command can be used:
spack install py-openmc+mpi ^openmc+optimize ^openmpi
Note
+mpi
is automatically forwarded to OpenMC.
Tip
When installing py-openmc, it will use Spack’s preferred Python. For
example, assuming Spack’s preferred Python is 3.8.7, to build py-openmc
against the latest Python 3.7 instead, ^python@3.7.0:3.7.99
should be
added to the specification on the command line. Additionally, a compiler
type and version can be specified at the end of the command using
%gcc@<version>
, %intel@<version>
, etc.
A useful tool in Spack is to look at the dependency tree before installation.
This can be observed using Spack’s spec
tool:
Once installed, environment/lmod modules can be generated or Spack’s load
feature can be used to access the installed packages.
Installing from Source¶
Prerequisites¶
Required
A C/C++ compiler such as gcc
OpenMC’s core codebase is written in C++. The source files have been tested to work with a wide variety of compilers. If you are using a Debian-based distribution, you can install the g++ compiler using the following command:
sudo apt install g++CMake cross-platform build system
The compiling and linking of source files is handled by CMake in a platform-independent manner. If you are using Debian or a Debian derivative such as Ubuntu, you can install CMake using the following command:
sudo apt install cmakeHDF5 Library for portable binary output format
OpenMC uses HDF5 for many input/output files. As such, you will need to have HDF5 installed on your computer. The installed version will need to have been compiled with the same compiler you intend to compile OpenMC with. If compiling with gcc from the APT repositories, users of Debian derivatives can install HDF5 and/or parallel HDF5 through the package manager:
sudo apt install libhdf5-devParallel versions of the HDF5 library called libhdf5-mpich-dev and libhdf5-openmpi-dev exist which are built against MPICH and OpenMPI, respectively. To link against a parallel HDF5 library, make sure to set the HDF5_PREFER_PARALLEL CMake option, e.g.:
CXX=mpicxx.mpich cmake -DHDF5_PREFER_PARALLEL=on ..Note that the exact package names may vary depending on your particular distribution and version.
If you are using building HDF5 from source in conjunction with MPI, we recommend that your HDF5 installation be built with parallel I/O features. An example of configuring HDF5 is listed below:
CC=mpicc ./configure --enable-parallelYou may omit
--enable-parallel
if you want to compile HDF5 in serial.
Optional
An MPI implementation for distributed-memory parallel runs
To compile with support for parallel runs on a distributed-memory architecture, you will need to have a valid implementation of MPI installed on your machine. The code has been tested and is known to work with the latest versions of both OpenMPI and MPICH. OpenMPI and/or MPICH can be installed on Debian derivatives with:
sudo apt install mpich libmpich-dev sudo apt install openmpi-bin libopenmpi-devgit version control software for obtaining source code
DAGMC toolkit for simulation using CAD-based geometries
OpenMC supports particle tracking in CAD-based geometries via the Direct Accelerated Geometry Monte Carlo (DAGMC) toolkit (installation instructions). For use in OpenMC, only the
MOAB_DIR
andBUILD_TALLY
variables need to be specified in the CMake configuration step when building DAGMC. This option also allows unstructured mesh tallies on tetrahedral MOAB meshes. In addition to turning this option on, the path to the DAGMC installation should be specified as part of theCMAKE_PREFIX_PATH
variable:cmake -Ddagmc=on -DCMAKE_PREFIX_PATH=/path/to/dagmc/installationlibMesh mesh library framework for numerical simulations of partial differential equations
This optional dependency enables support for unstructured mesh tally filters using libMesh meshes. Any 3D element type supported by libMesh can be used, but the implementation is currently restricted to collision estimators. In addition to turning this option on, the path to the libMesh installation should be specified as part of the
CMAKE_PREFIX_PATH
variable.:CXX=mpicxx cmake -Dlibmesh=on -DCMAKE_PREFIX_PATH=/path/to/libmesh/installationNote that libMesh is most commonly compiled with MPI support. If that is the case, then OpenMC should be compiled with MPI support as well.
Obtaining the Source¶
All OpenMC source code is hosted on GitHub. You can download the source code directly from GitHub or, if you have the git version control software installed on your computer, you can use git to obtain the source code. The latter method has the benefit that it is easy to receive updates directly from the GitHub repository. GitHub has a good set of instructions for how to set up git to work with GitHub since this involves setting up ssh keys. With git installed and setup, the following command will download the full source code from the GitHub repository:
git clone --recurse-submodules https://github.com/openmc-dev/openmc.git
By default, the cloned repository will be set to the development branch. To switch to the source of the latest stable release, run the following commands:
cd openmc
git checkout master
Build Configuration¶
Compiling OpenMC with CMake is carried out in two steps. First, cmake
is run
to determine the compiler, whether optional packages (MPI, HDF5) are available,
to generate a list of dependencies between source files so that they may be
compiled in the correct order, and to generate a normal Makefile. The Makefile
is then used by make
to actually carry out the compile and linking
commands. A typical out-of-source build would thus look something like the
following
mkdir build && cd build
cmake ..
make
Note that first a build directory is created as a subdirectory of the source directory. The Makefile in the top-level directory will automatically perform an out-of-source build with default options.
CMakeLists.txt Options¶
The following options are available in the CMakeLists.txt file:
- debug
- Enables debugging when compiling. The flags added are dependent on which compiler is used.
- profile
- Enables profiling using the GNU profiler, gprof.
- optimize
- Enables high-optimization using compiler-dependent flags. For gcc and Intel C++, this compiles with -O3.
- openmp
- Enables shared-memory parallelism using the OpenMP API. The C++ compiler being used must support OpenMP. (Default: on)
- dagmc
- Enables use of CAD-based DAGMC geometries and MOAB_ unstructured mesh tallies. Please see the note about DAGMC in the optional dependencies list for more information on this feature. The installation directory for DAGMC should also be defined as DAGMC_ROOT in the CMake configuration command. (Default: off)
- libmesh
- Enables the use of unstructured mesh tallies with libMesh. (Default: off)
- coverage
- Compile and link code instrumented for coverage analysis. This is typically used in conjunction with gcov.
To set any of these options (e.g. turning on debug mode), the following form should be used:
cmake -Ddebug=on /path/to/openmc
Compiling with MPI¶
To compile with MPI, set the CXX
environment variable to the path to
the MPI C++ wrapper. For example, in a bash shell:
export CXX=mpicxx
cmake /path/to/openmc
Note that in many shells, environment variables can be set for a single command, i.e.
CXX=mpicxx cmake /path/to/openmc
Selecting HDF5 Installation¶
CMakeLists.txt searches for the h5cc
or h5pcc
HDF5 C wrapper on
your PATH environment variable and subsequently uses it to determine library
locations and compile flags. If you have multiple installations of HDF5 or one
that does not appear on your PATH, you can set the HDF5_ROOT environment
variable to the root directory of the HDF5 installation, e.g.
export HDF5_ROOT=/opt/hdf5/1.8.15
cmake /path/to/openmc
This will cause CMake to search first in /opt/hdf5/1.8.15/bin for h5cc
/
h5pcc
before it searches elsewhere. As noted above, an environment variable
can typically be set for a single command, i.e.
HDF5_ROOT=/opt/hdf5/1.8.15 cmake /path/to/openmc
Compiling on Linux and Mac OS X¶
To compile OpenMC on Linux or Max OS X, run the following commands from within the root directory of the source code:
mkdir build && cd build
cmake ..
make
make install
This will build an executable named openmc
and install it (by default in
/usr/local/bin). If you do not have administrative privileges, you can install
OpenMC locally by specifying an install prefix when running cmake:
cmake -DCMAKE_INSTALL_PREFIX=$HOME/.local ..
The CMAKE_INSTALL_PREFIX
variable can be changed to any path for which you
have write-access.
Compiling on Windows 10¶
Recent versions of Windows 10 include a subsystem for Linux that allows one to
run Bash within Ubuntu running in Windows. First, follow the installation guide
here to get Bash
on Ubuntu on Windows setup. Once you are within bash, obtain the necessary
prerequisites via apt
. Finally, follow the
instructions for compiling on linux.
Testing Build¶
To run the test suite, you will first need to download a pre-generated cross section library along with windowed multipole data. Please refer to our Test Suite documentation for further details.
Installing Python API¶
If you installed OpenMC using Conda, no further steps are
necessary in order to use OpenMC’s Python API. However, if
you are installing from source, the Python API is not
installed by default when make install
is run because in many situations it
doesn’t make sense to install a Python package in the same location as the
openmc
executable (for example, if you are installing the package into a
virtual environment). The
easiest way to install the openmc
Python package is to use pip, which is
included by default in Python 3.4+. From the root directory of the OpenMC
distribution/repository, run:
pip install .
pip will first check that all required third-party packages have been installed, and if they are not present,
they will be installed by downloading the appropriate packages from the Python
Package Index (PyPI). However, do note that since pip
runs the setup.py
script which requires NumPy, you will have to first
install NumPy:
pip install numpy
Installing in “Development” Mode¶
If you are primarily doing development with OpenMC, it is strongly recommended to install the Python package in “editable” mode.
Prerequisites¶
The Python API works with Python 3.5+. In addition to Python itself, the API relies on a number of third-party packages. All prerequisites can be installed using Conda (recommended), pip, or through the package manager in most Linux distributions.
Required
- NumPy
- NumPy is used extensively within the Python API for its powerful N-dimensional array.
- SciPy
- SciPy’s special functions, sparse matrices, and spatial data structures are used for several optional features in the API.
- pandas
- Pandas is used to generate tally DataFrames as demonstrated in an example notebook.
- h5py
- h5py provides Python bindings to the HDF5 library. Since OpenMC outputs various HDF5 files, h5py is needed to provide access to data within these files from Python.
- Matplotlib
- Matplotlib is used to providing plotting functionality in the API like the
Universe.plot()
method and theopenmc.plot_xs()
function. - uncertainties
- Uncertainties are used for decay data in the
openmc.data
module. - lxml
- lxml is used for the openmc-validate-xml script and various other parts of the Python API.
Optional
- mpi4py
- mpi4py provides Python bindings to MPI for running distributed-memory parallel runs. This package is needed if you plan on running depletion simulations in parallel using MPI.
- Cython
- Cython is used for resonance reconstruction for ENDF data converted to
openmc.data.IncidentNeutron
. - vtk
- The Python VTK bindings are needed to convert voxel and track files to VTK format.
- pytest
- The pytest framework is used for unit testing the Python API.
If you are running simulations that require OpenMC’s Python bindings to the C
API (including depletion and CMFD), it is recommended to build h5py
(and
mpi4py
, if you are using MPI) using the same compilers and HDF5 version as
for OpenMC. Thus, the install process would proceed as follows:
mkdir build && cd build
HDF5_ROOT=<path to HDF5> CXX=<path to mpicxx> cmake ..
make
make install
cd ..
MPICC=<path to mpicc> pip install mpi4py
HDF5_DIR=<path to HDF5> pip install --no-binary=h5py h5py
If you are using parallel HDF5, you’ll also need to make sure the right MPI wrapper is used when installing h5py:
CC=<path to mpicc> HDF5_MPI=ON HDF5_DIR=<path to HDF5> pip install --no-binary=h5py h5py
Configuring Input Validation with GNU Emacs nXML mode¶
The GNU Emacs text editor has a built-in mode that extends functionality for
editing XML files. One of the features in nXML mode is the ability to perform
real-time validation of XML files against a RELAX NG schema. The OpenMC
source contains RELAX NG schemas for each type of user input file. In order for
nXML mode to know about these schemas, you need to tell emacs where to find a
“locating files” description. Adding the following lines to your ~/.emacs
file will enable real-time validation of XML input files:
(require 'rng-loc)
(add-to-list 'rng-schema-locating-files "~/openmc/schemas.xml")
Make sure to replace the last string on the second line with the path to the schemas.xml file in your own OpenMC source directory.
Cross Section Configuration¶
In order to run a simulation with OpenMC, you will need cross section data for each nuclide or material in your problem. OpenMC can be run in continuous-energy or multi-group mode.
In continuous-energy mode, OpenMC uses a native HDF5 format (see Nuclear Data File Formats) to store all nuclear data. Pregenerated HDF5 libraries can be found at https://openmc.org; unless you have specific data needs, it is highly recommended to use one of the pregenerated libraries. Alternatively, if you have ACE format data that was produced with NJOY, such as that distributed with MCNP or Serpent, it can be converted to the HDF5 format using the using the Python API. Several sources provide openly available ACE data including the ENDF/B, JEFF, and TENDL libraries as well as the LANL Nuclear Data Team. In addition to tabulated cross sections in the HDF5 files, OpenMC relies on windowed multipole data to perform on-the-fly Doppler broadening.
In multi-group mode, OpenMC utilizes an HDF5-based library format which can be used to describe nuclide- or material-specific quantities.
Environment Variables¶
When openmc is run, it will look for several environment
variables that indicate where cross sections can be found. While the location of
cross sections can also be indicated through the
openmc.Materials.cross_sections
attribute (or in the materials.xml file), if you always use the same set of cross section data, it
is often easier to just set an environment variable that will be picked up by
default every time OpenMC is run. The following environment variables are used:
OPENMC_CROSS_SECTIONS
- Indicates the path to the cross_sections.xml
summary file that is used to locate HDF5 format cross section libraries if the
user has not specified
openmc.Materials.cross_sections
(equivalently, the <cross_sections> Element in materials.xml). OPENMC_MG_CROSS_SECTIONS
- Indicates the path to an HDF5 file that contains
multi-group cross sections if the user has not specified
openmc.Materials.cross_sections
(equivalently, the <cross_sections> Element in materials.xml).
To set these environment variables persistently, export them from your shell
profile (.profile
or .bashrc
in bash).
Continuous-Energy Cross Sections¶
Using Pregenerated Libraries¶
Various evaluated nuclear data libraries have been processed into the HDF5
format required by OpenMC and can be found at https://openmc.org. You
can find both libraries generated by the OpenMC development team as well as
libraries based on ACE files distributed elsewhere. To use these libraries,
download the archive file, unpack it, and then set your
OPENMC_CROSS_SECTIONS
environment variable to the absolute path of
the cross_sections.xml
file contained in the unpacked directory.
Manually Creating a Library from ACE files¶
The openmc.data
module in the Python API enables users to directly
convert ACE data to OpenMC’s HDF5 format and create a corresponding
cross_sections.xml file. For those who prefer to use
the API directly, the openmc.data.IncidentNeutron
and
openmc.data.ThermalScattering
classes can be used to read ACE data and
convert it to HDF5. For continuous-energy incident neutron data, use the
IncidentNeutron.from_ace()
class method to read in an existing ACE file
and the IncidentNeutron.export_to_hdf5()
method to write the data to an
HDF5 file.
u235 = openmc.data.IncidentNeutron.from_ace('92235.710nc')
u235.export_to_hdf5('U235.h5')
If you have multiple ACE files for the same nuclide at different temperatures,
you can use the IncidentNeutron.add_temperature_from_ace()
method to
append cross sections to an existing IncidentNeutron
instance:
u235 = openmc.data.IncidentNeutron.from_ace('92235.710nc')
for suffix in [711, 712, 713, 714, 715, 716]:
u235.add_temperature_from_ace('92235.{}nc'.format(suffix))
u235.export_to_hdf5('U235.h5')
Similar methods exist for thermal scattering data:
light_water = openmc.data.ThermalScattering.from_ace('lwtr.20t')
for suffix in range(21, 28):
light_water.add_temperature_from_ace('lwtr.{}t'.format(suffix))
light_water.export_to_hdf5('lwtr.h5')
Once you have created corresponding HDF5 files for each of your ACE files, you
can create a library and export it to XML using the
openmc.data.DataLibrary
class:
library = openmc.data.DataLibrary()
library.register_file('U235.h5')
library.register_file('lwtr.h5')
...
library.export_to_xml()
At this point, you will have a cross_sections.xml
file that you can use in
OpenMC.
Hint
The IncidentNeutron
class allows you to view/modify cross
sections, secondary angle/energy distributions, probability tables,
etc. For a more thorough overview of the capabilities of this class,
see the example notebook.
Manually Creating a Library from ENDF files¶
If you need to create a nuclear data library and you do not already have
suitable ACE files or you need to further customize the data (for example,
adding more temperatures), the IncidentNeutron.from_njoy()
and
ThermalScattering.from_njoy()
methods can be used to create data instances
by directly running NJOY. Both methods require that you pass the name of ENDF
file(s) that are passed on to NJOY. For example, to generate data for Zr-92:
zr92 = openmc.data.IncidentNeutron.from_njoy('n-040_Zr_092.endf')
By default, data is produced at room temperature, 293.6 K. You can also specify a list of temperatures that you want data at:
zr92 = openmc.data.IncidentNeutron.from_njoy(
'n-040_Zr_092.endf', temperatures=[300., 600., 1000.])
The IncidentNeutron.from_njoy()
method assumes you have an executable
named njoy
available on your path. If you want to explicitly name the
executable, the njoy_exec
optional argument can be used. Additionally, the
stdout
argument can be used to show the progress of the NJOY run.
To generate a thermal scattering file, you need to specify both an ENDF incident neutron sub-library file as well as a thermal neutron scattering sub-library file; for example:
light_water = openmc.data.ThermalScattering.from_njoy(
'neutrons/n-001_H_001.endf', 'thermal_scatt/tsl-HinH2O.endf')
Once you have instances of IncidentNeutron
and
ThermalScattering
, a library can be created by using the
export_to_hdf5()
methods and the DataLibrary
class as described in
Manually Creating a Library from ACE files.
Enabling Resonance Scattering Treatments¶
In order for OpenMC to correctly treat elastic scattering in heavy nuclides
where low-lying resonances might be present (see
Energy-Dependent Cross Section Model), the elastic scattering cross section at 0 K
must be present. If the data you are using was generated via
IncidentNeutron.from_njoy()
, you will already have 0 K elastic scattering
cross sections available. Otherwise, to add 0 K elastic scattering cross
sections to an existing IncidentNeutron
instance, you can use the
IncidentNeutron.add_elastic_0K_from_endf()
method which requires an ENDF
file for the nuclide you are modifying:
u238 = openmc.data.IncidentNeutron.from_hdf5('U238.h5')
u238.add_elastic_0K_from_endf('n-092_U_238.endf')
u238.export_to_hdf5('U238_with_0K.h5')
With 0 K elastic scattering data present, you can turn on a resonance scattering
method using Settings.resonance_scattering
.
Note
The process of reconstructing resonances and generating tabulated 0 K
cross sections can be computationally expensive, especially for
nuclides like U-238 where thousands of resonances are present. Thus,
running the IncidentNeutron.add_elastic_0K_from_endf()
method
may take several minutes to complete.
Photon Cross Sections¶
Photon interaction data is needed to run OpenMC with photon transport enabled. Some of this data, namely bremsstrahlung cross sections from Seltzer and Berger, mean excitation energy from the NIST ESTAR database, and Compton profiles calculated by Biggs et al. and available in the Geant4 G4EMLOW data file, is distributed with OpenMC. The rest is available from the NNDC, which provides ENDF data from the photo-atomic and atomic relaxation sublibraries of the ENDF/B-VII.1 library.
Most of the pregenerated HDF5 libraries available at https://openmc.org
already have photon interaction data included. If you are building a data
library yourself, it is possible to use the Python API directly to convert
photon interaction data from an ENDF or ACE file to an HDF5 file. The
openmc.data.IncidentPhoton
class contains an
IncidentPhoton.from_ace()
method that will generate photon data from an
ACE table and an IncidentPhoton.export_to_hdf5()
method that writes the
data to an HDF5 file:
u = openmc.data.IncidentPhoton.from_ace('92000.12p')
u.export_to_hdf5('U.h5')
Similarly, the IncidentPhoton.from_endf()
method can be used to read
photon data from an ENDF file. In this case, both the photo-atomic and atomic
relaxation sublibrary files are required:
u = openmc.data.IncidentPhoton.from_endf('photoat-092_U_000.endf',
'atom-092_U_000.endf')
Once the HDF5 files have been generated, a library can be created using the
DataLibrary
class as described in Manually Creating a Library from ACE files.
Windowed Multipole Data¶
OpenMC is capable of using windowed multipole data for on-the-fly Doppler
broadening. A comprehensive multipole data library containing all nuclides in
ENDF/B-VII.1 is available on GitHub. To obtain this library, download
and unpack an archive (.zip or .tag.gz) from GitHub. Once unpacked, you can use
the openmc.data.DataLibrary
class to register the .h5 files as
described in Manually Creating a Library from ACE files.
The official ENDF/B-VII.1 HDF5 library includes the windowed multipole library, so if you are using this library, the windowed multipole data will already be available to you.
Multi-Group Cross Sections¶
Multi-group cross section libraries are generally tailored to the specific
calculation to be performed. Therefore, at this point in time, OpenMC is not
distributed with any pre-existing multi-group cross section libraries.
However, if obtained or generated their own library, the user
should set the OPENMC_MG_CROSS_SECTIONS
environment variable
to the absolute path of the file library expected to used most frequently.
For an example of how to create a multi-group library, see the example notebook.
Basics of Using OpenMC¶
Running a Model¶
When you build and install OpenMC, you will have an openmc
executable on your system. When you run openmc
, the first thing it will do
is look for a set of XML files that describe the model you want to
simulate. Three of these files are required and another three are optional, as
described below.
Required
- Materials Specification – materials.xml
- This file describes what materials are present in the problem and what they are composed of. Additionally, it indicates where OpenMC should look for a cross section library.
- Geometry Specification – geometry.xml
- This file describes how the materials defined in
materials.xml
occupy regions of space. Physical volumes are defined using constructive solid geometry, described in detail in Defining Geometry. - Settings Specification – settings.xml
- This file indicates what mode OpenMC should be run in, how many particles to simulate, the source definition, and a whole host of miscellaneous options.
Optional
- Tallies Specification – tallies.xml
- This file describes what physical quantities should be tallied during the simulation (fluxes, reaction rates, currents, etc.).
- Geometry Plotting Specification – plots.xml
- This file gives specifications for producing slice or voxel plots of the geometry.
Warning
OpenMC models should be treated as code, and it is important to be careful with code from untrusted sources.
eXtensible Markup Language (XML)¶
Unlike many other Monte Carlo codes which use an arbitrary-format ASCII file with “cards” to specify a particular geometry, materials, and associated run settings, the input files for OpenMC are structured in a set of XML files. XML, which stands for eXtensible Markup Language, is a simple format that allows data to be exchanged efficiently between different programs and interfaces.
Anyone who has ever seen webpages written in HTML will be familiar with the structure of XML whereby “tags” enclosed in angle brackets denote that a particular piece of data will follow. Let us examine the follow example:
<person>
<firstname>John</firstname>
<lastname>Smith</lastname>
<age>27</age>
<occupation>Health Physicist</occupation>
</person>
Here we see that the first tag indicates that the following data will describe a person. The nested tags firstname, lastname, age, and occupation indicate characteristics about the person being described.
In much the same way, OpenMC input uses XML tags to describe the geometry, the materials, and settings for a Monte Carlo simulation. Note that because the XML files have a well-defined structure, they can be validated using the openmc-validate-xml script or using Emacs nXML mode.
Creating Input Files¶
The most rudimentary option for creating input files is to simply write them from scratch using the XML format specifications. This approach will feel familiar to users of other Monte Carlo codes such as MCNP and Serpent, with the added bonus that the XML formats feel much more “readable”. Alternatively, input files can be generated using OpenMC’s Python API, which is introduced in the following section.
Python API¶
OpenMC’s Python API defines a set of functions and classes
that roughly correspond to elements in the XML files. For example, the
openmc.Cell
Python class directly corresponds to the
<cell> Element in XML. Each XML file itself also has a corresponding class:
openmc.Geometry
for geometry.xml
, openmc.Materials
for
materials.xml
, openmc.Settings
for settings.xml
, and so on. To
create a model then, one creates instances of these classes and then uses the
export_to_xml()
method, e.g., Geometry.export_to_xml()
. Most scripts
that generate a full model will look something like the following:
# Create materials
materials = openmc.Materials()
...
materials.export_to_xml()
# Create geometry
geometry = openmc.Geometry()
...
geometry.export_to_xml()
# Assign simulation settings
settings = openmc.Settings()
...
settings.export_to_xml()
Once a model has been created and exported to XML, a simulation can be run either
by calling openmc directly from a shell or by using the
openmc.run()
function from Python.
Identifying Objects¶
In the XML user input files, each object (cell, surface, tally, etc.) has to be uniquely identified by a positive integer (ID) in the same manner as MCNP and Serpent. In the Python API, integer IDs can be assigned but it is not strictly required. When IDs are not explicitly assigned to instances of the OpenMC Python classes, they will be automatically assigned.
Viewing and Analyzing Results¶
After a simulation has been completed by running openmc, you will have several output files that were created:
tallies.out
- An ASCII file showing the mean and standard deviation of the mean for any user-defined tallies.
summary.h5
- An HDF5 file with a complete description of the geometry and materials used in the simulation.
statepoint.#.h5
- An HDF5 file with the complete results of the simulation, including tallies as well as the final source distribution. This file can be used both to view/analyze results as well as restart a simulation if desired.
For a simple simulation with few tallies, looking at the tallies.out
file
might be sufficient. For anything more complicated (plotting results, finding a
subset of results, etc.), you will likely find it easier to work with the
statepoint file directly using the openmc.StatePoint
class. For more
details on working with statepoints, see Working with State Points.
Physical Units¶
Unless specified otherwise, all length quantities are assumed to be in units of centimeters, all energy quantities are assumed to be in electronvolts, and all time quantities are assumed to be in seconds.
Measure | Default unit | Symbol |
---|---|---|
length | centimeter | cm |
energy | electronvolt | eV |
time | second | s |
ERSN-OpenMC Graphical User Interface¶
A third-party Java-based user-friendly graphical user interface for creating XML input files called ERSN-OpenMC is developed and maintained by members of the Radiation and Nuclear Systems Group at the Faculty of Sciences Tetouan, Morocco. The GUI also allows one to automatically download prerequisites for installing and running OpenMC.
Material Compositions¶
Materials in OpenMC are defined as a set of nuclides/elements at specified
densities and are created using the openmc.Material
class. Once a
material has been instantiated, nuclides can be added with
Material.add_nuclide()
and elements can be added with
Material.add_element()
. Densities can be specified using atom fractions or
weight fractions. For example, to create a material and add Gd152 at 0.5 atom
percent, you’d run:
mat = openmc.Material()
mat.add_nuclide('Gd152', 0.5, 'ao')
The third argument to Material.add_nuclide()
can also be ‘wo’ for weight
percent. The densities specified for each nuclide/element are relative and are
renormalized based on the total density of the material. The total density is
set using the Material.set_density()
method. The density can be specified
in gram per cubic centimeter (‘g/cm3’), atom per barn-cm (‘atom/b-cm’), or
kilogram per cubic meter (‘kg/m3’), e.g.,
mat.set_density('g/cm3', 4.5)
Natural Elements¶
The Material.add_element()
method works exactly the same as
Material.add_nuclide()
, except that instead of specifying a single isotope
of an element, you specify the element itself. For example,
mat.add_element('C', 1.0)
This method can also accept case-insensitive element names such as
mat.add_element('aluminium', 1.0)
Internally, OpenMC stores data on the atomic masses and natural abundances of all known isotopes and then uses this data to determine what isotopes should be added to the material. When the material is later exported to XML for use by the openmc executable, you’ll see that any natural elements were expanded to the naturally-occurring isotopes.
The Material.add_element()
method can also be used to add uranium at a
specified enrichment through the enrichment argument. For example, the
following would add 3.2% enriched uranium to a material:
mat.add_element('U', 1.0, enrichment=3.2)
In addition to U235 and U238, concentrations of U234 and U236 will be present and are determined through a correlation based on measured data.
It is also possible to perform enrichment of any element that is composed
of two naturally-occurring isotopes (e.g., Li or B) in terms of atomic percent.
To invoke this, provide the additional argument enrichment_target to
Material.add_element()
. For example the following would enrich B10
to 30ao%:
mat.add_element('B', 1.0, enrichment=30.0, enrichment_target='B10')
In order to enrich an isotope in terms of mass percent (wo%), provide the extra argument enrichment_type. For example the following would enrich Li6 to 15wo%:
mat.add_element('Li', 1.0, enrichment=15.0, enrichment_target='Li6',
enrichment_type='wo')
Often, cross section libraries don’t actually have all naturally-occurring
isotopes for a given element. For example, in ENDF/B-VII.1, cross section
evaluations are given for O16 and O17 but not for O18. If OpenMC is aware of
what cross sections you will be using (through the
OPENMC_CROSS_SECTIONS
environment variable), it will attempt to only
put isotopes in your model for which you have cross section data. In the case of
oxygen in ENDF/B-VII.1, the abundance of O18 would end up being lumped with O16.
Thermal Scattering Data¶
If you have a moderating material in your model like water or graphite, you
should assign thermal scattering data (so-called \(S(\alpha,\beta)\)) using
the Material.add_s_alpha_beta()
method. For example, to model light water,
you would need to add hydrogen and oxygen to a material and then assign the
c_H_in_H2O
thermal scattering data:
water = openmc.Material()
water.add_nuclide('H1', 2.0)
water.add_nuclide('O16', 1.0)
water.add_s_alpha_beta('c_H_in_H2O')
water.set_density('g/cm3', 1.0)
Naming Conventions¶
OpenMC uses the GNDS naming convention for nuclides, metastable states, and compounds:
Nuclides: | SymA where “A” is the mass number (e.g., Fe56 ) |
---|---|
Elements: | Sym0 (e.g., Fe0 or C0 ) |
Excited states: | SymA_eN (e.g., V51_e1 for the first excited state of
Vanadium-51.) This is only used in decay data. |
Metastable states: | |
SymA_mN (e.g., Am242_m1 for the first excited state
of Americium-242). |
|
Compounds: | c_String_Describing_Material (e.g., c_H_in_H2O ). Used for
thermal scattering data. |
Important
The element syntax, e.g., C0
, is only used when the cross
section evaluation is an elemental evaluation, like carbon in
ENDF/B-VII.1! If you are adding an element via
Material.add_element()
, just use Sym
.
Temperature¶
Some Monte Carlo codes define temperature implicitly through the cross section data, which is itself given only at a particular temperature. In OpenMC, the material definition is decoupled from the specification of temperature. Instead, temperatures are assigned to cells directly. Alternatively, a default temperature can be assigned to a material that is to be applied to any cell where the material is used. In the absence of any cell or material temperature specification, a global default temperature can be set that is applied to all cells and materials. Anytime a material temperature is specified, it will override the global default temperature. Similarly, anytime a cell temperatures is specified, it will override the material or global default temperature. All temperatures should be given in units of Kelvin.
To assign a default material temperature, one should use the temperature
attribute, e.g.,
hot_fuel = openmc.Material()
hot_fuel.temperature = 1200.0 # temperature in Kelvin
Warning
MCNP users should be aware that OpenMC does not use the concept of
cross section suffixes like “71c” or “80c”. Temperatures in Kelvin
should be assigned directly per material or per cell using the
Material.temperature
or Cell.temperature
attributes, respectively.
Material Mixtures¶
In OpenMC it is possible to mix any number of materials to create a new material
with the correct nuclide composition and density. The
Material.mix_materials()
method takes a list of materials and
a list of their mixing fractions. Mixing fractions can be provided as atomic
fractions, weight fractions, or volume fractions. The fraction type
can be specified by passing ‘ao’, ‘wo’, or ‘vo’ as the third argument, respectively.
For example, assuming the required materials have already been defined, a MOX
material with 3% plutonium oxide by weight could be created using the following:
mox = openmc.Material.mix_materials([uo2, puo2], [0.97, 0.03], 'wo')
It should be noted that, if mixing fractions are specifed as atomic or weight fractions, the supplied fractions should sum to one. If the fractions are specified as volume fractions, and the sum of the fractions is less than one, then the remaining fraction is set as void material.
Warning
Materials with \(S(\alpha,\beta)\) thermal scattering data
cannot be used in Material.mix_materials()
. However, thermal
scattering data can be added to a material created by
Material.mix_materials()
.
Material Collections¶
The openmc executable expects to find a materials.xml
file
when it is run. To create this file, one needs to instantiate the
openmc.Materials
class and add materials to it. The Materials
class acts like a list (in fact, it is a subclass of Python’s built-in
list
class), so materials can be added by passing a list to the
constructor, using methods like append()
, or through the operator
+=
. Once materials have been added to the collection, it can be exported
using the Materials.export_to_xml()
method.
materials = openmc.Materials()
materials.append(water)
materials += [uo2, zircaloy]
materials.export_to_xml()
# This is equivalent
materials = openmc.Materials([water, uo2, zircaloy])
materials.export_to_xml()
Cross Sections¶
OpenMC uses a file called cross_sections.xml to
indicate where cross section data can be found on the filesystem. This file
serves the same role that xsdir
does for MCNP or xsdata
does for
Serpent. Information on how to generate a cross section listing file can be
found in Manually Creating a Library from ACE files. Once you have a cross sections file that has
been generated, you can tell OpenMC to use this file either by setting
Materials.cross_sections
or by setting the
OPENMC_CROSS_SECTIONS
environment variable to the path of the
cross_sections.xml
file. The former approach would look like:
materials.cross_sections = '/path/to/cross_sections.xml'
Defining Geometry¶
Surfaces and Regions¶
The geometry of a model in OpenMC is defined using constructive solid geometry (CSG), also sometimes referred to as combinatorial geometry. CSG allows a user to create complex regions using Boolean operators (intersection, union, and complement) on simpler regions. In order to define a region that we can assign to a cell, we must first define surfaces which bound the region. A surface is a locus of zeros of a function of Cartesian coordinates \(x,y,z\), e.g.
- A plane perpendicular to the \(x\) axis: \(x - x_0 = 0\)
- A cylinder parallel to the \(z\) axis: \((x - x_0)^2 + (y - y_0)^2 - R^2 = 0\)
- A sphere: \((x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0\)
Defining a surface alone is not sufficient to specify a volume – in order to define an actual volume, one must reference the half-space of a surface. A surface half-space is the region whose points satisfy a positive or negative inequality of the surface equation. For example, for a sphere of radius one centered at the origin, the surface equation is \(f(x,y,z) = x^2 + y^2 + z^2 - 1 = 0\). Thus, we say that the negative half-space of the sphere, is defined as the collection of points satisfying \(f(x,y,z) < 0\), which one can reason is the inside of the sphere. Conversely, the positive half-space of the sphere would correspond to all points outside of the sphere, satisfying \(f(x,y,z) > 0\).
In the Python API, surfaces are created via subclasses of
openmc.Surface
. The available surface types and their corresponding
classes are listed in the following table.
Surface | Equation | Class |
---|---|---|
Plane perpendicular to \(x\)-axis | \(x - x_0 = 0\) | openmc.XPlane |
Plane perpendicular to \(y\)-axis | \(y - y_0 = 0\) | openmc.YPlane |
Plane perpendicular to \(z\)-axis | \(z - z_0 = 0\) | openmc.ZPlane |
Arbitrary plane | \(Ax + By + Cz = D\) | openmc.Plane |
Infinite cylinder parallel to \(x\)-axis | \((y-y_0)^2 + (z-z_0)^2 - R^2 = 0\) | openmc.XCylinder |
Infinite cylinder parallel to \(y\)-axis | \((x-x_0)^2 + (z-z_0)^2 - R^2 = 0\) | openmc.YCylinder |
Infinite cylinder parallel to \(z\)-axis | \((x-x_0)^2 + (y-y_0)^2 - R^2 = 0\) | openmc.ZCylinder |
Sphere | \((x-x_0)^2 + (y-y_0)^2 + (z-z_0)^2 - R^2 = 0\) | openmc.Sphere |
Cone parallel to the \(x\)-axis | \((y-y_0)^2 + (z-z_0)^2 - R^2(x-x_0)^2 = 0\) | openmc.XCone |
Cone parallel to the \(y\)-axis | \((x-x_0)^2 + (z-z_0)^2 - R^2(y-y_0)^2 = 0\) | openmc.YCone |
Cone parallel to the \(z\)-axis | \((x-x_0)^2 + (y-y_0)^2 - R^2(z-z_0)^2 = 0\) | openmc.ZCone |
General quadric surface | \(Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0\) | openmc.Quadric |
Each surface is characterized by several parameters. As one example, the parameters for a sphere are the \(x,y,z\) coordinates of the center of the sphere and the radius of the sphere. All of these parameters can be set either as optional keyword arguments to the class constructor or via attributes:
sphere = openmc.Sphere(r=10.0)
# This is equivalent
sphere = openmc.Sphere()
sphere.r = 10.0
Once a surface has been created, half-spaces can be obtained by applying the
unary -
or +
operators, corresponding to the negative and positive
half-spaces, respectively. For example:
>>> sphere = openmc.Sphere(r=10.0)
>>> inside_sphere = -sphere
>>> outside_sphere = +sphere
>>> type(inside_sphere)
<class 'openmc.surface.Halfspace'>
Instances of openmc.Halfspace
can be combined together using the
Boolean operators &
(intersection), |
(union), and ~
(complement):
>>> inside_sphere = -openmc.Sphere()
>>> above_plane = +openmc.ZPlane()
>>> northern_hemisphere = inside_sphere & above_plane
>>> type(northern_hemisphere)
<class 'openmc.region.Intersection'>
The &
operator can be thought of as a logical AND, the |
operator as a
logical OR, and the ~
operator as a logical NOT. Thus, if you wanted to
create a region that consists of the space for which \(-4 < z < -3\) or
\(3 < z < 4\), a union could be used:
>>> region_bottom = +openmc.ZPlane(-4) & -openmc.ZPlane(-3)
>>> region_top = +openmc.ZPlane(3) & -openmc.ZPlane(4)
>>> combined_region = region_bottom | region_top
Half-spaces and the objects resulting from taking the intersection, union, and/or complement or half-spaces are all considered regions that can be assigned to cells.
For many regions, a bounding-box can be determined automatically:
>>> northern_hemisphere.bounding_box
(array([-1., -1., 0.]), array([1., 1., 1.]))
While a bounding box can be determined for regions involving half-spaces of
spheres, cylinders, and axis-aligned planes, it generally cannot be determined
if the region involves cones, non-axis-aligned planes, or other exotic
second-order surfaces. For example, the openmc.model.hexagonal_prism()
function returns the interior region of a hexagonal prism; because it is bounded
by a openmc.Plane
, trying to get its bounding box won’t work:
>>> hex = openmc.model.hexagonal_prism()
>>> hex.bounding_box
(array([-0.8660254, -inf, -inf]),
array([ 0.8660254, inf, inf]))
Boundary Conditions¶
When a surface is created, by default particles that pass through the surface
will consider it to be transmissive, i.e., they pass through the surface
freely. If your model does not extend to infinity in all spatial dimensions, you
may want to specify different behavior for particles passing through a
surface. To specify a vacuum boundary condition, simply change the
Surface.boundary_type
attribute to ‘vacuum’:
outer_surface = openmc.Sphere(r=100.0, boundary_type='vacuum')
# This is equivalent
outer_surface = openmc.Sphere(r=100.0)
outer_surface.boundary_type = 'vacuum'
Reflective and periodic boundary conditions can be set with the strings
‘reflective’ and ‘periodic’. Vacuum and reflective boundary conditions can be
applied to any type of surface. Periodic boundary conditions can be applied to
pairs of planar surfaces. If there are only two periodic surfaces they will be
matched automatically. Otherwise it is necessary to specify pairs explicitly
using the Surface.periodic_surface
attribute as in the following
example:
p1 = openmc.Plane(a=0.3, b=5.0, d=1.0, boundary_type='periodic')
p2 = openmc.Plane(a=0.3, b=5.0, d=-1.0, boundary_type='periodic')
p1.periodic_surface = p2
Both rotational and translational periodic boundary conditions are specified in the same fashion. If both planes have the same normal vector, a translational periodicity is assumed; rotational periodicity is assumed otherwise. Currently, only rotations about the \(z\)-axis are supported.
For a rotational periodic BC, the normal vectors of each surface must point
inwards—towards the valid geometry. For example, a XPlane
and
YPlane
would be valid for a 90-degree periodic rotation if the geometry
lies in the first quadrant of the Cartesian grid. If the geometry instead lies
in the fourth quadrant, the YPlane
must be replaced by a
Plane
with the normal vector pointing in the \(-y\) direction.
Cells¶
Once you have a material created and a region of space defined, you need to
define a cell that assigns the material to the region. Cells are created using
the openmc.Cell
class:
fuel = openmc.Cell(fill=uo2, region=pellet)
# This is equivalent
fuel = openmc.Cell()
fuel.fill = uo2
fuel.region = pellet
In this example, an instance of openmc.Material
is assigned to the
Cell.fill
attribute. One can also fill a cell with a universe or lattice. If you provide
no fill to a cell or assign a value of None, it will be treated as a “void”
cell with no material within. Particles are allowed to stream through the cell but
will undergo no collisions:
# This cell will be filled with void on export to XML
gap = openmc.Cell(region=pellet_gap)
The classes Halfspace
, Intersection
, Union
, and
Complement
and all instances of openmc.Region
and can be
assigned to the Cell.region
attribute.
Universes¶
Similar to MCNP and Serpent, OpenMC is capable of using universes, collections
of cells that can be used as repeatable units of geometry. At a minimum, there
must be one “root” universe present in the model. To define a universe, an
instance of openmc.Universe
is created and then cells can be added
using the Universe.add_cells()
or Universe.add_cell()
methods. Alternatively, a list of cells can be specified in the constructor:
universe = openmc.Universe(cells=[cell1, cell2, cell3])
# This is equivalent
universe = openmc.Universe()
universe.add_cells([cell1, cell2])
universe.add_cell(cell3)
Universes are generally used in three ways:
- To be assigned to a
Geometry
object (see Exporting a Geometry Model), - To be assigned as the fill for a cell via the
Cell.fill
attribute, and - To be used in a regular arrangement of universes in a lattice.
Once a universe is constructed, it can actually be used to determine what cell
or material is found at a given location by using the Universe.find()
method, which returns a list of universes, cells, and lattices which are
traversed to find a given point. The last element of that list would contain the
lowest-level cell at that location:
>>> universe.find((0., 0., 0.))[-1]
Cell
ID = 10000
Name = cell 1
Fill = Material 10000
Region = -10000
Rotation = None
Temperature = None
Translation = None
As you are building a geometry, it is also possible to display a plot of single
universe using the Universe.plot()
method. This method requires that you
have matplotlib installed.
Lattices¶
Many particle transport models involve repeated structures that occur in a
regular pattern such as a rectangular or hexagonal lattice. In such a case, it
would be cumbersome to have to define the boundaries of each of the cells to be
filled with a universe. OpenMC provides a means to define lattice structures
through the openmc.RectLattice
and openmc.HexLattice
classes.
Rectangular Lattices¶
A rectangular lattice defines a two-dimensional or three-dimensional array of universes that are filled into rectangular prisms (lattice elements) each of which has the same width, length, and height. To completely define a rectangular lattice, one needs to specify
- The coordinates of the lower-left corner of the lattice
(
RectLattice.lower_left
), - The pitch of the lattice, i.e., the distance between the center of adjacent
lattice elements (
RectLattice.pitch
), - What universes should fill each lattice element
(
RectLattice.universes
), and - A universe that is used to fill any lattice position outside the well-defined
portion of the lattice (
RectLattice.outer
).
For example, to create a 3x3 lattice centered at the origin in which each
lattice element is 5cm by 5cm and is filled by a universe u
, one could run:
lattice = openmc.RectLattice()
lattice.lower_left = (-7.5, -7.5)
lattice.pitch = (5.0, 5.0)
lattice.universes = [[u, u, u],
[u, u, u],
[u, u, u]]
Note that because this is a two-dimensional lattice, the lower-left coordinates
and pitch only need to specify the \(x,y\) values. The order that the
universes appear is such that the first row corresponds to lattice elements with
the highest \(y\) -value. Note that the RectLattice.universes
attribute expects a doubly-nested iterable of type openmc.Universe
—
this can be normal Python lists, as shown above, or a NumPy array can be used as
well:
lattice.universes = np.tile(u, (3, 3))
For a three-dimensional lattice, the \(x,y,z\) coordinates of the lower-left
coordinate need to be given and the pitch should also give dimensions for all
three axes. For example, to make a 3x3x3 lattice where the bottom layer is
universe u
, the middle layer is universe q
and the top layer is universe
z
would look like:
lat3d = openmc.RectLattice()
lat3d.lower_left = (-7.5, -7.5, -7.5)
lat3d.pitch = (5.0, 5.0, 5.0)
lat3d.universes = [
[[u, u, u],
[u, u, u],
[u, u, u]],
[[q, q, q],
[q, q, q],
[q, q, q]],
[[z, z, z],
[z, z, z]
[z, z, z]]]
Again, using NumPy can make things easier:
lat3d.universes = np.empty((3, 3, 3), dtype=openmc.Universe)
lat3d.universes[0, ...] = u
lat3d.universes[1, ...] = q
lat3d.universes[2, ...] = z
Finally, it’s possible to specify that lattice positions that aren’t normally
without the bounds of the lattice be filled with an “outer” universe. This
allows one to create a truly infinite lattice if desired. An outer universe is
set with the RectLattice.outer
attribute.
Hexagonal Lattices¶
OpenMC also allows creation of 2D and 3D hexagonal lattices. Creating a hexagonal lattice is similar to creating a rectangular lattice with a few differences:
- The center of the lattice must be specified (
HexLattice.center
). - For a 2D hexagonal lattice, a single value for the pitch should be specified, although it still needs to appear in a list. For a 3D hexagonal lattice, the pitch in the radial and axial directions should be given.
- For a hexagonal lattice, the
HexLattice.universes
attribute cannot be given as a NumPy array for reasons explained below. - As with rectangular lattices, the
HexLattice.outer
attribute will specify an outer universe.
For a 2D hexagonal lattice, the HexLattice.universes
attribute should be
set to a two-dimensional list of universes filling each lattice element. Each
sub-list corresponds to one ring of universes and is ordered from the outermost
ring to the innermost ring. The universes within each sub-list are ordered from
the “top” (position with greatest y value) and proceed in a clockwise fashion
around the ring. The HexLattice.show_indices()
static method can be used
to help figure out how to place universes:
>>> print(openmc.HexLattice.show_indices(3))
(0, 0)
(0,11) (0, 1)
(0,10) (1, 0) (0, 2)
(1, 5) (1, 1)
(0, 9) (2, 0) (0, 3)
(1, 4) (1, 2)
(0, 8) (1, 3) (0, 4)
(0, 7) (0, 5)
(0, 6)
Note that by default, hexagonal lattices are positioned such that each lattice
element has two faces that are parallel to the \(y\) axis. As one example,
to create a three-ring lattice centered at the origin with a pitch of 10 cm
where all the lattice elements centered along the \(y\) axis are filled with
universe u
and the remainder are filled with universe q
, the following
code would work:
hexlat = openmc.HexLattice()
hexlat.center = (0, 0)
hexlat.pitch = [10]
outer_ring = [u, q, q, q, q, q, u, q, q, q, q, q]
middle_ring = [u, q, q, u, q, q]
inner_ring = [u]
hexlat.universes = [outer_ring, middle_ring, inner_ring]
If you need to create a hexagonal boundary (composed of six planar surfaces) for
a hexagonal lattice, openmc.model.hexagonal_prism()
can be used.
Exporting a Geometry Model¶
Once you have finished building your geometry by creating surfaces, cell, and,
if needed, lattices, the last step is to create an instance of
openmc.Geometry
and export it to an XML file that the
openmc executable can read using the
Geometry.export_to_xml()
method. This can be done as follows:
geom = openmc.Geometry(root_univ)
geom.export_to_xml()
# This is equivalent
geom = openmc.Geometry()
geom.root_universe = root_univ
geom.export_to_xml()
Note that it’s not strictly required to manually create a root universe. You can
also pass a list of cells to the openmc.Geometry
constructor and it
will handle creating the unverse:
geom = openmc.Geometry([cell1, cell2, cell3])
geom.export_to_xml()
Using CAD-based Geometry¶
OpenMC relies on the Direct Accelerated Geometry Monte Carlo toolkit (DAGMC) to represent CAD-based geometry in a
surface mesh format. A DAGMC run can be enabled in OpenMC by setting the
dagmc
property to True
in the model Settings either via the Python
openmc.settings
Python class:
settings = openmc.Settings()
settings.dagmc = True
or in the settings.xml file:
<dagmc>true</dagmc>
With dagmc
set to true, OpenMC will load the DAGMC model (from a local file
named dagmc.h5m
) when initializing a simulation. If a geometry.xml is present as well, it will be ignored.
Note: DAGMC geometries used in OpenMC are currently required to be clean, meaning that all surfaces have been imprinted and merged successfully and that the model is watertight. Future implementations of DAGMC geometry will support small volume overlaps and un-merged surfaces.
Calculating Atoms Content¶
If the total volume occupied by all instances of a cell in the geometry is known by the user, it is possible to assign this volume to a cell without performing a stochastic volume calculation:
from uncertainties import ufloat
# Set known total volume in [cc]
cell = openmc.Cell()
cell.volume = 17.0
# Set volume if it is known with some uncertainty
cell.volume = ufloat(17.0, 0.1)
Once a volume is set, and a cell is filled with a material or distributed
materials, it is possible to use the atoms()
method to obtain
a dictionary of nuclides and their total number of atoms in all instances
of a cell (e.g. {'H1': 1.0e22, 'O16': 0.5e22, ...}
):
cell = openmc.Cell(fill = u02)
cell.volume = 17.0
O16_atoms = cell.atoms['O16']
Execution Settings¶
Once you have created the materials and geometry for your simulation, the last
step to have a complete model is to specify execution settings through the
openmc.Settings
class. At a minimum, you need to specify a source
distribution and how many particles to run. Many other execution settings can be set using the
openmc.Settings
object, but they are generally optional.
Run Modes¶
The Settings.run_mode
attribute controls what run mode is used when
openmc is executed. There are five different run modes that can
be specified:
- ‘eigenvalue’
- Runs a \(k\) eigenvalue simulation. See Eigenvalue Calculations for a
full description of eigenvalue calculations. In this mode, the
Settings.source
specifies a starting source that is only used for the first fission generation. - ‘fixed source’
- Runs a fixed-source calculation with a specified external source, specified in
the
Settings.source
attribute. - ‘volume’
- Runs a stochastic volume calculation.
- ‘plot’
- Generates slice or voxel plots (see Geometry Visualization).
- ‘particle restart’
- Simulate a single source particle using a particle restart file.
So, for example, to specify that OpenMC should be run in fixed source mode, you
would need to instantiate a openmc.Settings
object and assign the
Settings.run_mode
attribute:
settings = openmc.Settings()
settings.run_mode = 'fixed source'
If you don’t specify a run mode, the default run mode is ‘eigenvalue’.
Run Strategy¶
For a fixed source simulation, the total number of source particle histories
simulated is broken up into a number of batches, each corresponding to a
realization of the tally random variables. Thus, you
need to specify both the number of batches (Settings.batches
) as well as
the number of particles per batch (Settings.particles
).
For a \(k\) eigenvalue simulation, particles are grouped into fission
generations, as described in Eigenvalue Calculations. Successive fission
generations can be combined into a batch for statistical purposes. By default, a
batch will consist of only a single fission generation, but this can be changed
with the Settings.generations_per_batch
attribute. For problems with a
high dominance ratio, using multiple generations per batch can help reduce
underprediction of variance, thereby leading to more accurate confidence
intervals. Tallies should not be scored to until the source distribution
converges, as described in Method of Successive Generations, which may take
many generations. To specify the number of batches that should be discarded
before tallies begin to accumulate, use the Settings.inactive
attribute.
The following example shows how one would simulate 10000 particles per generation, using 10 generations per batch, 150 total batches, and discarding 5 batches. Thus, a total of 145 active batches (or 1450 generations) will be used for accumulating tallies.
settings.particles = 10000
settings.generations_per_batch = 10
settings.batches = 150
settings.inactive = 5
Number of Batches¶
In general, the stochastic uncertainty in your simulation results is directly related to how many total active particles are simulated (the product of the number of active batches, number of generations per batch, and number of particles). At a minimum, you should use enough active batches so that the central limit theorem is satisfied (about 30). Otherwise, reducing the overall uncertainty in your simulation by a factor of 2 will require using 4 times as many batches (since the standard deviation decreases as \(1/\sqrt{N}\)).
Number of Inactive Batches¶
For \(k\) eigenvalue simulations, the source distribution is not known a priori. Thus, a “guess” of the source distribution is made and then iterated on, with the source evolving closer to the true distribution at each iteration. Once the source distribution has converged, it is then safe to start accumulating tallies. Consequently, a preset number of inactive batches are run before the active batches (where tallies are turned on) begin. The number of inactive batches necessary to reach a converged source depends on the spatial extent of the problem, its dominance ratio, what boundary conditions are used, and many other factors. For small problems, using 50–100 inactive batches is likely sufficient. For larger models, many hundreds of inactive batches may be necessary. Users are recommended to use the Shannon entropy diagnostic as a way of determining how many inactive batches are necessary.
Specifying the initial source used for the very first batch is described in below. Although the initial source is arbitrary in the sense that any source will eventually converge to the correct distribution, using a source guess that is closer to the actual converged source distribution will translate into needing fewer inactive batches (and hence less simulation time).
For fixed source simulations, the source distribution is known exactly, so no
inactive batches are needed. In this case the Settings.inactive
attribute can be omitted since it defaults to zero.
Number of Generations per Batch¶
The standard deviation of tally results is calculated assuming that all realizations (batches) are independent. However, in a \(k\) eigenvalue calculation, the source sites for each batch are produced from fissions in the preceding batch, resulting in a correlation between successive batches. This correlation can result in an underprediction of the variance. That is, the variance reported is actually less than the true variance. To mitigate this effect, OpenMC allows you to group together multiple fission generations into a single batch for statistical purposes, rather than having each fission generation be a separate batch, which is the default behavior.
Number of Particles per Generation¶
There are several considerations for choosing the number of particles per generation. As discussed in Number of Batches, the total number of active particles will determine the level of stochastic uncertainty in simulation results, so using a higher number of particles will result in less uncertainty. For parallel simulations that use OpenMP and/or MPI, the number of particles per generation should be large enough to ensure good load balancing between threads. For example, if you are running on a single processor with 32 cores, each core should have at least 100 particles or so (i.e., at least 3,200 particles per generation should be used). Using a larger number of particles per generation can also help reduce the cost of synchronization and communication between batches. For \(k\) eigenvalue calculations, experts recommend at least 10,000 particles per generation to avoid any bias in the estimate of \(k\) eigenvalue or tallies.
External Source Distributions¶
External source distributions can be specified through the
Settings.source
attribute. If you have a single external source, you can
create an instance of openmc.Source
and use it to set the
Settings.source
attribute. If you have multiple external sources with
varying source strengths, Settings.source
should be set to a list of
openmc.Source
objects.
The openmc.Source
class has three main attributes that one can set:
Source.space
, which defines the spatial distribution,
Source.angle
, which defines the angular distribution, and
Source.energy
, which defines the energy distribution.
The spatial distribution can be set equal to a sub-class of
openmc.stats.Spatial
; common choices are openmc.stats.Point
or
openmc.stats.Box
. To independently specify distributions in the
\(x\), \(y\), and \(z\) coordinates, you can use
openmc.stats.CartesianIndependent
. To independently specify
distributions using spherical or cylindrical coordinates, you can use
openmc.stats.SphericalIndependent
or
openmc.stats.CylindricalIndependent
, respectively.
The angular distribution can be set equal to a sub-class of
openmc.stats.UnitSphere
such as openmc.stats.Isotropic
,
openmc.stats.Monodirectional
, or openmc.stats.PolarAzimuthal
.
By default, if no angular distribution is specified, an isotropic angular
distribution is used. As an example of a non-trivial angular distribution, the
following code would create a conical distribution with an aperture of 30
degrees pointed in the positive x direction:
from math import pi, cos
aperture = 30.0
mu = openmc.stats.Uniform(cos(aperture/2), 1.0)
phi = openmc.stats.Uniform(0.0, 2*pi)
angle = openmc.stats.PolarAzimuthal(mu, phi, reference_uvw=(1., 0., 0.))
The energy distribution can be set equal to any univariate probability
distribution. This could be a probability mass function
(openmc.stats.Discrete
), a Watt fission spectrum
(openmc.stats.Watt
), or a tabular distribution
(openmc.stats.Tabular
). By default, if no energy distribution is
specified, a Watt fission spectrum with \(a\) = 0.988 MeV and \(b\) =
2.249 MeV -1 is used.
As an example, to create an isotropic, 10 MeV monoenergetic source uniformly distributed over a cube centered at the origin with an edge length of 10 cm, one would run:
source = openmc.Source()
source.space = openmc.stats.Box((-5, -5, -5), (5, 5, 5))
source.angle = openmc.stats.Isotropic()
source.energy = openmc.stats.Discrete([10.0e6], [1.0])
settings.source = source
The openmc.Source
class also has a Source.strength
attribute
that indicates the relative strength of a source distribution if multiple are
used. For example, to create two sources, one that should be sampled 70% of the
time and another that should be sampled 30% of the time:
src1 = openmc.Source()
src1.strength = 0.7
...
src2 = openmc.Source()
src2.strength = 0.3
...
settings.source = [src1, src2]
Finally, the Source.particle
attribute can be used to indicate the
source should be composed of particles other than neutrons. For example, the
following would generate a photon source:
source = openmc.Source()
source.particle = 'photon'
...
settings.source = source
For a full list of all classes related to statistical distributions, see openmc.stats – Statistics.
File-based Sources¶
OpenMC can use a pregenerated HDF5 source file by specifying the filename
argument to openmc.Source
:
settings.source = openmc.Source(filename='source.h5')
Statepoint and source files are generated automatically when a simulation is run
and can be used as the starting source in a new simulation. Alternatively, a
source file can be manually generated with the openmc.write_source_file()
function. This is particularly useful for coupling OpenMC with another program
that generates a source to be used in OpenMC.
A source file based on particles that cross one or more surfaces can be
generated during a simulation using the Settings.surf_source_write
attribute:
settings.surf_source_write = {
'surfaces_ids': [1, 2, 3],
'max_particles': 10000
}
In this example, at most 10,000 source particles are stored when particles cross surfaces with IDs of 1, 2, or 3.
Custom Sources¶
It is often the case that one may wish to simulate a complex source distribution that is not possible to represent with the classes described above. For these situations, it is possible to define a complex source class containing an externally defined source function that is loaded at runtime. A simple example source is shown below.
#include <memory> // for unique_ptr
#include "openmc/random_lcg.h"
#include "openmc/source.h"
#include "openmc/particle.h"
class CustomSource : public openmc::Source
{
openmc::SourceSite sample(uint64_t* seed) const
{
openmc::SourceSite particle;
// weight
particle.particle = openmc::ParticleType::neutron;
particle.wgt = 1.0;
// position
double angle = 2.0 * M_PI * openmc::prn(seed);
double radius = 3.0;
particle.r.x = radius * std::cos(angle);
particle.r.y = radius * std::sin(angle);
particle.r.z = 0.0;
// angle
particle.u = {1.0, 0.0, 0.0};
particle.E = 14.08e6;
particle.delayed_group = 0;
return particle;
}
};
extern "C" std::unique_ptr<CustomSource> openmc_create_source(std::string parameters)
{
return std::make_unique<CustomSource>();
}
The above source creates monodirectional 14.08 MeV neutrons that are distributed in a ring with a 3 cm radius. This routine is not particularly complex, but should serve as an example upon which to build more complicated sources.
Note
The source class must inherit from
openmc::Source
and implement asample()
function.Note
The
openmc_create_source()
function signature must be declaredextern "C"
.Note
You should only use the
openmc::prn()
random number generator.
In order to build your external source, you will need to link it against the OpenMC shared library. This can be done by writing a CMakeLists.txt file:
cmake_minimum_required(VERSION 3.3 FATAL_ERROR)
project(openmc_sources CXX)
add_library(source SHARED source_ring.cpp)
find_package(OpenMC REQUIRED HINTS <path to openmc>)
target_link_libraries(source OpenMC::libopenmc)
After running cmake
and make
, you will have a libsource.so (or .dylib)
file in your build directory. Setting the openmc.Source.library
attribute to the path of this shared library will indicate that it should be
used for sampling source particles at runtime.
Custom Parameterized Sources¶
Some custom sources may have values (parameters) that can be changed between
runs. This is supported by using the openmc_create_source()
function to
pass parameters defined in the openmc.Source.parameters
attribute to
the source class when it is created:
#include <memory> // for unique_ptr
#include "openmc/source.h"
#include "openmc/particle.h"
class CustomSource : public openmc::Source {
public:
CustomSource(double energy) : energy_{energy} { }
// Samples from an instance of this class.
openmc::SourceSite sample(uint64_t* seed) const
{
openmc::SourceSite particle;
// weight
particle.particle = openmc::ParticleType::neutron;
particle.wgt = 1.0;
// position
particle.r.x = 0.0;
particle.r.y = 0.0;
particle.r.z = 0.0;
// angle
particle.u = {1.0, 0.0, 0.0};
particle.E = this->energy_;
particle.delayed_group = 0;
return particle;
}
private:
double energy_;
};
extern "C" std::unique_ptr<CustomSource> openmc_create_source(std::string parameter) {
double energy = std::stod(parameter);
return std::make_unique<CustomSource>(energy);
}
As with the basic custom source functionality, the custom source library
location must be provided in the openmc.Source.library
attribute.
Shannon Entropy¶
To assess convergence of the source distribution, the scalar Shannon entropy
metric is often used in Monte Carlo codes. OpenMC also allows you to calculate
Shannon entropy at each generation over a specified mesh, created using the
openmc.RegularMesh
class. After instantiating a RegularMesh
,
you need to specify the lower-left coordinates of the mesh
(RegularMesh.lower_left
), the number of mesh cells in each direction
(RegularMesh.dimension
) and either the upper-right coordinates of the
mesh (RegularMesh.upper_right
) or the width of each mesh cell
(RegularMesh.width
). Once you have a mesh, simply assign it to the
Settings.entropy_mesh
attribute.
entropy_mesh = openmc.RegularMesh()
entropy_mesh.lower_left = (-50, -50, -25)
entropy_mesh.upper_right = (50, 50, 25)
entropy_mesh.dimension = (8, 8, 8)
settings.entropy_mesh = entropy_mesh
If you’re unsure of what bounds to use for the entropy mesh, you can try getting
a bounding box for the entire geometry using the Geometry.bounding_box
property:
geom = openmc.Geometry()
...
m = openmc.RegularMesh()
m.lower_left, m.upper_right = geom.bounding_box
m.dimension = (8, 8, 8)
settings.entropy_mesh = m
Photon Transport¶
In addition to neutrons, OpenMC is also capable of simulating the passage of
photons through matter. This allows the modeling of photon production from
neutrons as well as pure photon calculations. The
Settings.photon_transport
attribute can be used to enable photon
transport:
settings.photon_transport = True
The way in which OpenMC handles secondary charged particles can be specified
with the Settings.electron_treatment
attribute. By default, the
thick-target bremsstrahlung (TTB) approximation is used to generate
bremsstrahlung radiation emitted by electrons and positrons created in photon
interactions. To neglect secondary bremsstrahlung photons and instead deposit
all energy from electrons locally, the local energy deposition option can be
selected:
settings.electron_treatment = 'led'
Note
Some features related to photon transport are not currently implemented, including:
- Tallying photon energy deposition.
- Generating a photon source from a neutron calculation that can be used for a later fixed source photon calculation.
- Photoneutron reactions.
Generation of Output Files¶
A number of attributes of the openmc.Settings
class can be used to
control what files are output and how often. First, there is the
Settings.output
attribute which takes a dictionary having keys
‘summary’, ‘tallies’, and ‘path’. The first two keys controls whether a
summary.h5
and tallies.out
file are written, respectively (see
Viewing and Analyzing Results for a description of those files). By default, output files
are written to the current working directory; this can be changed by setting the
‘path’ key. For example, if you want to disable the tallies.out
file and
write the summary.h5
to a directory called ‘results’, you’d specify the
Settings.output
dictionary as:
settings.output = {
'tallies': False,
'path': 'results'
}
Generation of statepoint and source files is handled separately through the
Settings.statepoint
and Settings.sourcepoint
attributes. Both of
those attributes expect dictionaries and have a ‘batches’ key which indicates at
which batches statepoints and source files should be written. Note that by
default, the source is written as part of the statepoint file; this behavior can
be changed by the ‘separate’ and ‘write’ keys of the
Settings.sourcepoint
dictionary, the first of which indicates whether
the source should be written to a separate file and the second of which
indicates whether the source should be written at all.
As an example, to write a statepoint file every five batches:
settings.batches = n
settings.statepoint = {'batches': range(5, n + 5, 5)}
Specifying Tallies¶
In order to obtain estimates of physical quantities in your simulation, you need
to create one or more tallies using the openmc.Tally
class. As
explained in detail in the theory manual, tallies
provide estimates of a scoring function times the flux integrated over some
region of phase space, as in:
Thus, to specify a tally, we need to specify what regions of phase space should be included when deciding whether to score an event as well as what the scoring function (\(f\) in the above equation) should be used. The regions of phase space are generally called filters and the scoring functions are simply called scores.
The only cases when filters do not correspond directly with the regions of phase space are when expansion functions are applied in the integrand, such as for Legendre expansions of the scattering kernel.
Filters¶
To specify the regions of phase space, one must create a
openmc.Filter
. Since openmc.Filter
is an abstract class, you
actually need to instantiate one of its sub-classes (for a full listing, see
Constructing Tallies). For example, to indicate that events that occur in a
given cell should score to the tally, we would create a
openmc.CellFilter
:
cell_filter = openmc.CellFilter([fuel.id, moderator.id, reflector.id])
Another commonly used filter is openmc.EnergyFilter
, which specifies
multiple energy bins over which events should be scored. Thus, if we wanted to
tally events where the incident particle has an energy in the ranges [0 eV, 4
eV] and [4 eV, 1 MeV], we would do the following:
energy_filter = openmc.EnergyFilter([0.0, 4.0, 1.0e6])
Energies are specified in eV and need to be monotonically increasing.
Caution
An energy bin between zero and the lowest energy specified is not included by default as it is in MCNP.
Once you have created a filter, it should be assigned to a openmc.Tally
instance through the Tally.filters
attribute:
tally.filters.append(cell_filter)
tally.filters.append(energy_filter)
# This is equivalent
tally.filters = [cell_filter, energy_filter]
Note
You are actually not required to assign any filters to a tally. If you create a tally with no filters, all events will score to the tally. This can be useful if you want to know, for example, a reaction rate over your entire model.
Scores¶
To specify the scoring functions, a list of strings needs to be given to the
Tally.scores
attribute. You can score the flux (‘flux’), or a reaction
rate (‘total’, ‘fission’, etc.). For example, to tally the elastic scattering
rate and the fission neutron production, you’d assign:
tally.scores = ['elastic', 'nu-fission']
With no further specification, you will get the total elastic scattering rate
and the total fission neutron production. If you want reaction rates for a
particular nuclide or set of nuclides, you can set the Tally.nuclides
attribute to a list of strings indicating which nuclides. The nuclide names
should follow the same naming convention as that used
for material specification. If we wanted the reaction rates only for U235 and
U238 (separately), we’d set:
tally.nuclides = ['U235', 'U238']
You can also list ‘all’ as a nuclide which will give you a separate reaction rate for every nuclide in the model.
The following tables show all valid scores:
Score | Description |
---|---|
flux | Total flux. |
Score | Description |
---|---|
absorption | Total absorption rate. For incident neutrons, this accounts for all reactions that do not produce secondary neutrons as well as fission. For incident photons, this includes photoelectric and pair production. |
elastic | Elastic scattering reaction rate. |
fission | Total fission reaction rate. |
scatter | Total scattering rate. |
total | Total reaction rate. |
(n,2nd) | (n,2nd) reaction rate. |
(n,2n) | (n,2n) reaction rate. |
(n,3n) | (n,3n) reaction rate. |
(n,na) | (n,n\(\alpha\)) reaction rate. |
(n,n3a) | (n,n3\(\alpha\)) reaction rate. |
(n,2na) | (n,2n\(\alpha\)) reaction rate. |
(n,3na) | (n,3n\(\alpha\)) reaction rate. |
(n,np) | (n,np) reaction rate. |
(n,n2a) | (n,n2\(\alpha\)) reaction rate. |
(n,2n2a) | (n,2n2\(\alpha\)) reaction rate. |
(n,nd) | (n,nd) reaction rate. |
(n,nt) | (n,nt) reaction rate. |
(n,n3He) | (n,n3He) reaction rate. |
(n,nd2a) | (n,nd2\(\alpha\)) reaction rate. |
(n,nt2a) | (n,nt2\(\alpha\)) reaction rate. |
(n,4n) | (n,4n) reaction rate. |
(n,2np) | (n,2np) reaction rate. |
(n,3np) | (n,3np) reaction rate. |
(n,n2p) | (n,n2p) reaction rate. |
(n,n*X*) | Level inelastic scattering reaction rate. The X indicates what which inelastic level, e.g., (n,n3) is third-level inelastic scattering. |
(n,nc) | Continuum level inelastic scattering reaction rate. |
(n,gamma) | Radiative capture reaction rate. |
(n,p) | (n,p) reaction rate. |
(n,d) | (n,d) reaction rate. |
(n,t) | (n,t) reaction rate. |
(n,3He) | (n,3He) reaction rate. |
(n,a) | (n,\(\alpha\)) reaction rate. |
(n,2a) | (n,2\(\alpha\)) reaction rate. |
(n,3a) | (n,3\(\alpha\)) reaction rate. |
(n,2p) | (n,2p) reaction rate. |
(n,pa) | (n,p\(\alpha\)) reaction rate. |
(n,t2a) | (n,t2\(\alpha\)) reaction rate. |
(n,d2a) | (n,d2\(\alpha\)) reaction rate. |
(n,pd) | (n,pd) reaction rate. |
(n,pt) | (n,pt) reaction rate. |
(n,da) | (n,d\(\alpha\)) reaction rate. |
coherent-scatter | Coherent (Rayleigh) scattering reaction rate. |
incoherent-scatter | Incoherent (Compton) scattering reaction rate. |
photoelectric | Photoelectric absorption reaction rate. |
pair-production | Pair production reaction rate. |
Arbitrary integer | An arbitrary integer is interpreted to mean the reaction rate for a reaction with a given ENDF MT number. |
Score | Description |
---|---|
delayed-nu-fission | Total production of delayed neutrons due to fission. |
prompt-nu-fission | Total production of prompt neutrons due to fission. |
nu-fission | Total production of neutrons due to fission. |
nu-scatter | This score is similar in functionality to the
scatter score except the total production of
neutrons due to scattering is scored vice simply
the scattering rate. This accounts for
multiplicity from (n,2n), (n,3n), and (n,4n)
reactions. |
H1-production | Total production of H1. |
H2-production | Total production of H2 (deuterium). |
H3-production | Total production of H3 (tritium). |
He3-production | Total production of He3. |
He4-production | Total production of He4 (alpha particles). |
Score | Description |
---|---|
current | Used in combination with a meshsurface filter: Partial currents on the boundaries of each cell in a mesh. It may not be used in conjunction with any other score. Only energy and mesh filters may be used. Used in combination with a surface filter: Net currents on any surface previously defined in the geometry. It may be used along with any other filter, except meshsurface filters. Surfaces can alternatively be defined with cell from and cell filters thereby resulting in tallying partial currents. Units are particles per source particle. |
events | Number of scoring events. Units are events per source particle. |
inverse-velocity | The flux-weighted inverse velocity where the velocity is in units of centimeters per second. |
heating | Total nuclear heating in units of eV per source particle. For neutrons, this corresponds to MT=301 produced by NJOY’s HEATR module while for photons, this is tallied from either direct photon energy deposition (analog estimator) or pre-generated photon heating number. See Heating and Energy Deposition |
heating-local | Total nuclear heating in units of eV per source particle assuming energy from secondary photons is deposited locally. Note that this score should only be used for incident neutrons. See Heating and Energy Deposition. |
kappa-fission | The recoverable energy production rate due to fission. The recoverable energy is defined as the fission product kinetic energy, prompt and delayed neutron kinetic energies, prompt and delayed \(\gamma\)-ray total energies, and the total energy released by the delayed \(\beta\) particles. The neutrino energy does not contribute to this response. The prompt and delayed \(\gamma\)-rays are assumed to deposit their energy locally. Units are eV per source particle. |
fission-q-prompt | The prompt fission energy production rate. This energy comes in the form of fission fragment nuclei, prompt neutrons, and prompt \(\gamma\)-rays. This value depends on the incident energy and it requires that the nuclear data library contains the optional fission energy release data. Energy is assumed to be deposited locally. Units are eV per source particle. |
fission-q-recoverable | The recoverable fission energy production rate. This energy comes in the form of fission fragment nuclei, prompt and delayed neutrons, prompt and delayed \(\gamma\)-rays, and delayed \(\beta\)-rays. This tally differs from the kappa-fission tally in that it is dependent on incident neutron energy and it requires that the nuclear data library contains the optional fission energy release data. Energy is assumed to be deposited locally. Units are eV per source paticle. |
decay-rate | The delayed-nu-fission-weighted decay rate where the decay rate is in units of inverse seconds. |
damage-energy | Damage energy production in units of eV per source particle. This corresponds to MT=444 produced by NJOY’s HEATR module. |
Normalization of Tally Results¶
As described in Scores, all tally scores are normalized per
source particle simulated. However, for analysis of a given system, we usually
want tally scores in a more natural unit. For example, neutron flux is often
reported in units of particles/cm2-s. For a fixed source simulation,
it is usually straightforward to convert units if the source rate is known. For
example, if the system being modeled includes a source that is emitting 104 neutrons per second, the tally results just need to be multipled by 104. This can either be done manually or using the
openmc.Source.strength
attribute.
For a \(k\)-eigenvalue calculation, normalizing tally results is not as
simple because the source rate is not actually known. Instead, we typically know
the system power, \(P\), which represents how much energy is deposited per
unit time. Most of this energy originates from fission, but a small percentage
also results from other reactions (e.g., photons emitted from \((n,\gamma)\)
reactions). The most rigorous method to normalize tally results is to run a
coupled neutron-photon calculation and tally the heating
score over the
entire system. This score provides the heating rate in units of [eV/source],
which we’ll denote \(H\). Then, calculate the heating rate in J/source as
Dividing the power by the observed heating rate then gives us a normalization factor that can be applied to other tallies:
Multiplying by the normalization factor and dividing by volume, we can then get the flux in typical units:
There are several slight variations on this procedure:
- Run a neutron-only calculation and estimate the total heating using the
heating-local
score (this requires that your nuclear data has local heating data available, such as in the official data library at https://openmc.org. See Heating and Energy Deposition for more information.) - Run a neutron-only calculation and use the
kappa-fission
orfission-q-recoverable
scores along with an estimate of the extra heating due to neutron capture reactions. - Calculate the overall fission rate and then used a fixed Q value to estimate the heating rate.
Note that the only difference between these and the above procedures is in how \(H'\) is estimated.
Geometry Visualization¶
OpenMC is capable of producing two-dimensional slice plots of a geometry as well
as three-dimensional voxel plots using the geometry plotting run mode. The geometry plotting mode relies on the presence of a
plots.xml file that indicates what plots should be created. To
create this file, one needs to create one or more openmc.Plot
instances, add them to a openmc.Plots
collection, and then use the
Plots.export_to_xml
method to write the plots.xml
file.
Slice Plots¶

By default, when an instance of openmc.Plot
is created, it indicates
that a 2D slice plot should be made. You can specify the origin of the plot
(Plot.origin
), the width of the plot in each direction
(Plot.width
), the number of pixels to use in each direction
(Plot.pixels
), and the basis directions for the plot. For example, to
create a \(x\) - \(z\) plot centered at (5.0, 2.0, 3.0) with a width of
(50., 50.) and 400x400 pixels:
plot = openmc.Plot()
plot.basis = 'xz'
plot.origin = (5.0, 2.0, 3.0)
plot.width = (50., 50.)
plot.pixels = (400, 400)
The color of each pixel is determined by placing a particle at the center of
that pixel and using OpenMC’s internal find_cell
routine (the same one used
for particle tracking during simulation) to determine the cell and material at
that location.
Note
In this example, pixels are 50/400=0.125 cm wide. Thus, this plot may miss any features smaller than 0.125 cm, since they could exist between pixel centers. More pixels can be used to resolve finer features but will result in larger files.
By default, a unique color will be assigned to each cell in the geometry. If you
want your plot to be colored by material instead, change the
Plot.color_by
attribute:
plot.color_by = 'material'
If you don’t like the random colors assigned, you can also indicate that particular cells/materials should be given colors of your choosing:
plot.colors = {
water: 'blue',
clad: 'black'
}
# This is equivalent
plot.colors = {
water: (0, 0, 255),
clad: (0, 0, 0)
}
Note that colors can be given as RGB tuples or by a string indicating a valid SVG color.
When you’re done creating your openmc.Plot
instances, you need to then
assign them to a openmc.Plots
collection and export it to XML:
plots = openmc.Plots([plot1, plot2, plot3])
plots.export_to_xml()
# This is equivalent
plots = openmc.Plots()
plots.append(plot1)
plots += [plot2, plot3]
plots.export_to_xml()
To actually generate the plots, run the openmc.plot_geometry()
function. Alternatively, run the openmc executable with the
--plot
command-line flag. When that has finished, you will have one or more
.ppm
files, i.e., portable pixmap files. On some Linux
distributions, these .ppm
files are natively viewable. If you find that
you’re unable to open them on your system (or you don’t like the fact that they
are not compressed), you may want to consider converting them to another format.
This is easily accomplished with the convert
command available on most Linux
distributions as part of the ImageMagick package. (On Debian
derivatives: sudo apt install imagemagick
). Images are then converted like:
convert myplot.ppm myplot.png
Alternatively, if you’re working within a Jupyter
Notebook or QtConsole, you can use the openmc.plot_inline()
to run OpenMC
in plotting mode and display the resulting plot within the notebook.
Voxel Plots¶

The openmc.Plot
class can also be told to generate a 3D voxel plot
instead of a 2D slice plot. Simply change the Plot.type
attribute to
‘voxel’. In this case, the Plot.width
and Plot.pixels
attributes
should be three items long, e.g.:
vox_plot = openmc.Plot()
vox_plot.type = 'voxel'
vox_plot.width = (100., 100., 50.)
vox_plot.pixels = (400, 400, 200)
The voxel plot data is written to an HDF5 file. The voxel file can subsequently be converted into a standard mesh format that can be viewed in ParaView, VisIt, etc. This typically will compress the size of the file significantly. The provided openmc-voxel-to-vtk script can convert the HDF5 voxel file to VTK formats. Once processed into a standard 3D file format, colors and masks can be defined using the stored ID numbers to better explore the geometry. The process for doing this will depend on the 3D viewer, but should be straightforward.
Note
3D voxel plotting can be very computer intensive for the viewing program (Visit, ParaView, etc.) if the number of voxels is large (>10 million or so). Thus if you want an accurate picture that renders smoothly, consider using only one voxel in a certain direction.
Depletion and Transmutation¶
OpenMC supports coupled depletion, or burnup, calculations through the
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.
The depletion module is designed such that the flux/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
openmc.deplete
module offers a single transport operator,
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 openmc.Geometry
instance and a
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 Material.depletable
attribute.
Important
The volume must be specified for each material that is depleted by
setting the Material.volume
attribute. This is necessary
in order to calculate the proper normalization of tally results
based on the source rate.
openmc.deplete
supports multiple time-integration methods for determining
material compositions over time. Each method appears as a different class.
For example, 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 openmc.deplete.Operator
is passed to
one of these functions 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
depletion_results.h5
file is written. The results can be analyzed using the
openmc.deplete.ResultsList
class. This class has methods that allow for
easy retrieval of k-effective, nuclide concentrations, and reaction rates over
time:
results = openmc.deplete.ResultsList.from_hdf5("depletion_results.h5")
time, keff = results.get_eigenvalue()
Note that the coupling between the transport solver and the transmutation solver happens in-memory rather than by reading/writing files on disk.
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,
and normalization of tally results is performed based on energy deposition. It
is also possible to run a fixed-source simulation and perform normalization
based on a known source rate. First, as with all fixed-source calculations, we
need to set the run mode:
settings.run_mode = 'fixed source'
Additionally, all materials that you wish to deplete need to be marked as such
using the Material.depletable
attribute:
mat = openmc.Material()
mat.depletable = True
When constructing the Operator
, 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')
Finally, when creating a depletion integrator, use the source_rates
argument:
integrator = openmc.deplete.PredictorIntegrator(op, timesteps, sources_rates=...)
As with the power
argument, you can provide a different source rate for each
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¶
The default energy deposition mode, "fission-q"
, instructs the
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.
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 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 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")
# alternatively, pass the modified fission Q directly to the operator
op = openmc.deplete.Operator(geometry, setting, "chain.xml",
fission_q=fission_q)
A more complete way to model the energy deposition is to use the modified heating reactions described in Heating and Energy Deposition. 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",
normalization_mode="energy-deposition")
These modified heating libraries can be generated by running the latest version
of 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
creating a single 3.5 wt.% enriched fuel fuel_3
, and placing that fuel in
every fuel pin in an assembly or even full core problem. This certainly
expedites the model making process, but can pose issues with depletion. Under
this setup, openmc.deplete
will deplete a single fuel_3
material
using a single set of reaction rates, and produce a single new composition for
the next time step. This can be problematic if the same fuel_3
is used in
very different regions of the problem.
As an example, consider a full-scale power reactor core with vacuum boundary
conditions, and with fuel pins solely composed of the same fuel_3
material.
The fuel pins towards the center of the problem will surely experience a more
intense neutron flux and greater reaction rates than those towards the edge of
the domain. This indicates that the fuel in the center should be at a more
depleted state than periphery pins, at least for the fist depletion step.
However, without any other instructions, OpenMC will deplete fuel_3
as a
single material, and all of the fuel pins will have an identical composition at
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,
diff_burnable_mats=True)
For our example problem, this would deplete fuel on the outer region of the
problem with different reaction rates than those in the center. Materials will
be depleted corresponding to their local neutron spectra, and have unique
compositions at each transport step. The volume of the original fuel_3
material must represent the volume of all the fuel_3
in the problem.
When creating the unique materials, this volume will be equally distributed
across all material instances.
Note
This will increase the total memory usage and run time due to an increased number of tallies and material definitions.
Executables and Scripts¶
openmc
¶
Once you have a model built (see Basics of Using OpenMC), you can either run the openmc executable directly from the directory containing your XML input files, or you can specify as a command-line argument the directory containing the XML input files.
Warning
OpenMC models should be treated as code, and it is important to be careful with code from untrusted sources.
For example, if your XML input files are in the directory
/home/username/somemodel/
, one way to run the simulation would be:
cd /home/username/somemodel
openmc
Alternatively, you could run from any directory:
openmc /home/username/somemodel
Note that in the latter case, any output files will be placed in the present
working directory which may be different from
/home/username/somemodel
. openmc
accepts the following command line
flags:
-c, --volume | Run in stochastic volume calculation mode |
-e, --event | Run using event-based parallelism |
-g, --geometry-debug | |
Run in geometry debugging mode, where cell overlaps are checked for after each move of a particle | |
-n, --particles N | |
Use N particles per generation or batch | |
-p, --plot | Run in plotting mode |
-r, --restart file | |
Restart a previous run from a state point or a particle restart file | |
-s, --threads N | |
Run with N OpenMP threads | |
-t, --track | Write tracks for all particles |
-v, --version | Show version information |
-h, --help | Show help message |
Note
If you’re using the Python API, openmc.run()
is equivalent to
running openmc
from the command line.
openmc-ace-to-hdf5
¶
This script can be used to create HDF5 nuclear data libraries used by OpenMC if you have existing ACE files. There are four different ways you can specify ACE libraries that are to be converted:
- List each ACE library as a positional argument. This is very useful in
conjunction with the usual shell utilities (
ls
,find
, etc.). - Use the
--xml
option to specify a pre-v0.9 cross_sections.xml file. - Use the
--xsdir
option to specify a MCNP xsdir file. - Use the
--xsdata
option to specify a Serpent xsdata file.
The script does not use any extra information from cross_sections.xml/ xsdir/
xsdata files to determine whether the nuclide is metastable. Instead, the
--metastable
argument can be used to specify whether the ZAID naming convention
follows the NNDC data convention (1000*Z + A + 300 + 100*m), or the MCNP data
convention (essentially the same as NNDC, except that the first metastable state
of Am242 is 95242 and the ground state is 95642).
The optional --fission_energy_release
argument will accept an HDF5 file
containing a library of fission energy release (ENDF MF=1 MT=458) data. A
library built from ENDF/B-VII.1 data is released with OpenMC and can be found at
openmc/data/fission_Q_data_endb71.h5. This data is necessary for
‘fission-q-prompt’ and ‘fission-q-recoverable’ tallies, but is not needed
otherwise.
-h, --help | show help message and exit |
-d DESTINATION, --destination DESTINATION | |
Directory to create new library in | |
-m META, --metastable META | |
How to interpret ZAIDs for metastable nuclides. META can be either ‘nndc’ or ‘mcnp’. (default: nndc) | |
--xml XML | Old-style cross_sections.xml that lists ACE libraries |
--xsdir XSDIR | MCNP xsdir file that lists ACE libraries |
--xsdata XSDATA | |
Serpent xsdata file that lists ACE libraries | |
--fission_energy_release FISSION_ENERGY_RELEASE | |
HDF5 file containing fission energy release data |
openmc-plot-mesh-tally
¶
openmc-plot-mesh-tally
provides a graphical user interface for plotting mesh
tallies. The path to the statepoint file can be provided as an optional arugment
(if omitted, a file dialog will be presented).
openmc-track-to-vtk
¶
This script converts HDF5 particle track files to VTK
poly data that can be viewed with ParaView or VisIt. The filenames of the
particle track files should be given as posititional arguments. The output
filename can also be changed with the -o
flag:
-o OUT, --out OUT | |
Output VTK poly filename |
openmc-update-inputs
¶
If you have existing XML files that worked in a previous version of OpenMC that
no longer work with the current version, you can try to update these files using
openmc-update-inputs
. If any of the given files do not match the most
up-to-date formatting, then they will be automatically rewritten. The old
out-of-date files will not be deleted; they will be moved to a new file with
‘.original’ appended to their name.
Formatting changes that will be made:
- geometry.xml
- Lattices containing ‘outside’ attributes/tags will be replaced with lattices containing ‘outer’ attributes, and the appropriate cells/universes will be added. Any ‘surfaces’ attributes/elements on a cell will be renamed ‘region’.
- materials.xml
- Nuclide names will be changed from ACE aliases (e.g., Am-242m) to HDF5/GND names (e.g., Am242_m1). Thermal scattering table names will be changed from ACE aliases (e.g., HH2O) to HDF5/GND names (e.g., c_H_in_H2O).
openmc-update-mgxs
¶
This script updates OpenMC’s deprecated multi-group cross section XML files to the latest HDF5-based format.
-i IN, --input IN | |
Input XML file | |
-o OUT, --output OUT | |
Output file in HDF5 format |
openmc-validate-xml
¶
Input files can be checked before executing OpenMC using the
openmc-validate-xml
script which is installed alongside the Python API. Two
command line arguments can be set when running openmc-validate-xml
:
-i, --input-path | |
Location of OpenMC input files. | |
-r, --relaxng-path | |
Location of OpenMC RelaxNG files |
If the RelaxNG path is not set, the script will search for these files because
it expects that the user is either running the script located in the install
directory bin
folder or in src/utils
. Once executed, it will match
OpenMC XML files with their RelaxNG schema and check if they are valid. Below
is a table of the messages that will be printed after each file is checked.
Message | Description |
---|---|
[XML ERROR] | Cannot parse XML file. |
[NO RELAXNG FOUND] | No RelaxNG file found for XML file. |
[NOT VALID] | XML file does not match RelaxNG. |
[VALID] | XML file matches RelaxNG. |
openmc-voxel-to-vtk
¶
When OpenMC generates voxel plots, they are in an
HDF5 format that is not terribly useful by itself. The
openmc-voxel-to-vtk
script converts a voxel HDF5 file to a VTK file. To run this script, you will need to have the VTK
Python bindings installed. To convert a voxel file, simply provide the path to
the file:
openmc-voxel-to-vtk voxel_1.h5
The openmc-voxel-to-vtk
script also takes the following optional
command-line arguments:
-o, --output | Path to output VTK file |
Data Processing and Visualization¶
This section is intended to explain procedures for carrying out common post-processing tasks with OpenMC. While several utilities of varying complexity are provided to help automate the process, the most powerful capabilities for post-processing derive from use of the Python API.
Working with State Points¶
Tally results are saved in both a text file (tallies.out) as well as an HDF5 statepoint file. While the tallies.out file may be fine for simple tallies, in many cases the user requires more information about the tally or the run, or has to deal with a large number of result values (e.g. for mesh tallies). In these cases, extracting data from the statepoint file via the Python API is the preferred method of data analysis and visualization.
Data Extraction¶
A great deal of information is available in statepoint files (See
State Point File Format), all of which is accessible through the Python
API. The openmc.StatePoint
class can load statepoints and access data
as requested; it is used in many of the provided plotting utilities, OpenMC’s
regression test suite, and can be used in user-created scripts to carry out
manipulations of the data.
An example notebook demonstrates how to extract data from a statepoint using the Python API.
Plotting in 2D¶
The notebook example also demonstrates how to plot a structured mesh tally in two dimensions using the Python API. One can also use the openmc-plot-mesh-tally script which provides an interactive GUI to explore and plot structured mesh tallies for any scores and filter bins.

Getting Data into MATLAB¶
There is currently no front-end utility to dump tally data to MATLAB files, but
the process is straightforward. First extract the data using the Python API via
openmc.statepoint
and then use the Scipy MATLAB IO routines to save to a MAT
file. Note that all arrays that are accessible in a statepoint are already in
NumPy arrays that can be reshaped and dumped to MATLAB in one step.
Particle Track Visualization¶

OpenMC can dump particle tracks—the position of particles as they are transported through the geometry. There are two ways to make OpenMC output tracks: all particle tracks through a command line argument or specific particle tracks through settings.xml.
Running openmc with the argument -t
or --track
will cause
a track file to be created for every particle transported in the code. Be
careful as this will produce as many files as there are source particles in your
simulation. To identify a specific particle for which a track should be created,
set the Settings.track
attribute to a tuple containing the batch,
generation, and particle number of the desired particle. For example, to create
a track file for particle 4 of batch 1 and generation 2:
settings = openmc.Settings()
settings.track = (1, 2, 4)
To specify multiple particles, the length of the iterable should be a multiple of three, e.g., if we wanted particles 3 and 4 from batch 1 and generation 2:
settings.track = (1, 2, 3, 1, 2, 4)
After running OpenMC, the working directory will contain a file of the form “track_(batch #)_(generation #)_(particle #).h5” for each particle tracked. These track files can be converted into VTK poly data files with the openmc-track-to-vtk script.
Source Site Processing¶
For eigenvalue problems, OpenMC will store information on the fission source
sites in the statepoint file by default. For each source site, the weight,
position, sampled direction, and sampled energy are stored. To extract this data
from a statepoint file, the openmc.statepoint
module can be used. An
example notebook demontrates how to
analyze and plot source information.
Running in Parallel¶
If you are running a simulation on a computer with multiple cores, multiple sockets, or multiple nodes (i.e., a cluster), you can benefit from the fact that OpenMC is able to use all available hardware resources if configured correctly. OpenMC is capable of using both distributed-memory (MPI) and shared-memory (OpenMP) parallelism. If you are on a single-socket workstation or a laptop, using shared-memory parallelism is likely sufficient. On a multi-socket node, cluster, or supercomputer, chances are you will need to use both distributed-memory (across nodes) and shared-memory (within a single node) parallelism.
Distributed-Memory Parallelism (MPI)¶
MPI defines a library specification for message-passing between processes. There are two major implementations of MPI, OpenMPI and MPICH. Both implementations are known to work with OpenMC; there is no obvious reason to prefer one over the other. Building OpenMC with support for MPI requires that you have one of these implementations installed on your system. For instructions on obtaining MPI, see Prerequisites. Once you have an MPI implementation installed, compile OpenMC following Compiling with MPI.
To run a simulation using MPI, openmc needs to be called using the mpiexec wrapper. For example, to run OpenMC using 32 processes:
mpiexec -n 32 openmc
The same thing can be achieved from the Python API by supplying the mpi_args
argument to openmc.run()
:
openmc.run(mpi_args=['mpiexec', '-n', '32'])
Maximizing Performance¶
There are a number of things you can do to ensure that you obtain optimal performance on a machine when running in parallel:
Use OpenMP within each NUMA node. Some large server processors have so many cores that the last level cache is split to reduce memory latency. For example, the Intel Xeon Haswell-EP architecture uses a snoop mode called cluster on die where the L3 cache is split in half. Thus, in general, you should use one MPI process per socket (and OpenMP within each socket), but for these large processors, you will want to go one step further and use one process per NUMA node. The Xeon Phi Knights Landing architecture uses a similar concept called sub NUMA clustering.
Use a sufficiently large number of particles per generation. Between fission generations, a number of synchronization tasks take place. If the number of particles per generation is too low and you are using many processes/threads, the synchronization time may become non-negligible.
Use hardware threading if available.
Use process binding. When running with MPI, you should ensure that processes are bound to a specific hardware region. This can be set using the
-bind-to
(MPICH) or--bind-to
(OpenMPI) option tompiexec
.Turn off generation of tallies.out. For large simulations with millions of tally bins or more, generating this ASCII file might consume considerable time. You can turn off generation of
tallies.out
via theSettings.output
attribute:settings = openmc.Settings() settings.output = {'tallies': False}
Stochastic Volume Calculations¶
OpenMC has a capability to stochastically determine volumes of cells, materials, and universes. The method works by overlaying a bounding box, sampling points from within the box, and seeing what fraction of points were found in a desired domain. The benefit of doing this stochastically (as opposed to equally-spaced points), is that it is possible to give reliable error estimates on each stochastic quantity.
To specify that a volume calculation be run, you first need to create an
instance of openmc.VolumeCalculation
. The constructor takes a list of
cells, materials, or universes; the number of samples to be used; and the
lower-left and upper-right Cartesian coordinates of a bounding box that encloses
the specified domains:
lower_left = (-0.62, -0.62, -50.)
upper_right = (0.62, 0.62, 50.)
vol_calc = openmc.VolumeCalculation([fuel, clad, moderator], 1000000,
lower_left, upper_right)
For domains contained within regions that have simple definitions, OpenMC can sometimes automatically determine a bounding box. In this case, the last two arguments are not necessary. For example,
sphere = openmc.Sphere(r=10.0)
cell = openm.Cell(region=-sphere)
vol_calc = openmc.VolumeCalculation([cell], 1000000)
Of course, the volumes that you need this capability for are often the ones with complex definitions.
A threshold can be applied for the calculation’s variance, standard deviation,
or relative error of volume estimates using openmc.VolumeCalculation.set_trigger()
:
vol_calc.set_trigger(1e-05, 'std_dev')
If a threshold is provided, calculations will be performed iteratively using the number of samples specified on the calculation until all volume uncertainties are below the threshold value. If no threshold is provided, the calculation will run the number of samples specified once and return the result.
Once you have one or more openmc.VolumeCalculation
objects created, you
can then assign then to Settings.volume_calculations
:
settings = openmc.Settings()
settings.volume_calculations = [cell_vol_calc, mat_vol_calc]
To execute the volume calculations, one can either set Settings.run_mode
to ‘volume’ and run openmc.run()
, or alternatively run
openmc.calculate_volumes()
which doesn’t require that
Settings.run_mode
be set.
When your volume calculations have finished, you can load the results using the
VolumeCalculation.load_results()
method on an existing object. If you
don’t have an existing VolumeCalculation
object, you can create one and
load results simultaneously using the VolumeCalculation.from_hdf5()
class
method:
vol_calc = openmc.VolumeCalculation(...)
...
openmc.calculate_volumes()
vol_calc.load_results('volume_1.h5')
# ..or we can create a new object
vol_calc = openmc.VolumeCalculation.from_hdf5('volume_1.h5')
After the results are loaded, volume estimates will be stored in
VolumeCalculation.volumes
. There is also a
VolumeCalculation.atoms_dataframe
attribute that shows stochastic
estimates of the number of atoms of each type of nuclide within the specified
domains along with their uncertainties.
Troubleshooting¶
Problems with Compilation¶
If you are experiencing problems trying to compile OpenMC, first check if the error you are receiving is among the following options.
Problems with Simulations¶
Segmentation Fault¶
A segmentation fault occurs when the program tries to access a variable in memory that was outside the memory allocated for the program. The best way to debug a segmentation fault is to re-compile OpenMC with debug options turned on. Create a new build directory and type the following commands:
mkdir build-debug && cd build-debug
cmake -Ddebug=on /path/to/openmc
make
Now when you re-run your problem, it should report exactly where the program failed. If after reading the debug output, you are still unsure why the program failed, post a message on the OpenMC Discourse Forum.
ERROR: No cross_sections.xml file was specified in settings.xml or in the OPENMC_CROSS_SECTIONS environment variable.¶
OpenMC needs to know where to find cross section data for each nuclide.
Information on what data is available and in what files is summarized in a
cross_sections.xml file. You need to tell OpenMC where to find the
cross_sections.xml file either with the <cross_sections> Element in settings.xml or
with the OPENMC_CROSS_SECTIONS
environment variable. It is recommended
to add a line in your .profile
or .bash_profile
setting the
OPENMC_CROSS_SECTIONS
environment variable.
Geometry Debugging¶
Overlapping Cells¶
For fast run times, normal simulations do not check if the geometry is incorrectly defined to have overlapping cells. This can lead to incorrect results that may or may not be obvious when there are errors in the geometry input file. The built-in 2D and 3D plotters will check for cell overlaps at the center of every pixel or voxel position they process, however this might not be a sufficient check to ensure correctly defined geometry. For instance, if an overlap is of small aspect ratio, the plotting resolution might not be high enough to produce any pixels in the overlapping area.
To reliably validate a geometry input, it is best to run the problem in
geometry debugging mode with the -g
, -geometry-debug
, or
--geometry-debug
command-line options. This will enable checks for
overlapping cells at every move of esch simulated particle. Depending on the
complexity of the geometry input file, this could add considerable overhead to
the run (these runs can still be done in parallel). As a result, for this run
mode the user will probably want to run fewer particles than a normal
simulation run. In this case it is important to be aware of how much coverage
each area of the geometry is getting. For instance, if certain regions do not
have many particles travelling through them there will not be many locations
where overlaps are checked for in that region. The user should refer to the
output after a geometry debug run to see how many checks were performed in each
cell, and then adjust the number of starting particles or starting source
distributions accordingly to achieve good coverage.
ERROR: After particle __ crossed surface __ it could not be located in any cell and it did not leak.¶
This error can arise either if a problem is specified with no boundary conditions or if there is an error in the geometry itself. First check to ensure that all of the outer surfaces of your geometry have been given vacuum or reflective boundary conditions. If proper boundary conditions have been applied and you still receive this error, it means that a surface/cell/lattice in your geometry has been specified incorrectly or is missing.
The best way to debug this error is to turn on a trace for the particle getting lost. After the error message, the code will display what batch, generation, and particle number caused the error. In your settings.xml, add a <trace> Element followed by the batch, generation, and particle number. This will give you detailed output every time that particle enters a cell, crosses a boundary, or has a collision. For example, if you received this error at cycle 5, generation 1, particle 4032, you would enter:
<trace>5 1 4032</trace>
For large runs it is often advantageous to run only the offending particle by
using particle restart mode with the -r
command-line option in conjunction
with the particle restart files that are created when particles are lost with
this error.
Developer’s Guide¶
Welcome to the OpenMC Developer’s Guide! This guide documents how contributions are made to OpenMC, what style rules exist for the code, how to run tests, and other related topics.
Contributing to OpenMC¶
Thank you for considering contributing to OpenMC! We look forward to welcoming new members to the community and will do our best to help you get up to speed. The purpose of this section is to document how the project is managed: how contributions (bug fixes, enhancements, new features) are made, how they are evaluated, who is permitted to merge pull requests, and what happens in the event of disagreements. Once you have read through this section, the Development Workflow section outlines the actual mechanics of making a contribution (forking, submitting a pull request, etc.).
The goal of our governance model is to:
- Encourage new contributions.
- Encourage contributors to remain involved.
- Avoid unnecessary processes and bureaucracy whenever possible.
- Create a transparent decision making process which makes it clear how contributors can be involved in decision making.
Overview¶
OpenMC uses a liberal contribution model for project governance. Anyone involved in development in a non-trivial capacity is given an opportunity to influence the direction of the project. Project decisions are made through a consensus-seeking process rather than by voting.
Terminology¶
- A Contributor is any individual creating or commenting on an issue or pull request.
- A Committer is a subset of contributors who are authorized to review and merge pull requests.
- The TC (Technical Committee) is a group of committers who have the authority to make decisions on behalf of the project team in order to resolve disputes.
- The Project Lead is a single individual who has the authority to make a final decision when the TC is unable to reach consensus.
Contribution Process¶
Any change to the OpenMC repository must be made through a pull request (PR). This applies to all changes to documentation, code, binary files, etc. Even long term committers and TC members must use pull requests.
No pull request may be merged without being independently reviewed.
For non-trivial contributions, pull requests should not be merged for at least 36 hours to ensure that contributors in other timezones have time to review. Consideration should be given to weekends and other holiday periods to ensure active committers have reasonable time to become involved in the discussion and review process if they wish. Any committer may request that the review period be extended if they are unable to review the change within 36 hours.
During review, a committer may request that a specific contributor who is most versed in a particular area review the PR before it can be merged.
A pull request can be merged by any committer, but only if no objections are raised by any other committer. In the case of an objection being raised, all involved committers should seek consensus through discussion and compromise.
In the case of an objection being raised in a pull request by another committer, all involved committers should seek to arrive at a consensus by way of addressing concerns being expressed through discussion, compromise on the proposed change, or withdrawal of the proposed change.
If objections to a PR are made and committers cannot reach a consensus on how to proceed, the decision is escalated to the TC. TC members should regularly discuss pending contributions in order to find a resolution. It is expected that only a small minority of issues be brought to the TC for resolution and that discussion and compromise among committers be the default resolution mechanism.
Becoming a Committer¶
All contributors who make a non-trivial contribution will be added as a committer in a timely manner. Committers are expected to follow this policy.
TC Process¶
Any issues brought to the TC will be addressed among the committee with a consensus-seeking process. The group tries to find a resolution that has no objections among TC members. If a consensus cannot be reached, the Project Lead has the ultimate authority to make a final decision. It is expected that the majority of decisions made by the TC are via a consensus seeking process and that the Project Lead intercedes only as a last resort.
Resolution may involve returning the issue to committers with suggestions on how to move forward towards a consensus.
Members can be added to the TC at any time. Any committer can nominate another committer to the TC and the TC uses its standard consensus seeking process to evaluate whether or not to add this new member. Members who do not participate consistently at the level of a majority of the other members are expected to resign.
In the event that the Project Lead resigns or otherwise steps down, the TC uses a consensus seeking process to choose a new Project Lead.
Next Steps¶
If you are interested in working on a specific feature or helping to address outstanding issues, consider joining the developer’s mailing list and/or Slack community. Note that some issues have specifically been labeled as good for first-time contributors. Once you’re at the point of writing code, make sure your read through the Development Workflow section to understand the mechanics of making pull requests and what is expected during code reviews.
Development Workflow¶
Anyone wishing to make contributions to OpenMC should be fully acquianted and comfortable working with git and GitHub. We assume here that you have git installed on your system, have a GitHub account, and have setup SSH keys to be able to create/push to repositories on GitHub.
Overview¶
Development of OpenMC relies heavily on branching; specifically, we use a branching model sometimes referred to as git flow. If you plan to contribute to OpenMC development, we highly recommend that you read the linked blog post to get a sense of how the branching model works. There are two main branches that always exist: master and develop. The master branch is a stable branch that contains the latest release of the code. The develop branch is where any ongoing development takes place prior to a release and is not guaranteed to be stable. When the development team decides that a release should occur, the develop branch is merged into master.
All new features, enhancements, and bug fixes should be developed on a branch that branches off of develop. When the feature is completed, a pull request is initiated on GitHub that is then reviewed by a committer. If the pull request is satisfactory, it is then merged into develop. Note that a committer may not review their own pull request (i.e., an independent code review is required).
Code Review Criteria¶
In order to be considered suitable for inclusion in the develop branch, the following criteria must be satisfied for all proposed changes:
- Changes have a clear purpose and are useful.
- Compiles and passes all tests under multiple build configurations (This is checked by Travis CI).
- If appropriate, test cases are added to regression or unit test suites.
- No memory leaks (checked with valgrind).
- Conforms to the OpenMC style guide.
- No degradation of performance or greatly increased memory usage. This is not a hard rule – in certain circumstances, a performance loss might be acceptable if there are compelling reasons.
- New features/input are documented.
- No unnecessary external software dependencies are introduced.
Contributing¶
Now that you understand the basic development workflow, let’s discuss how an individual can contribute to development. Note that this would apply to both new features and bug fixes. The general steps for contributing are as follows:
Fork the main openmc repository from openmc-dev/openmc. This will create a repository with the same name under your personal account. As such, you can commit to it as you please without disrupting other developers.
Clone your fork of OpenMC and create a branch that branches off of develop:
git clone --recurse-submodules git@github.com:yourusername/openmc.git cd openmc git checkout -b newbranch develop
Make your changes on the new branch that you intend to have included in develop. If you have made other changes that should not be merged back, ensure that those changes are made on a different branch.
Issue a pull request from GitHub and select the develop branch of openmc-dev/openmc as the target.
At a minimum, you should describe what the changes you’ve made are and why you are making them. If the changes are related to an oustanding issue, make sure it is cross-referenced.
A committer will review your pull request based on the criteria above. Any issues with the pull request can be discussed directly on the pull request page itself.
After the pull request has been thoroughly vetted, it is merged back into the develop branch of openmc-dev/openmc.
Private Development¶
While the process above depends on the fork of the OpenMC repository being publicly available on GitHub, you may also wish to do development on a private repository for research or commercial purposes. The proper way to do this is to create a complete copy of the OpenMC repository (not a fork from GitHub). The private repository can then either be stored just locally or in conjunction with a private repository on Github (this requires a paid plan). Alternatively, Bitbucket offers private repositories for free. If you want to merge some changes you’ve made in your private repository back to openmc-dev/openmc repository, simply follow the steps above with an extra step of pulling a branch from your private repository into a public fork.
Working in “Development” Mode¶
If you are making changes to the Python API during development, it is highly suggested to install the Python API in development/editable mode using pip. From the root directory of the OpenMC repository, run:
pip install -e .[test]
This installs the OpenMC Python package in “editable” mode so
that 1) it can be imported from a Python interpreter and 2) any changes made are
immediately reflected in the installed version (that is, you don’t need to keep
reinstalling it). While the same effect can be achieved using the
PYTHONPATH
environment variable, this is generally discouraged as it
can interfere with virtual environments.
Style Guide for OpenMC¶
In order to keep the OpenMC code base consistent in style, this guide specifies a number of rules which should be adhered to when modified existing code or adding new code in OpenMC.
C++¶
Important
To ensure consistent styling with little effort, this project
uses clang-format. The
repository contains a .clang-format
file that can be used to
automatically apply the style rules that are described below. The easiest
way to use clang-format is through a plugin/extension for your editor/IDE
that automatically runs clang-format using the .clang-format
file
whenever a file is saved.
Indentation¶
Use two spaces per indentation level.
Miscellaneous¶
Follow the C++ Core Guidelines except when they conflict with another guideline listed here. For convenience, many important guidelines from that list are repeated here.
Conform to the C++14 standard.
Always use C++-style comments (//
) as opposed to C-style (/**/
). (It
is more difficult to comment out a large section of code that uses C-style
comments.)
Do not use C-style casting. Always use the C++-style casts static_cast
,
const_cast
, or reinterpret_cast
. (See ES.49)
Source Files¶
Use a .cpp
suffix for code files and .h
for header files.
Header files should always use include guards with the following style (See SF.8):
#ifndef OPENMC_MODULE_NAME_H
#define OPENMC_MODULE_NAME_H
namespace openmc {
...
content
...
}
#endif // OPENMC_MODULE_NAME_H
Avoid hidden dependencies by always including a related header file first, followed by C/C++ library includes, other library includes, and then local includes. For example:
// foo.cpp
#include "foo.h"
#include <cstddef>
#include <iostream>
#include <vector>
#include "hdf5.h"
#include "pugixml.hpp"
#include "error.h"
#include "random_lcg.h"
Naming¶
Struct and class names should be CamelCase, e.g. HexLattice
.
Functions (including member functions) should be lower-case with underscores,
e.g. get_indices
.
Local variables, global variables, and struct/class member variables should be
lower-case with underscores (e.g., n_cells
) except for physics symbols that
are written differently by convention (e.g., E
for energy). Data members of
classes (but not structs) additionally have trailing underscores (e.g.,
a_class_member_
).
The following conventions are used for variables with short names:
d
stands for “distance”E
stands for “energy”p
stands for “particle”r
stands for “position”rx
stands for “reaction”u
stands for “direction”xs
stands for “cross section”
All classes and non-member functions should be declared within the openmc
namespace. Global variables must be declared in a namespace nested within the
openmc
namespace. The following sub-namespaces are in use:
openmc::data
: Fundamental nuclear data (cross sections, multigroup data, decay constants, etc.)openmc::model
: Variables related to geometry, materials, and talliesopenmc::settings
: Global settings / optionsopenmc::simulation
: Variables used only during a simulation
Accessors and mutators (get and set functions) may be named like
variables. These often correspond to actual member variables, but this is not
required. For example, int count()
and void set_count(int count)
.
Variables declared constexpr or const that have static storage duration (exist
for the duration of the program) should be upper-case with underscores,
e.g., SQRT_PI
.
Use C++-style declarator layout (see NL.18): pointer and reference operators in declarations should be placed adject to the base type rather than the variable name. Avoid declaring multiple names in a single declaration to avoid confusion:
T* p; // good
T& p; // good
T *p; // bad
T* p, q; // misleading
Curly braces¶
For a class declaration, the opening brace should be on the same line that lists the name of the class.
class Matrix {
...
};
For a function definition, the opening and closing braces should each be on their own lines. This helps distinguish function code from the argument list. If the entire function fits on one or two lines, then the braces can be on the same line. e.g.:
return_type function(type1 arg1, type2 arg2)
{
content();
}
return_type
function_with_many_args(type1 arg1, type2 arg2, type3 arg3,
type4 arg4)
{
content();
}
int return_one() {return 1;}
int return_one()
{return 1;}
For a conditional, the opening brace should be on the same line as the end of
the conditional statement. If there is a following else if
or else
statement, the closing brace should be on the same line as that following
statement. Otherwise, the closing brace should be on its own line. A one-line
conditional can have the closing brace on the same line or it can omit the
braces entirely e.g.:
if (condition) {
content();
}
if (condition1) {
content();
} else if (condition 2) {
more_content();
} else {
further_content();
}
if (condition) {content()};
if (condition) content();
For loops similarly have an opening brace on the same line as the statement and a closing brace on its own line. One-line loops may have the closing brace on the same line or omit the braces entirely.
for (int i = 0; i < 5; i++) {
content();
}
for (int i = 0; i < 5; i++) {content();}
for (int i = 0; i < 5; i++) content();
Python¶
Style for Python code should follow PEP8.
Docstrings for functions and methods should follow numpydoc style.
Python code should work with Python 3.4+.
Use of third-party Python packages should be limited to numpy, scipy, matplotlib, pandas, and h5py. Use of other third-party packages must be implemented as optional dependencies rather than required dependencies.
Prefer pathlib when working with filesystem paths over functions in the os module or other standard-library modules. Functions that accept arguments that represent a filesystem path should work with both strings and Path objects.
Test Suite¶
The OpenMC test suite consists of two parts, a regression test suite and a unit test suite. The regression test suite is based on regression or integrated testing where different types of input files are configured and the full OpenMC code is executed. Results from simulations are compared with expected results. The unit tests are primarily intended to test individual functions/classes in the OpenMC Python API.
Prerequisites¶
The test suite relies on the third-party pytest package. To run either or both the regression and unit test suites, it is assumed that you have OpenMC fully installed, i.e., the openmc executable is available on your
PATH
and theopenmc
Python module is importable. In development where it would be onerous to continually install OpenMC every time a small change is made, it is recommended to install OpenMC in development/editable mode. With setuptools, this is accomplished by running:python setup.py develop
or using pip (recommended):
pip install -e .[test]
The test suite requires a specific set of cross section data in order for tests to pass. A download URL for the data that OpenMC expects can be found within
tools/ci/download-xs.sh
.In addition to the HDF5 data, some tests rely on ENDF files. A download URL for those can also be found in
tools/ci/download-xs.sh
.Some tests require NJOY to preprocess cross section data. The test suite assumes that you have an
njoy
executable available on yourPATH
.
Running Tests¶
To execute the test suite, go to the tests/
directory and run:
pytest
If you want to collect information about source line coverage in the Python API, you must have the pytest-cov plugin installed and run:
pytest --cov=../openmc --cov-report=html
Generating XML Inputs¶
Many of the regression tests rely on the Python API to build an appropriate model. However, it can sometimes be desirable to work directly with the XML input files rather than having to run a script in order to run the problem/test. To build the input files for a test without actually running the test, you can run:
pytest --build-inputs <name-of-test>
Adding Tests to the Regression Suite¶
To add a new test to the regression test suite, create a sub-directory in the
tests/regression_tests/
directory. To configure a test you need to add the
following files to your new test directory:
- OpenMC input XML files, if they are not generated through the Python API
- test.py - Python test driver script; please refer to other tests to see how to construct. Any output files that are generated during testing must be removed at the end of this script.
- inputs_true.dat - ASCII file that contains Python API-generated XML files concatenated together. When the test is run, inputs that are generated are compared to this file.
- results_true.dat - ASCII file that contains the expected results from the test. The file results_test.dat is compared to this file during the execution of the python test driver script. When the above files have been created, generate a results_test.dat file and copy it to this name and commit. It should be noted that this file should be generated with basic compiler options during openmc configuration and build (e.g., no MPI, no debug/optimization).
In addition to this description, please see the various types of tests that are already included in the test suite to see how to create them. If all is implemented correctly, the new test will automatically be discovered by pytest.
Making User Input Changes¶
Users are encouraged to use OpenMC’s Python API to build XML files that the OpenMC solver then reads during the initialization phase. Thus, to modify, add, or remove user input options, changes must be made both within the Python API and the C++ source that reads XML files produced by the Python API. The following steps should be followed to make changes to user input:
Determine the Python class you need to change. For example, if you are adding a new setting, you probably want to change the
openmc.Settings
class. If you are adding a new surface type, you would need to create a subclass ofopenmc.Surface
.To add a new option, the class will need a property attribute. For example, if you wanted to add a “fast_mode” setting, you would need two methods that look like:
@property def fast_mode(self): ... @fast_mode.setter def fast_mode(self, fast_mode): ...
Make sure that when an instance of the class is exported to XML (usually through a
export_to_xml()
orto_xml_element()
method), a new element is written to the appropriate file. OpenMC uses thexml.etree.ElementTree
API, so refer to the documentation of that module for guidance on creating elements/attributes.Make sure that your input can be categorized as one of the datatypes from XML Schema Part 2 and that parsing of the data appropriately reflects this. For example, for a boolean value, true can be represented either by “true” or by “1”.
Now that you’re done with the Python side, you need to make modifications to the C++ codebase. Make appropriate changes in source files (e.g., settings.cpp). You should use convenience functions defined by xml_interface.cpp.
If you’ve made changes in the geometry or materials, make sure they are written out to the statepoint or summary files and that the
openmc.StatePoint
andopenmc.Summary
classes read them in.Finally, a set of RELAX NG schemas exists that enables validation of input files. You should modify the RELAX NG schema for the file you changed. The easiest way to do this is to change the compact syntax file (e.g.
src/relaxng/geometry.rnc
) and then convert it to regular XML syntax using trang:trang geometry.rnc geometry.rng
For most user input additions and changes, it is simple enough to follow a “monkey see, monkey do” approach. When in doubt, contact your nearest OpenMC developer or send a message to the developers mailing list.
Building Sphinx Documentation¶
In order to build the documentation in the docs
directory, you will need to
have the Sphinx third-party Python
package. The easiest way to install Sphinx is via pip:
pip install sphinx
Additionally, you will need several Sphinx extensions that can be installed directly with pip:
pip install sphinx-numfig
pip install sphinxcontrib-katex
pip install sphinxcontrib-svg2pdfconverter
Building Documentation as a Webpage¶
To build the documentation as a webpage (what appears at
https://docs.openmc.org), simply go to the docs
directory and run:
make html
Building Documentation as a PDF¶
To build PDF documentation, you will need to have a LaTeX distribution installed
on your computer. Once you have a LaTeX distribution installed, simply go to the
docs
directory and run:
make latexpdf
Deployment with Docker¶
OpenMC can be easily deployed using Docker on any
Windows, Mac or Linux system. With Docker running, execute the following
command in the shell to build a Docker image called debian/openmc:latest
:
docker build -t debian/openmc:latest https://github.com/openmc-dev/openmc.git#develop
Note
This may take 5 – 10 minutes to run to completion.
This command will execute the instructions in OpenMC’s Dockerfile
to
build a Docker image with OpenMC installed. The image includes OpenMC with
MPICH and parallel HDF5 in the /opt/openmc
directory, and
Miniconda3 with all of the Python
pre-requisites (NumPy, SciPy, Pandas, etc.) installed. The
NJOY2016 codebase is installed in
/opt/NJOY2016
to support full functionality and testing of the
openmc.data
Python module. The publicly available nuclear data libraries
necessary to run OpenMC’s test suite – including NNDC and WMP cross sections
and ENDF data – are in the /opt/openmc/data directory
, and the
corresponding OPENMC_CROSS_SECTIONS
,
OPENMC_MULTIPOLE_LIBRARY
, and OPENMC_ENDF_DATA
environment variables are initialized.
After building the Docker image, you can run the following to see the names of
all images on your machine, including debian/openmc:latest
:
docker image ls
Now you can run the following to create a Docker container called
my_openmc
based on the debian/openmc:latest
image:
docker run -it --name=my_openmc debian/openmc:latest
This command will open an interactive shell running from within the Docker container where you have access to use OpenMC.
Note
The docker run
command supports many
options
for spawning containers – including mounting volumes from the
host filesystem – which many users will find useful.
Python API¶
OpenMC includes a rich Python API that enables programmatic pre- and post-processing. The easiest way to begin using the API is to take a look at the Examples. This assumes that you are already familiar with Python and common third-party packages such as NumPy. If you have never used Python before, the prospect of learning a new code and a programming language might sound daunting. However, you should keep in mind that there are many substantial benefits to using the Python API, including:
- The ability to define dimensions using variables.
- Availability of standard-library modules for working with files.
- An entire ecosystem of third-party packages for scientific computing.
- Automated multi-group cross section generation (
openmc.mgxs
) - A fully-featured nuclear data interface (
openmc.data
) - Depletion capability (
openmc.deplete
) - Convenience functions (e.g., a function returning a hexagonal region)
- Ability to plot individual universes as geometry is being created
- A \(k_\text{eff}\) search function (
openmc.search_for_keff()
) - Random sphere packing for generating TRISO particle locations
(
openmc.model.pack_spheres()
) - Ability to create materials based on natural elements or uranium enrichment
For those new to Python, there are many good tutorials available online. We recommend going through the modules from Codecademy and/or the Scipy lectures.
The full API documentation serves to provide more information on a given module or class.
Tip
Users are strongly encouraged to use the Python API to generate input files and analyze results.
Modules
openmc
– Basic Functionality¶
Handling nuclear data¶
openmc.XSdata |
A multi-group cross section data set providing all the multi-group data necessary for a multi-group OpenMC calculation. |
openmc.MGXSLibrary |
Multi-Group Cross Sections file used for an OpenMC simulation. |
Simulation Settings¶
openmc.Source |
Distribution of phase space coordinates for source sites. |
openmc.SourceParticle |
Source particle |
openmc.VolumeCalculation |
Stochastic volume calculation specifications and results. |
openmc.Settings |
Settings used for an OpenMC simulation. |
The following function can be used for generating a source file:
openmc.write_source_file |
Write a source file using a collection of source particles |
Material Specification¶
openmc.Nuclide |
A nuclide that can be used in a material. |
openmc.Element |
A natural element that auto-expands to add the isotopes of an element to a material in their natural abundance. |
openmc.Macroscopic |
A Macroscopic object that can be used in a material. |
openmc.Material |
A material composed of a collection of nuclides/elements. |
openmc.Materials |
Collection of Materials used for an OpenMC simulation. |
Cross sections for nuclides, elements, and materials can be plotted using the following function:
openmc.plot_xs |
Creates a figure of continuous-energy cross sections for this item. |
Building geometry¶
openmc.Plane |
An arbitrary plane of the form \(Ax + By + Cz = D\). |
openmc.XPlane |
A plane perpendicular to the x axis of the form \(x - x_0 = 0\) |
openmc.YPlane |
A plane perpendicular to the y axis of the form \(y - y_0 = 0\) |
openmc.ZPlane |
A plane perpendicular to the z axis of the form \(z - z_0 = 0\) |
openmc.XCylinder |
An infinite cylinder whose length is parallel to the x-axis of the form \((y - y_0)^2 + (z - z_0)^2 = r^2\). |
openmc.YCylinder |
An infinite cylinder whose length is parallel to the y-axis of the form \((x - x_0)^2 + (z - z_0)^2 = r^2\). |
openmc.ZCylinder |
An infinite cylinder whose length is parallel to the z-axis of the form \((x - x_0)^2 + (y - y_0)^2 = r^2\). |
openmc.Sphere |
A sphere of the form \((x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = r^2\). |
openmc.Cone |
A conical surface parallel to the x-, y-, or z-axis. |
openmc.XCone |
A cone parallel to the x-axis of the form \((y - y_0)^2 + (z - z_0)^2 = r^2 (x - x_0)^2\). |
openmc.YCone |
A cone parallel to the y-axis of the form \((x - x_0)^2 + (z - z_0)^2 = r^2 (y - y_0)^2\). |
openmc.ZCone |
A cone parallel to the x-axis of the form \((x - x_0)^2 + (y - y_0)^2 = r^2 (z - z_0)^2\). |
openmc.Quadric |
A surface of the form \(Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0\). |
openmc.Halfspace |
A positive or negative half-space region. |
openmc.Intersection |
Intersection of two or more regions. |
openmc.Union |
Union of two or more regions. |
openmc.Complement |
Complement of a region. |
openmc.Cell |
A region of space defined as the intersection of half-space created by quadric surfaces. |
openmc.Universe |
A collection of cells that can be repeated. |
openmc.RectLattice |
A lattice consisting of rectangular prisms. |
openmc.HexLattice |
A lattice consisting of hexagonal prisms. |
openmc.Geometry |
Geometry representing a collection of surfaces, cells, and universes. |
Many of the above classes are derived from several abstract classes:
openmc.Surface |
An implicit surface with an associated boundary condition. |
openmc.Region |
Region of space that can be assigned to a cell. |
openmc.Lattice |
A repeating structure wherein each element is a universe. |
Constructing Tallies¶
openmc.Filter |
Tally modifier that describes phase-space and other characteristics. |
openmc.UniverseFilter |
Bins tally event locations based on the Universe they occured in. |
openmc.MaterialFilter |
Bins tally event locations based on the Material they occured in. |
openmc.CellFilter |
Bins tally event locations based on the Cell they occured in. |
openmc.CellFromFilter |
Bins tally on which Cell the neutron came from. |
openmc.CellbornFilter |
Bins tally events based on which Cell the neutron was born in. |
openmc.CellInstanceFilter |
Bins tally events based on which cell instance a particle is in. |
openmc.CollisionFilter |
Bins tally events based on the number of collisions. |
openmc.SurfaceFilter |
Filters particles by surface crossing |
openmc.MeshFilter |
Bins tally event locations onto a regular, rectangular mesh. |
openmc.MeshSurfaceFilter |
Filter events by surface crossings on a regular, rectangular mesh. |
openmc.EnergyFilter |
Bins tally events based on incident particle energy. |
openmc.EnergyoutFilter |
Bins tally events based on outgoing particle energy. |
openmc.MuFilter |
Bins tally events based on particle scattering angle. |
openmc.PolarFilter |
Bins tally events based on the incident particle’s direction. |
openmc.AzimuthalFilter |
Bins tally events based on the incident particle’s direction. |
openmc.DistribcellFilter |
Bins tally event locations on instances of repeated cells. |
openmc.DelayedGroupFilter |
Bins fission events based on the produced neutron precursor groups. |
openmc.EnergyFunctionFilter |
Multiplies tally scores by an arbitrary function of incident energy. |
openmc.LegendreFilter |
Score Legendre expansion moments up to specified order. |
openmc.SpatialLegendreFilter |
Score Legendre expansion moments in space up to specified order. |
openmc.SphericalHarmonicsFilter |
Score spherical harmonic expansion moments up to specified order. |
openmc.ZernikeFilter |
Score Zernike expansion moments in space up to specified order. |
openmc.ZernikeRadialFilter |
Score the \(m = 0\) (radial variation only) Zernike moments up to specified order. |
openmc.ParticleFilter |
Bins tally events based on the Particle type. |
openmc.RegularMesh |
A regular Cartesian mesh in one, two, or three dimensions |
openmc.RectilinearMesh |
A 3D rectilinear Cartesian mesh |
openmc.UnstructuredMesh |
A 3D unstructured mesh |
openmc.Trigger |
A criterion for when to finish a simulation based on tally uncertainties. |
openmc.TallyDerivative |
A material perturbation derivative to apply to a tally. |
openmc.Tally |
A tally defined by a set of scores that are accumulated for a list of nuclides given a set of filters. |
openmc.Tallies |
Collection of Tallies used for an OpenMC simulation. |
Geometry Plotting¶
openmc.Plot |
Definition of a finite region of space to be plotted. |
openmc.Plots |
Collection of Plots used for an OpenMC simulation. |
Running OpenMC¶
openmc.run |
Run an OpenMC simulation. |
openmc.calculate_volumes |
Run stochastic volume calculations in OpenMC. |
openmc.plot_geometry |
Run OpenMC in plotting mode |
openmc.plot_inline |
Display plots inline in a Jupyter notebook. |
openmc.search_for_keff |
Function to perform a keff search by modifying a model parametrized by a single independent variable. |
Post-processing¶
openmc.Particle |
Information used to restart a specific particle that caused a simulation to fail. |
openmc.StatePoint |
State information on a simulation at a certain point in time (at the end of a given batch). |
openmc.Summary |
Summary of geometry, materials, and tallies used in a simulation. |
The following classes and functions are used for functional expansion reconstruction.
openmc.ZernikeRadial |
Create radial only Zernike polynomials given coefficients and domain. |
openmc.legendre_from_expcoef |
Return a Legendre series object based on expansion coefficients. |
Various classes may be created when performing tally slicing and/or arithmetic:
openmc.arithmetic.CrossScore |
A special-purpose tally score used to encapsulate all combinations of two tally’s scores as an outer product for tally arithmetic. |
openmc.arithmetic.CrossNuclide |
A special-purpose nuclide used to encapsulate all combinations of two tally’s nuclides as an outer product for tally arithmetic. |
openmc.arithmetic.CrossFilter |
A special-purpose filter used to encapsulate all combinations of two tally’s filter bins as an outer product for tally arithmetic. |
openmc.arithmetic.AggregateScore |
A special-purpose tally score used to encapsulate an aggregate of a subset or all of tally’s scores for tally aggregation. |
openmc.arithmetic.AggregateNuclide |
A special-purpose tally nuclide used to encapsulate an aggregate of a subset or all of tally’s nuclides for tally aggregation. |
openmc.arithmetic.AggregateFilter |
A special-purpose tally filter used to encapsulate an aggregate of a subset or all of a tally filter’s bins for tally aggregation. |
Coarse Mesh Finite Difference Acceleration¶
CMFD is implemented in OpenMC and allows users to accelerate fission source
convergence during inactive neutron batches. To use CMFD, the
openmc.cmfd.CMFDRun
class executes OpenMC through the C API, solving
the CMFD system between fission generations and modifying the source weights.
Note that the openmc.cmfd
module is not imported by default with the
openmc
namespace and needs to be imported explicitly.
openmc.cmfd.CMFDMesh |
A structured Cartesian mesh used for CMFD acceleration. |
openmc.cmfd.CMFDRun |
Class for running CMFD acceleration through the C API. |
At the minimum, a CMFD mesh needs to be specified in order to run CMFD. Once the
mesh and other optional properties are set, a simulation can be run with CMFD
turned on using openmc.cmfd.CMFDRun.run()
.
openmc.model
– Model Building¶
Convenience Functions¶
openmc.model.borated_water |
Return a Material with the composition of boron dissolved in water. |
openmc.model.cylinder_from_points |
Return a cylinder given points that define the axis and a radius. |
openmc.model.hexagonal_prism |
Create a hexagon region from six surface planes. |
openmc.model.rectangular_prism |
Get an infinite rectangular prism from four planar surfaces. |
openmc.model.subdivide |
Create regions separated by a series of surfaces. |
openmc.model.pin |
Convenience function for building a fuel pin |
Composite Surfaces¶
openmc.model.RectangularParallelepiped |
Rectangular parallelpiped composite surface |
openmc.model.RightCircularCylinder |
Right circular cylinder composite surface |
openmc.model.XConeOneSided |
One-sided cone parallel the x-axis |
openmc.model.YConeOneSided |
One-sided cone parallel the y-axis |
openmc.model.ZConeOneSided |
One-sided cone parallel the z-axis |
TRISO Fuel Modeling¶
Classes¶
openmc.model.TRISO |
Tristructural-isotopic (TRISO) micro fuel particle |
Functions¶
openmc.model.create_triso_lattice |
Create a lattice containing TRISO particles for optimized tracking. |
openmc.model.pack_spheres |
Generate a random, non-overlapping configuration of spheres within a container. |
Model Container¶
Classes¶
openmc.model.Model |
Model container. |
openmc.examples
– Example Models¶
Simple Models¶
openmc.examples.slab_mg |
Create a 1D slab model. |
Reactor Models¶
openmc.examples.pwr_pin_cell |
Create a PWR pin-cell model. |
openmc.examples.pwr_assembly |
Create a PWR assembly model. |
openmc.examples.pwr_core |
Create a PWR full-core model. |
openmc.deplete
– Depletion¶
Primary API¶
The two primary requirements to perform depletion with openmc.deplete
are:
- A transport operator
- 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 \(\Delta t\), and obtaining new compositions given a power
or power density. The 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.
PredictorIntegrator |
Deplete using a first-order predictor algorithm. |
CECMIntegrator |
Deplete using the CE/CM algorithm. |
CELIIntegrator |
Deplete using the CE/LI CFQ4 algorithm. |
CF4Integrator |
Deplete using the CF4 algorithm. |
EPCRK4Integrator |
Deplete using the EPC-RK4 algorithm. |
LEQIIntegrator |
Deplete using the LE/QI CFQ4 algorithm. |
SICELIIntegrator |
Deplete using the SI-CE/LI CFQ4 algorithm. |
SILEQIIntegrator |
Deplete using the SI-LE/QI CFQ4 algorithm. |
Each of these classes expects a “transport operator” to be passed. An operator specific to OpenMC is available using the following class:
Operator |
OpenMC transport operator for depletion. |
The Operator
must also have some knowledge of how nuclides transmute
and decay. This is handled by the Chain
.
Minimal Example¶
A minimal example for performing depletion would be:
Internal Classes and Functions¶
When running in parallel using mpi4py, the MPI intercommunicator used can
be changed by modifying the following module variable. If it is not explicitly
modified, it defaults to mpi4py.MPI.COMM_WORLD
.
-
openmc.deplete.
comm
¶ MPI intercommunicator used to call OpenMC library
Type: mpi4py.MPI.Comm
During a depletion calculation, the depletion chain, reaction rates, and number densities are managed through a series of internal classes that are not normally visible to a user. However, should you find yourself wondering about these classes (e.g., if you want to know what decay modes or reactions are present in a depletion chain), they are documented here. The following classes store data for a depletion chain:
Chain |
Full representation of a depletion chain. |
DecayTuple |
Decay mode information |
Nuclide |
Decay modes, reactions, and fission yields for a single nuclide. |
ReactionTuple |
Transmutation reaction information |
FissionYieldDistribution |
Energy-dependent fission product yields for a single nuclide |
FissionYield |
Mapping for fission yields of a parent at a specific energy |
The Chain
class uses information from the following module variable:
-
chain.
REACTIONS
¶ Dictionary that maps transmutation reaction names to information needed when a chain is being generated: MT values, the change in atomic/mass numbers resulting from the reaction, and what secondaries are produced.
Type: dict
The following classes are used during a depletion simulation and store auxiliary data, such as number densities and reaction rates for each material.
AtomNumber |
Stores local material compositions (atoms of each nuclide). |
OperatorResult |
Result of applying transport operator |
ReactionRates |
Reaction rates resulting from a transport operator call |
Results |
Output of a depletion run |
ResultsList |
A list of openmc.deplete.Results objects |
The following class and functions are used to solve the depletion equations,
with cram.CRAM48()
being the default.
cram.IPFCramSolver |
CRAM depletion solver that uses incomplete partial factorization |
cram.CRAM16 |
Solve depletion equations using IPF CRAM |
cram.CRAM48 |
Solve depletion equations using IPF CRAM |
pool.deplete |
Deplete materials using given reaction rates for a specified time |
-
pool.
USE_MULTIPROCESSING
¶ Boolean switch to enable or disable the use of
multiprocessing
when solving the Bateman equations. The default is to usemultiprocessing
, but can cause the simulation to hang in some computing environments, namely due to MPI and networking restrictions. Disabling this option will result in only a single CPU core being used for depletion.Type: bool
The following classes are used to help the openmc.deplete.Operator
compute quantities like effective fission yields, reaction rates, and
total system energy.
helpers.AveragedFissionYieldHelper |
Class that computes fission yields based on average fission energy |
helpers.ChainFissionHelper |
Computes normalization using fission Q values from depletion chain |
helpers.ConstantFissionYieldHelper |
Class that uses a single set of fission yields on each isotope |
helpers.DirectReactionRateHelper |
Class for generating one-group reaction rates with direct tallies |
helpers.EnergyScoreHelper |
Class responsible for obtaining system energy via a tally score |
helpers.FissionYieldCutoffHelper |
Helper that computes fission yields based on a cutoff energy |
helpers.FluxCollapseHelper |
Class that generates one-group reaction rates using multigroup flux |
Abstract Base Classes¶
A good starting point for extending capabilities in openmc.deplete
is
to examine the following abstract base classes. Custom classes can
inherit from abc.TransportOperator
to implement alternative
schemes for collecting reaction rates and other data from a transport code
prior to depleting materials
abc.TransportOperator |
Abstract class defining a transport operator |
The following classes are abstract classes used to pass information from
OpenMC simulations back on to the abc.TransportOperator
abc.NormalizationHelper |
Abstract class for obtaining normalization factor on tallies |
abc.FissionYieldHelper |
Abstract class for processing energy dependent fission yields |
abc.ReactionRateHelper |
Abstract class for generating reaction rates for operators |
abc.TalliedFissionYieldHelper |
Abstract class for computing fission yields with tallies |
Custom integrators or depletion solvers can be developed by subclassing from the following abstract base classes:
abc.Integrator |
Abstract class for solving the time-integration for depletion |
abc.SIIntegrator |
Abstract class for the Stochastic Implicit Euler integrators |
abc.DepSystemSolver |
Abstract class for solving depletion equations |
openmc.mgxs
– Multi-Group Cross Section Generation¶
Energy Groups¶
Module Variables¶
-
openmc.mgxs.
GROUP_STRUCTURES
¶ - “CASMO-X” (where X is 2, 4, 8, 16, 25, 40 or 70) from the CASMO lattice physics code
- “XMAS-172” designed for LWR analysis ([SAR1990], [SAN2004])
- “SHEM-361” designed for LWR analysis to eliminate self-shielding calculations of thermal resonances ([HFA2005], [SAN2007], [HEB2008])
- activation energy group structures “VITAMIN-J-42”, “VITAMIN-J-175”, “TRIPOLI-315”, “CCFE-709” and “UKAEA-1102”
[SAR1990] Sartori, E., OECD/NEA Data Bank: Standard Energy Group Structures of Cross Section Libraries for Reactor Shielding, Reactor Cell and Fusion Neutronics Applications: VITAMIN-J, ECCO-33, ECCO-2000 and XMAS JEF/DOC-315 Revision 3 - DRAFT (December 11, 1990). [SAN2004] Santamarina, A., Collignon, C., & Garat, C. (2004). French calculation schemes for light water reactor analysis. United States: American Nuclear Society - ANS. [HFA2005] Hfaiedh, N. & Santamarina, A., “Determination of the Optimized SHEM Mesh for Neutron Transport Calculations,” Proc. Top. Mtg. in Mathematics & Computations, Supercomputing, Reactor Physics and Nuclear and Biological Applications, September 12-15, Avignon, France, 2005. [SAN2007] Santamarina, A. & Hfaiedh, N. (2007). The SHEM energy mesh for accurate fuel depletion and BUC calculations. Proceedings of the International Conference on Safety Criticality ICNC 2007, St Peterburg (Russia), Vol. I pp. 446-452. [HEB2008] Hébert, Alain & Santamarina, Alain. (2008). Refinement of the Santamarina-Hfaiedh energy mesh between 22.5 eV and 11.4 keV. International Conference on the Physics of Reactors 2008, PHYSOR 08. 2. 929-938. Type: Dictionary of commonly used energy group structures
Classes¶
openmc.mgxs.EnergyGroups |
An energy groups structure used for multi-group cross-sections. |
Multi-group Cross Sections¶
openmc.mgxs.MGXS |
An abstract multi-group cross section for some energy group structure within some spatial domain. |
openmc.mgxs.MatrixMGXS |
An abstract multi-group cross section for some energy group structure within some spatial domain. |
openmc.mgxs.AbsorptionXS |
An absorption multi-group cross section. |
openmc.mgxs.CaptureXS |
A capture multi-group cross section. |
openmc.mgxs.Chi |
The fission spectrum. |
openmc.mgxs.Current |
A current multi-group cross section. |
openmc.mgxs.DiffusionCoefficient |
A diffusion coefficient multi-group cross section. |
openmc.mgxs.FissionXS |
A fission multi-group cross section. |
openmc.mgxs.InverseVelocity |
An inverse velocity multi-group cross section. |
openmc.mgxs.KappaFissionXS |
A recoverable fission energy production rate multi-group cross section. |
openmc.mgxs.MultiplicityMatrixXS |
The scattering multiplicity matrix. |
openmc.mgxs.NuFissionMatrixXS |
A fission production matrix multi-group cross section. |
openmc.mgxs.ScatterXS |
A scattering multi-group cross section. |
openmc.mgxs.ScatterMatrixXS |
A scattering matrix multi-group cross section with the cosine of the change-in-angle represented as one or more Legendre moments or a histogram. |
openmc.mgxs.ScatterProbabilityMatrix |
The group-to-group scattering probability matrix. |
openmc.mgxs.TotalXS |
A total multi-group cross section. |
openmc.mgxs.TransportXS |
A transport-corrected total multi-group cross section. |
openmc.mgxs.ArbitraryXS |
A multi-group cross section for an arbitrary reaction type. |
openmc.mgxs.ArbitraryMatrixXS |
A multi-group matrix cross section for an arbitrary reaction type. |
openmc.mgxs.MeshSurfaceMGXS |
An abstract multi-group cross section for some energy group structure on the surfaces of a mesh domain. |
Multi-delayed-group Cross Sections¶
openmc.mgxs.MDGXS |
An abstract multi-delayed-group cross section for some energy and delayed group structures within some spatial domain. |
openmc.mgxs.MatrixMDGXS |
An abstract multi-delayed-group cross section for some energy group and delayed group structure within some spatial domain. |
openmc.mgxs.ChiDelayed |
The delayed fission spectrum. |
openmc.mgxs.DelayedNuFissionXS |
A fission delayed neutron production multi-group cross section. |
openmc.mgxs.DelayedNuFissionMatrixXS |
A fission delayed neutron production matrix multi-group cross section. |
openmc.mgxs.Beta |
The delayed neutron fraction. |
openmc.mgxs.DecayRate |
The decay rate for delayed neutron precursors. |
Multi-group Cross Section Libraries¶
openmc.mgxs.Library |
A multi-energy-group and multi-delayed-group cross section library for some energy group structure. |
openmc.stats
– Statistics¶
Univariate Probability Distributions¶
openmc.stats.Univariate |
Probability distribution of a single random variable. |
openmc.stats.Discrete |
Distribution characterized by a probability mass function. |
openmc.stats.Uniform |
Distribution with constant probability over a finite interval [a,b] |
openmc.stats.Maxwell |
Maxwellian distribution in energy. |
openmc.stats.Watt |
Watt fission energy spectrum. |
openmc.stats.Tabular |
Piecewise continuous probability distribution. |
openmc.stats.Legendre |
Probability density given by a Legendre polynomial expansion \(\sum\limits_{\ell=0}^N \frac{2\ell + 1}{2} a_\ell P_\ell(\mu)\). |
openmc.stats.Mixture |
Probability distribution characterized by a mixture of random variables. |
openmc.stats.Normal |
Normally distributed sampling. |
openmc.stats.Muir |
Muir energy spectrum. |
Angular Distributions¶
openmc.stats.UnitSphere |
Distribution of points on the unit sphere. |
openmc.stats.PolarAzimuthal |
Angular distribution represented by polar and azimuthal angles |
openmc.stats.Isotropic |
Isotropic angular distribution. |
openmc.stats.Monodirectional |
Monodirectional angular distribution. |
Spatial Distributions¶
openmc.stats.Spatial |
Distribution of locations in three-dimensional Euclidean space. |
openmc.stats.CartesianIndependent |
Spatial distribution with independent x, y, and z distributions. |
openmc.stats.CylindricalIndependent |
Spatial distribution represented in cylindrical coordinates. |
openmc.stats.SphericalIndependent |
Spatial distribution represented in spherical coordinates. |
openmc.stats.Box |
Uniform distribution of coordinates in a rectangular cuboid. |
openmc.stats.Point |
Delta function in three dimensions. |
openmc.data
– Nuclear Data Interface¶
Core Classes¶
The following classes are used for incident neutron data, decay data, fission and product yields.
IncidentNeutron |
Continuous-energy neutron interaction data. |
Reaction |
A nuclear reaction |
Product |
Secondary particle emitted in a nuclear reaction |
FissionEnergyRelease |
Energy relased by fission reactions. |
DataLibrary |
Collection of cross section data libraries. |
Decay |
Radioactive decay data. |
FissionProductYields |
Independent and cumulative fission product yields. |
WindowedMultipole |
Resonant cross sections represented in the windowed multipole format. |
ProbabilityTables |
Unresolved resonance region probability tables. |
The following classes are used for storing atomic data (incident photon cross sections, atomic relaxation):
IncidentPhoton |
Photon interaction data. |
PhotonReaction |
Photon-induced reaction |
AtomicRelaxation |
Atomic relaxation data. |
The following classes are used for storing thermal neutron scattering data:
ThermalScattering |
A ThermalScattering object contains thermal scattering data as represented by an S(alpha, beta) table. |
ThermalScatteringReaction |
Thermal scattering reaction |
CoherentElastic |
Coherent elastic scattering data from a crystalline material |
IncoherentElastic |
Incoherent elastic scattering cross section |
Core Functions¶
atomic_mass |
Return atomic mass of isotope in atomic mass units. |
atomic_weight |
Return atomic weight of an element in atomic mass units. |
dose_coefficients |
Return effective dose conversion coefficients from ICRP-116 |
gnd_name |
Return nuclide name using GND convention |
isotopes |
Return naturally occurring isotopes and their abundances |
linearize |
Return a tabulated representation of a one-variable function |
thin |
Check for (x,y) points that can be removed. |
water_density |
Return the density of liquid water at a given temperature and pressure. |
zam |
Return tuple of (atomic number, mass number, metastable state) |
One-dimensional Functions¶
Function1D |
A function of one independent variable with HDF5 support. |
Tabulated1D |
A one-dimensional tabulated function. |
Polynomial |
A power series class. |
Combination |
Combination of multiple functions with a user-defined operator |
Sum |
Sum of multiple functions. |
Regions1D |
Piecewise composition of multiple functions. |
ResonancesWithBackground |
Cross section in resolved resonance region. |
Angle-Energy Distributions¶
AngleEnergy |
Distribution in angle and energy of a secondary particle. |
KalbachMann |
Kalbach-Mann distribution |
CorrelatedAngleEnergy |
Correlated angle-energy distribution |
UncorrelatedAngleEnergy |
Uncorrelated angle-energy distribution |
NBodyPhaseSpace |
N-body phase space distribution |
LaboratoryAngleEnergy |
Laboratory angle-energy distribution |
AngleDistribution |
Angle distribution as a function of incoming energy |
EnergyDistribution |
Abstract superclass for all energy distributions. |
ArbitraryTabulated |
Arbitrary tabulated function given in ENDF MF=5, LF=1 represented as |
GeneralEvaporation |
General evaporation spectrum given in ENDF MF=5, LF=5 represented as |
MaxwellEnergy |
Simple Maxwellian fission spectrum represented as |
Evaporation |
Evaporation spectrum represented as |
WattEnergy |
Energy-dependent Watt spectrum represented as |
MadlandNix |
Energy-dependent fission neutron spectrum (Madland and Nix) given in ENDF MF=5, LF=12 represented as |
DiscretePhoton |
Discrete photon energy distribution |
LevelInelastic |
Level inelastic scattering |
ContinuousTabular |
Continuous tabular distribution |
CoherentElasticAE |
Differential cross section for coherent elastic scattering |
IncoherentElasticAE |
Differential cross section for incoherent elastic scattering |
IncoherentElasticAEDiscrete |
Discrete angle representation of incoherent elastic scattering |
IncoherentInelasticAEDiscrete |
Discrete angle representation of incoherent inelastic scattering |
Resonance Data¶
Resonances |
Resolved and unresolved resonance data |
ResonanceRange |
Resolved resonance range |
SingleLevelBreitWigner |
Single-level Breit-Wigner resolved resonance formalism data. |
MultiLevelBreitWigner |
Multi-level Breit-Wigner resolved resonance formalism data. |
ReichMoore |
Reich-Moore resolved resonance formalism data. |
RMatrixLimited |
R-matrix limited resolved resonance formalism data. |
ResonanceCovariances |
Resolved resonance covariance data |
ResonanceCovarianceRange |
Resonace covariance range. |
SingleLevelBreitWignerCovariance |
Single-level Breit-Wigner resolved resonance formalism covariance data. |
MultiLevelBreitWignerCovariance |
Multi-level Breit-Wigner resolved resonance formalism covariance data. |
ReichMooreCovariance |
Reich-Moore resolved resonance formalism covariance data. |
ParticlePair |
|
SpinGroup |
Resonance spin group |
Unresolved |
Unresolved resonance parameters as identified by LRU=2 in MF=2. |
ACE Format¶
Classes¶
ace.Library |
A Library objects represents an ACE-formatted file which may contain multiple tables with data. |
ace.Table |
ACE cross section table |
ace.TableType |
Type of ACE data table. |
Functions¶
ace.ascii_to_binary |
Convert an ACE file in ASCII format (type 1) to binary format (type 2). |
ace.get_libraries_from_xsdir |
Determine paths to ACE files from an MCNP xsdir file. |
ace.get_libraries_from_xsdata |
Determine paths to ACE files from a Serpent xsdata file. |
ENDF Format¶
Classes¶
endf.Evaluation |
ENDF material evaluation with multiple files/sections |
Functions¶
endf.float_endf |
Convert string of floating point number in ENDF to float. |
endf.get_cont_record |
Return data from a CONT record in an ENDF-6 file. |
endf.get_evaluations |
Return a list of all evaluations within an ENDF file. |
endf.get_head_record |
Return data from a HEAD record in an ENDF-6 file. |
endf.get_tab1_record |
Return data from a TAB1 record in an ENDF-6 file. |
endf.get_tab2_record |
|
endf.get_text_record |
Return data from a TEXT record in an ENDF-6 file. |
NJOY Interface¶
njoy.run |
Run NJOY with given commands |
njoy.make_pendf |
Generate pointwise ENDF file from an ENDF file |
njoy.make_ace |
Generate incident neutron ACE file from an ENDF file |
njoy.make_ace_thermal |
Generate thermal scattering ACE file from ENDF files |
openmc.lib
– Python bindings to the C/C++ API¶
This module provides bindings to C/C++ functions defined by OpenMC shared
library. When the openmc.lib
package is imported, the OpenMC shared
library is automatically loaded. Calls to the OpenMC library can then be via
functions or objects in openmc.lib
, for example:
openmc.lib.init()
openmc.lib.run()
openmc.lib.finalize()
Functions¶
calculate_volumes |
Run stochastic volume calculation |
finalize |
Finalize simulation and free memory |
find_cell |
Find the cell at a given point |
find_material |
Find the material at a given point |
hard_reset |
Reset tallies, timers, and pseudo-random number generator state. |
init |
Initialize OpenMC |
iter_batches |
Iterator over batches. |
keff |
Return the calculated k-eigenvalue and its standard deviation. |
load_nuclide |
Load cross section data for a nuclide. |
next_batch |
Run next batch. |
num_realizations |
Number of realizations of global tallies. |
plot_geometry |
Plot geometry |
reset |
Reset tally results |
run |
Run simulation |
run_in_memory |
Provides context manager for calling OpenMC shared library functions. |
simulation_init |
Initialize simulation |
simulation_finalize |
Finalize simulation |
source_bank |
Return source bank as NumPy array |
statepoint_write |
Write a statepoint file. |
Classes¶
Cell |
Cell stored internally. |
EnergyFilter |
|
MaterialFilter |
|
Material |
Material stored internally. |
MeshFilter |
|
MeshSurfaceFilter |
|
Nuclide |
Nuclide stored internally. |
RegularMesh |
RegularMesh stored internally. |
Tally |
Tally stored internally. |
openmc.openmoc_compatible
– OpenMOC Compatibility¶
Core Classes¶
openmc.openmoc_compatible.get_openmoc_material |
Return an OpenMOC material corresponding to an OpenMC material. |
openmc.openmoc_compatible.get_openmc_material |
Return an OpenMC material corresponding to an OpenMOC material. |
openmc.openmoc_compatible.get_openmoc_surface |
Return an OpenMOC surface corresponding to an OpenMC surface. |
openmc.openmoc_compatible.get_openmc_surface |
Return an OpenMC surface corresponding to an OpenMOC surface. |
openmc.openmoc_compatible.get_openmoc_cell |
Return an OpenMOC cell corresponding to an OpenMC cell. |
openmc.openmoc_compatible.get_openmc_cell |
Return an OpenMC cell corresponding to an OpenMOC cell. |
openmc.openmoc_compatible.get_openmoc_universe |
Return an OpenMOC universe corresponding to an OpenMC universe. |
openmc.openmoc_compatible.get_openmc_universe |
Return an OpenMC universe corresponding to an OpenMOC universe. |
openmc.openmoc_compatible.get_openmoc_lattice |
Return an OpenMOC lattice corresponding to an OpenMOC lattice. |
openmc.openmoc_compatible.get_openmc_lattice |
Return an OpenMC lattice corresponding to an OpenMOC lattice. |
openmc.openmoc_compatible.get_openmoc_geometry |
Return an OpenMC geometry corresponding to an OpenMOC geometry. |
openmc.openmoc_compatible.get_openmc_geometry |
Return an OpenMC geometry corresponding to an OpenMOC geometry. |
C/C++ API¶
The libopenmc shared library that is built when installing OpenMC exports a
number of C interoperable functions and global variables that can be used for
in-memory coupling. While it is possible to directly use the C/C++ API as
documented here for coupling, most advanced users will find it easier to work
with the Python bindings in the openmc.lib
module.
Warning
The C/C++ API is still experimental and may undergo substantial changes in future releases.
Type Definitions¶
-
Bank
¶ Attributes of a source particle.
-
double
wgt
¶ Weight of the particle
-
double xyz[3]
Position of the particle (units of cm)
-
double uvw[3]
Unit vector indicating direction of the particle
-
double
E
¶ Energy of the particle in eV
-
int
delayed_group
¶ If the particle is a delayed neutron, indicates which delayed precursor group it was born from. If not a delayed neutron, this member is zero.
-
double
Functions¶
-
int
openmc_calculate_volumes
()¶ Run a stochastic volume calculation
Returns: Return status (negative if an error occurred) Return type: int
-
int
openmc_cell_get_fill
(int32_t index, int* type, int32_t** indices, int32_t* n)¶ Get the fill for a cell
Parameters: - index (int32_t) – Index in the cells array
- type (int*) – Type of the fill
- indices (int32_t**) – Array of material indices for cell
- n (int32_t*) – Length of indices array
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_cell_get_id
(int32_t index, int32_t* id)¶ Get the ID of a cell
Parameters: - index (int32_t) – Index in the cells array
- id (int32_t*) – ID of the cell
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_cell_get_temperature
(int32_t index, const int32_t* instance, double* T)¶ Get the temperature of a cell
Parameters: - index (int32_t) – Index in the cells array
- instance (int32_t*) – Which instance of the cell. If a null pointer is passed, the temperature of the first instance is returned.
- T (double*) – temperature of the cell
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_cell_set_fill
(int32_t index, int type, int32_t n, const int32_t* indices)¶ Set the fill for a cell
Parameters: - index (int32_t) – Index in the cells array
- type (int) – Type of the fill
- n (int32_t) – Length of indices array
- indices (const int32_t*) – Array of material indices for cell
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_cell_set_id
(int32_t index, int32_t id)¶ Set the ID of a cell
Parameters: - index (int32_t) – Index in the cells array
- id (int32_t) – ID of the cell
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_cell_set_temperature
(index index, double T, const int32_t* instance, bool set_contained)¶ Set the temperature of a cell.
Parameters: - index (int32_t) – Index in the cells array
- T (double) – Temperature in Kelvin
- instance (const int32_t*) – Which instance of the cell. To set the temperature for all instances, pass a null pointer.
- set_contained – If the cell is not filled by a material, whether to set the temperatures of all filled cells
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_energy_filter_get_bins
(int32_t index, double** energies, int32_t* n)¶ Return the bounding energies for an energy filter
Parameters: - index (int32_t) – Index in the filters array
- energies (double**) – Bounding energies of the bins for the energy filter
- n (int32_t*) – Number of energies specified
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_energy_filter_set_bins
(int32_t index, int32_t n, const double* energies)¶ Set the bounding energies for an energy filter
Parameters: - index (int32_t) – Index in the filters array
- n (int32_t) – Number of energies specified
- energies (const double*) – Bounding energies of the bins for the energy filter
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_extend_cells
(int32_t n, int32_t* index_start, int32_t* index_end)¶ Extend the cells array by n elements
Parameters: - n (int32_t) – Number of cells to create
- index_start (int32_t*) – Index of first new cell
- index_end (int32_t*) – Index of last new cell
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_extend_filters
(int32_t n, int32_t* index_start, int32_t* index_end)¶ Extend the filters array by n elements
Parameters: - n (int32_t) – Number of filters to create
- index_start (int32_t*) – Index of first new filter
- index_end (int32_t*) – Index of last new filter
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_extend_materials
(int32_t n, int32_t* index_start, int32_t* index_end)¶ Extend the materials array by n elements
Parameters: - n (int32_t) – Number of materials to create
- index_start (int32_t*) – Index of first new material
- index_end (int32_t*) – Index of last new material
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_extend_sources
(int32_t n, int32_t* index_start, int32_t* index_end)¶ Extend the external sources array by n elements
Parameters: - n (int32_t) – Number of sources to create
- index_start (int32_t*) – Index of first new source
- index_end (int32_t*) – Index of last new source
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_extend_tallies
(int32_t n, int32_t* index_start, int32_t* index_end)¶ Extend the tallies array by n elements
Parameters: - n (int32_t) – Number of tallies to create
- index_start (int32_t*) – Index of first new tally
- index_end (int32_t*) – Index of last new tally
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_filter_get_id
(int32_t index, int32_t* id)¶ Get the ID of a filter
Parameters: - index (int32_t) – Index in the filters array
- id (int32_t*) – ID of the filter
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_filter_set_id
(int32_t index, int32_t id)¶ Set the ID of a filter
Parameters: - index (int32_t) – Index in the filters array
- id (int32_t) – ID of the filter
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_finalize
()¶ Finalize a simulation
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_find
(double* xyz, int rtype, int32_t* id, int32_t* instance)¶ Determine the ID of the cell/material containing a given point
Parameters: - xyz (double[3]) – Cartesian coordinates
- rtype (int) – Which ID to return (1=cell, 2=material)
- id (int32_t*) – ID of the cell/material found. If a material is requested and the point is in a void, the ID is 0. If an error occurs, the ID is -1.
- instance (int32_t*) – If a cell is repeated in the geometry, the instance of the cell that was found and zero otherwise.
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_get_cell_index
(int32_t id, int32_t* index)¶ Get the index in the cells array for a cell with a given ID
Parameters: - id (int32_t) – ID of the cell
- index (int32_t*) – Index in the cells array
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_get_filter_index
(int32_t id, int32_t* index)¶ Get the index in the filters array for a filter with a given ID
Parameters: - id (int32_t) – ID of the filter
- index (int32_t*) – Index in the filters array
Returns: Return status (negative if an error occurs)
Return type: int
-
void
openmc_get_filter_next_id
(int32_t* id)¶ Get an integer ID that has not been used by any filters.
Parameters: - id (int32_t*) – Unused integer ID
-
int
openmc_get_keff
(double k_combined[2])¶ Parameters: - k_combined (double[2]) – Combined estimate of k-effective
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_get_material_index
(int32_t id, int32_t* index)¶ Get the index in the materials array for a material with a given ID
Parameters: - id (int32_t) – ID of the material
- index (int32_t*) – Index in the materials array
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_get_n_batches
(int* n_batches, bool get_max_batches)¶ Get number of batches to simulate
Parameters: - n_batches (int*) – Number of batches to simulate
- get_max_batches (bool) – Whether to return n_batches or n_max_batches (only relevant when triggers are used)
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_get_nuclide_index
(const char name[], int* index)¶ Get the index in the nuclides array for a nuclide with a given name
Parameters: - name (const char[]) – Name of the nuclide
- index (int*) – Index in the nuclides array
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_get_tally_index
(int32_t id, int32_t* index)¶ Get the index in the tallies array for a tally with a given ID
Parameters: - id (int32_t) – ID of the tally
- index (int32_t*) – Index in the tallies array
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_hard_reset
()¶ Reset tallies, timers, and pseudo-random number generator state
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_init
(int argc, char** argv, const void* intracomm)¶ Initialize OpenMC
Parameters: - argc (int) – Number of command-line arguments (including command)
- argv (char**) – Command-line arguments
- intracomm (const void*) – MPI intracommunicator. If MPI is not being used, a null pointer should be passed.
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_load_nuclide
(const char* name, const double* temps, int n)¶ Load data for a nuclide from the HDF5 data library.
Parameters: - name (const char*) – Name of the nuclide.
- temps (const double*) – Temperatures in [K] to load data at
- n (int) – Number of temperatures
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_add_nuclide
(int32_t index, const char name[], double density)¶ Add a nuclide to an existing material. If the nuclide already exists, the density is overwritten.
Parameters: - index (int32_t) – Index in the materials array
- name (const char[]) – Name of the nuclide
- density (double) – Density in atom/b-cm
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_get_densities
(int32_t index, int** nuclides, double** densities, int* n)¶ Get density for each nuclide in a material.
Parameters: - index (int32_t) – Index in the materials array
- nuclides (int**) – Pointer to array of nuclide indices
- densities (double**) – Pointer to the array of densities
- n (int*) – Length of the array
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_get_density
(int32_t index, double* density)¶ Get density of a material.
Parameters: - index (int32_t) – Index in the materials array
- denity (double*) – Pointer to a density
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_get_id
(int32_t index, int32_t* id)¶ Get the ID of a material
Parameters: - index (int32_t) – Index in the materials array
- id (int32_t*) – ID of the material
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_material_set_density
(int32_t index, double density, const char* units)¶ Set the density of a material.
Parameters: - index (int32_t) – Index in the materials array
- density (double) – Density of the material
- units (const char*) – Units for density
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_set_densities
(int32_t index, int n, const char** name, const double* density)¶ Parameters: - index (int32_t) – Index in the materials array
- n (int) – Length of name/density
- name (const char**) – Array of nuclide names
- density (const double*) – Array of densities
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_material_set_id
(int32_t index, int32_t id)¶ Set the ID of a material
Parameters: - index (int32_t) – Index in the materials array
- id (int32_t) – ID of the material
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_material_filter_get_bins
(int32_t index, int32_t** bins, int32_t* n)¶ Get the bins for a material filter
Parameters: - index (int32_t) – Index in the filters array
- bins (int32_t**) – Index in the materials array for each bin
- n (int32_t*) – Number of bins
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_material_filter_set_bins
(int32_t index, int32_t n, const int32_t* bins)¶ Set the bins for a material filter
Parameters: - index (int32_t) – Index in the filters array
- n (int32_t) – Number of bins
- bins (const int32_t*) – Index in the materials array for each bin
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_mesh_filter_set_mesh
(int32_t index, int32_t index_mesh)¶ Set the mesh for a mesh filter
Parameters: - index (int32_t) – Index in the filters array
- index_mesh (int32_t) – Index in the meshes array
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_next_batch
()¶ Simulate next batch of particles. Must be called after openmc_simulation_init().
Returns: Integer indicating whether simulation has finished (negative) or not finished (zero). Return type: int
-
int
openmc_nuclide_name
(int index, char** name)¶ Get name of a nuclide
Parameters: - index (int) – Index in the nuclides array
- name (char**) – Name of the nuclide
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_plot_geometry
()¶ Run plotting mode.
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_reset
()¶ Resets all tally scores
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_run
()¶ Run a simulation
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_set_n_batches
(int32_t n_batches, bool set_max_batches, bool add_statepoint_batch)¶ Set number of batches and number of max batches
Parameters: - n_batches (int32_t) – Number of batches to simulate
- set_max_batches (bool) – Whether to set settings::n_max_batches or settings::n_batches (only relevant when triggers are used)
- add_statepoint_batch (bool) – Whether to add n_batches to settings::statepoint_batch
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_simulation_finalize
()¶ Finalize a simulation.
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_simulation_init
()¶ Initialize a simulation. Must be called after openmc_init().
Returns: Return status (negative if an error occurs) Return type: int
-
int
openmc_source_bank
(struct Bank** ptr, int64_t* n)¶ Return a pointer to the source bank array.
Parameters: - ptr (struct Bank**) – Pointer to the source bank array
- n (int64_t*) – Length of the source bank array
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_source_set_strength
(int32_t index, double strength)¶ Set the strength of an external source
Parameters: - index (int32_t) – Index in the external source array
- strength (double) – Source strength
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_statepoint_write
(const char filename[], const bool* write_source)¶ Write a statepoint file
Parameters: - filename (const char[]) – Name of file to create. If a null pointer is passed, a filename is assigned automatically.
- write_source (const bool*) – Whether to include the source bank
Returns: Return status (negative if an error occurs)
Return type: int
-
int
openmc_tally_get_id
(int32_t index, int32_t* id)¶ Get the ID of a tally
Parameters: - index (int32_t) – Index in the tallies array
- id (int32_t*) – ID of the tally
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_get_filters
(int32_t index, int32_t** indices, int* n)¶ Get filters specified in a tally
Parameters: - index (int32_t) – Index in the tallies array
- indices (int32_t**) – Array of filter indices
- n (int*) – Number of filters
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_get_n_realizations
(int32_t index, int32_t* n)¶ Parameters: - index (int32_t) – Index in the tallies array
- n (int32_t*) – Number of realizations
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_get_nuclides
(int32_t index, int** nuclides, int* n)¶ Get nuclides specified in a tally
Parameters: - index (int32_t) – Index in the tallies array
- nuclides (int**) – Array of nuclide indices
- n (int*) – Number of nuclides
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_get_scores
(int32_t index, int** scores, int* n)¶ Get scores specified for a tally
Parameters: - index (int32_t) – Index in the tallies array
- scores (int**) – Array of scores
- n (int*) – Number of scores
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_results
(int32_t index, double** ptr, int shape_[3])¶ Get a pointer to tally results array.
Parameters: - index (int32_t) – Index in the tallies array
- ptr (double**) – Pointer to the results array
- shape (int[3]) – Shape of the results array
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_set_filters
(int32_t index, int n, const int32_t* indices)¶ Set filters for a tally
Parameters: - index (int32_t) – Index in the tallies array
- n (int) – Number of filters
- indices (const int32_t*) – Array of filter indices
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_set_id
(int32_t index, int32_t id)¶ Set the ID of a tally
Parameters: - index (int32_t) – Index in the tallies array
- id (int32_t) – ID of the tally
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_set_nuclides
(int32_t index, int n, const char** nuclides)¶ Set the nuclides for a tally
Parameters: - index (int32_t) – Index in the tallies array
- n (int) – Number of nuclides
- nuclides (const char**) – Array of nuclide names
Returns: Return status (negative if an error occurred)
Return type: int
-
int
openmc_tally_set_scores
(int32_t index, int n, const int* scores)¶ Set scores for a tally
Parameters: - index (int32_t) – Index in the tallies array
- n (int) – Number of scores
- scores (const int*) – Array of scores
Returns: Return status (negative if an error occurred)
Return type: int
File Format Specifications¶
Input Files¶
Geometry Specification – geometry.xml¶
<surface>
Element¶
Each <surface>
element can have the following attributes or sub-elements:
id: A unique integer that can be used to identify the surface.
Default: None
name: An optional string name to identify the surface in summary output files. This string is limited to 52 characters for formatting purposes.
Default: “”
type: The type of the surfaces. This can be “x-plane”, “y-plane”, “z-plane”, “plane”, “x-cylinder”, “y-cylinder”, “z-cylinder”, “sphere”, “x-cone”, “y-cone”, “z-cone”, or “quadric”.
Default: None
coeffs: The corresponding coefficients for the given type of surface. See below for a list a what coefficients to specify for a given surface
Default: None
boundary: The boundary condition for the surface. This can be “transmission”, “vacuum”, “reflective”, or “periodic”. Periodic boundary conditions can only be applied to x-, y-, and z-planes. Only axis-aligned periodicity is supported, i.e., x-planes can only be paired with x-planes. Specify which planes are periodic and the code will automatically identify which planes are paired together.
Default: “transmission”
periodic_surface_id: If a periodic boundary condition is applied, this attribute identifies the
id
of the corresponding periodic sufrace.
The following quadratic surfaces can be modeled:
x-plane: A plane perpendicular to the x axis, i.e. a surface of the form \(x - x_0 = 0\). The coefficients specified are “\(x_0\)”. y-plane: A plane perpendicular to the y axis, i.e. a surface of the form \(y - y_0 = 0\). The coefficients specified are “\(y_0\)”. z-plane: A plane perpendicular to the z axis, i.e. a surface of the form \(z - z_0 = 0\). The coefficients specified are “\(z_0\)”. plane: An arbitrary plane of the form \(Ax + By + Cz = D\). The coefficients specified are “\(A \: B \: C \: D\)”. x-cylinder: An infinite cylinder whose length is parallel to the x-axis. This is a quadratic surface of the form \((y - y_0)^2 + (z - z_0)^2 = R^2\). The coefficients specified are “\(y_0 \: z_0 \: R\)”. y-cylinder: An infinite cylinder whose length is parallel to the y-axis. This is a quadratic surface of the form \((x - x_0)^2 + (z - z_0)^2 = R^2\). The coefficients specified are “\(x_0 \: z_0 \: R\)”. z-cylinder: An infinite cylinder whose length is parallel to the z-axis. This is a quadratic surface of the form \((x - x_0)^2 + (y - y_0)^2 = R^2\). The coefficients specified are “\(x_0 \: y_0 \: R\)”. sphere: A sphere of the form \((x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = R^2\). The coefficients specified are “\(x_0 \: y_0 \: z_0 \: R\)”. x-cone: A cone parallel to the x-axis of the form \((y - y_0)^2 + (z - z_0)^2 = R^2 (x - x_0)^2\). The coefficients specified are “\(x_0 \: y_0 \: z_0 \: R^2\)”. y-cone: A cone parallel to the y-axis of the form \((x - x_0)^2 + (z - z_0)^2 = R^2 (y - y_0)^2\). The coefficients specified are “\(x_0 \: y_0 \: z_0 \: R^2\)”. z-cone: A cone parallel to the x-axis of the form \((x - x_0)^2 + (y - y_0)^2 = R^2 (z - z_0)^2\). The coefficients specified are “\(x_0 \: y_0 \: z_0 \: R^2\)”. quadric: A general quadric surface of the form \(Ax^2 + By^2 + Cz^2 + Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0\) The coefficients specified are “\(A \: B \: C \: D \: E \: F \: G \: H \: J \: K\)”.
<cell>
Element¶
Each <cell>
element can have the following attributes or sub-elements:
id: A unique integer that can be used to identify the cell.
Default: None
name: An optional string name to identify the cell in summary output files. This string is limmited to 52 characters for formatting purposes.
Default: “”
universe: The
id
of the universe that this cell is contained in.Default: 0
fill: The
id
of the universe that fills this cell.Note
If a fill is specified, no material should be given.
Default: None
material: The
id
of the material that this cell contains. If the cell should contain no material, this can also be set to “void”. A list of materials can be specified for the “distributed material” feature. This will give each unique instance of the cell its own material.Note
If a material is specified, no fill should be given.
Default: None
region: A Boolean expression of half-spaces that defines the spatial region which the cell occupies. Each half-space is identified by the unique ID of the surface prefixed by - or + to indicate that it is the negative or positive half-space, respectively. The + sign for a positive half-space can be omitted. Valid Boolean operators are parentheses, union |, complement ~, and intersection. Intersection is implicit and indicated by the presence of whitespace. The order of operator precedence is parentheses, complement, intersection, and then union.
As an example, the following code gives a cell that is the union of the negative half-space of surface 3 and the complement of the intersection of the positive half-space of surface 5 and the negative half-space of surface 2:
<cell id="1" material="1" region="-3 | ~(5 -2)" />Note
The
region
attribute/element can be omitted to make a cell fill its entire universe.Default: A region filling all space.
temperature: The temperature of the cell in Kelvin. The temperature may be used in windowed multipole Doppler broadening or interpolation of pointwise cross sections versus temperature. A list of temperatures can be specified for the “distributed temperature” feature. This will give each unique instance of the cell its own temperature.
Default: If a material default temperature is supplied, it is used. In the absence of a material default temperature, the global default temperature is used.
rotation: If the cell is filled with a universe, this element specifies the angles in degrees about the x, y, and z axes that the filled universe should be rotated. Should be given as three real numbers. For example, if you wanted to rotate the filled universe by 90 degrees about the z-axis, the cell element would look something like:
<cell fill="..." rotation="0 0 90" />The rotation applied is an intrinsic rotation whose Tait-Bryan angles are given as those specified about the x, y, and z axes respectively. That is to say, if the angles are \((\phi, \theta, \psi)\), then the rotation matrix applied is \(R_z(\psi) R_y(\theta) R_x(\phi)\) or
\[\left [ \begin{array}{ccc} \cos\theta \cos\psi & -\cos\phi \sin\psi + \sin\phi \sin\theta \cos\psi & \sin\phi \sin\psi + \cos\phi \sin\theta \cos\psi \\ \cos\theta \sin\psi & \cos\phi \cos\psi + \sin\phi \sin\theta \sin\psi & -\sin\phi \cos\psi + \cos\phi \sin\theta \sin\psi \\ -\sin\theta & \sin\phi \cos\theta & \cos\phi \cos\theta \end{array} \right ]\]Default: None
translation: If the cell is filled with a universe, this element specifies a vector that is used to translate (shift) the universe. Should be given as three real numbers.
Note
Any translation operation is applied after a rotation, if also specified.
Default: None
<lattice>
Element¶
The <lattice>
can be used to represent repeating structures (e.g. fuel pins
in an assembly) or other geometry which fits onto a rectilinear grid. Each cell
within the lattice is filled with a specified universe. A <lattice>
accepts
the following attributes or sub-elements:
id: A unique integer that can be used to identify the lattice.
name: An optional string name to identify the lattice in summary output files. This string is limited to 52 characters for formatting purposes.
Default: “”
dimension: Two or three integers representing the number of lattice cells in the x- and y- (and z-) directions, respectively.
Default: None
lower_left: The coordinates of the lower-left corner of the lattice. If the lattice is two-dimensional, only the x- and y-coordinates are specified.
Default: None
pitch: If the lattice is 3D, then three real numbers that express the distance between the centers of lattice cells in the x-, y-, and z- directions. If the lattice is 2D, then omit the third value.
Default: None
outer: The unique integer identifier of a universe that will be used to fill all space outside of the lattice. The universe will be tiled repeatedly as if it were placed in a lattice of infinite size. This element is optional.
Default: An error will be raised if a particle leaves a lattice with no outer universe.
universes: A list of the universe numbers that fill each cell of the lattice.
Default: None
Here is an example of a properly defined 2d rectangular lattice:
<lattice id="10" dimension="3 3" outer="1">
<lower_left> -1.5 -1.5 </lower_left>
<pitch> 1.0 1.0 </pitch>
<universes>
2 2 2
2 1 2
2 2 2
</universes>
</lattice>
<hex_lattice>
Element¶
The <hex_lattice>
can be used to represent repeating structures (e.g. fuel
pins in an assembly) or other geometry which naturally fits onto a hexagonal
grid or hexagonal prism grid. Each cell within the lattice is filled with a
specified universe. This lattice uses the “flat-topped hexagon” scheme where two
of the six edges are perpendicular to the y-axis. A <hex_lattice>
accepts
the following attributes or sub-elements:
id: A unique integer that can be used to identify the lattice.
name: An optional string name to identify the hex_lattice in summary output files. This string is limited to 52 characters for formatting purposes.
Default: “”
n_rings: An integer representing the number of radial ring positions in the xy-plane. Note that this number includes the degenerate center ring which only has one element.
Default: None
n_axial: An integer representing the number of positions along the z-axis. This element is optional.
Default: None
orientation: The orientation of the hexagonal lattice. The string “x” indicates that two sides of the lattice are parallel to the x-axis, whereas the string “y” indicates that two sides are parallel to the y-axis.
Default: “y”
center: The coordinates of the center of the lattice. If the lattice does not have axial sections then only the x- and y-coordinates are specified.
Default: None
pitch: If the lattice is 3D, then two real numbers that express the distance between the centers of lattice cells in the xy-plane and along the z-axis, respectively. If the lattice is 2D, then omit the second value.
Default: None
outer: The unique integer identifier of a universe that will be used to fill all space outside of the lattice. The universe will be tiled repeatedly as if it were placed in a lattice of infinite size. This element is optional.
Default: An error will be raised if a particle leaves a lattice with no outer universe.
universes: A list of the universe numbers that fill each cell of the lattice.
Default: None
Here is an example of a properly defined 2d hexagonal lattice:
<hex_lattice id="10" n_rings="3" outer="1">
<center> 0.0 0.0 </center>
<pitch> 1.0 </pitch>
<universes>
202
202 202
202 202 202
202 202
202 101 202
202 202
202 202 202
202 202
202
</universes>
</hex_lattice>
Materials Specification – materials.xml¶
<cross_sections>
Element¶
The <cross_sections>
element has no attributes and simply indicates the path
to an XML cross section listing file (usually named cross_sections.xml). If this
element is absent from the settings.xml file, the
OPENMC_CROSS_SECTIONS
environment variable will be used to find the
path to the XML cross section listing when in continuous-energy mode, and the
OPENMC_MG_CROSS_SECTIONS
environment variable will be used in
multi-group mode.
<material>
Element¶
Each material
element can have the following attributes or sub-elements:
id: A unique integer that can be used to identify the material.
name: An optional string name to identify the material in summary output files. This string is limited to 52 characters for formatting purposes.
Default: “”
depletable: Boolean value indicating whether the material is depletable.
volume: Volume of the material in cm^3.
temperature: Temperature of the material in Kelvin.
Default: If a material default temperature is not given and a cell temperature is not specified, the global default temperature is used.
density: An element with attributes/sub-elements called
value
andunits
. Thevalue
attribute is the numeric value of the density while theunits
can be “g/cm3”, “kg/m3”, “atom/b-cm”, “atom/cm3”, or “sum”. The “sum” unit indicates that values appearing inao
orwo
attributes for<nuclide>
and<element>
sub-elements are to be interpreted as absolute nuclide/element densities in atom/b-cm or g/cm3, and the total density of the material is taken as the sum of all nuclides/elements. The “macro” unit is used with amacroscopic
quantity to indicate that the density is already included in the library and thus not needed here. However, if a value is provided for thevalue
, then this is treated as a number density multiplier on the macroscopic cross sections in the multi-group data. This can be used, for example, when perturbing the density slightly.Default: None
Note
A
macroscopic
quantity can not be used in conjunction with anuclide
,element
, orsab
quantity.nuclide: An element with attributes/sub-elements called
name
, andao
orwo
. Thename
attribute is the name of the cross-section for a desired nuclide. Finally, theao
andwo
attributes specify the atom or weight percent of that nuclide within the material, respectively. One example would be as follows:<nuclide name="H1" ao="2.0" /> <nuclide name="O16" ao="1.0" />Note
If one nuclide is specified in atom percent, all others must also be given in atom percent. The same applies for weight percentages.
Default: None
sab: Associates an S(a,b) table with the material. This element has an attribute/sub-element called
name
. Thename
attribute is the name of the S(a,b) table that should be associated with the material. There is also an optionalfraction
element which indicates what fraction of the relevant nuclides will be affected by the S(a,b) table (e.g. which fraction of a material is crystalline versus amorphous).fraction
defaults to unity.Default: None
Note
This element is not used in the multi-group <energy_mode> Element.
isotropic: The
isotropic
element indicates a list of nuclides for which elastic scattering should be treated as though it were isotropic in the laboratory system. This element may be most useful when using OpenMC to compute multi-group cross-sections for deterministic transport codes and to quantify the effects of anisotropic scattering.Default: No nuclides are treated as have isotropic elastic scattering.
Note
This element is not used in the multi-group <energy_mode> Element.
macroscopic: The
macroscopic
element is similar to thenuclide
element, but, recognizes that some multi-group libraries may be providing material specific macroscopic cross sections instead of always providing nuclide specific data like in the continuous-energy case. To that end, the macroscopic element has one attribute/sub-element calledname
. Thename
attribute is the name of the cross-section for a desired nuclide. One example would be as follows:<macroscopic name="UO2" />Note
This element is only used in the multi-group <energy_mode> Element.
Default: None
Settings Specification – settings.xml¶
All simulation parameters and miscellaneous options are specified in the settings.xml file.
<batches>
Element¶
The <batches>
element indicates the total number of batches to execute,
where each batch corresponds to a tally realization. In a fixed source
calculation, each batch consists of a number of source particles. In an
eigenvalue calculation, each batch consists of one or many fission source
iterations (generations), where each generation itself consists of a number of
source neutrons.
Default: None
<confidence_intervals>
Element¶
The <confidence_intervals>
element has no attributes and has an accepted
value of “true” or “false”. If set to “true”, uncertainties on tally results
will be reported as the half-width of the 95% two-sided confidence interval. If
set to “false”, uncertainties on tally results will be reported as the sample
standard deviation.
Default: false
<create_fission_neutrons>
Element¶
The <create_fission_neutrons>
element indicates whether fission neutrons
should be created or not. If this element is set to “true”, fission neutrons
will be created; otherwise the fission is treated as capture and no fission
neutron will be created. Note that this option is only applied to fixed source
calculation. For eigenvalue calculation, fission will always be treated as real
fission.
Default: true
<cutoff>
Element¶
The <cutoff>
element indicates two kinds of cutoffs. The first is the weight
cutoff used below which particles undergo Russian roulette. Surviving particles
are assigned a user-determined weight. Note that weight cutoffs and Russian
rouletting are not turned on by default. The second is the energy cutoff which
is used to kill particles under certain energy. The energy cutoff should not be
used unless you know particles under the energy are of no importance to results
you care. This element has the following attributes/sub-elements:
weight: The weight below which particles undergo Russian roulette.
Default: 0.25
weight_avg: The weight that is assigned to particles that are not killed after Russian roulette.
Default: 1.0
energy_neutron: The energy under which neutrons will be killed.
Default: 0.0
energy_photon: The energy under which photons will be killed.
Default: 1000.0
energy_electron: The energy under which electrons will be killed.
Default: 0.0
energy_positron: The energy under which positrons will be killed.
Default: 0.0
<dagmc>
Element¶
When the DAGMC mode is enabled, the OpenMC geometry will be read from the file
dagmc.h5m
. If a geometry.xml file is present with
dagmc
set to true
, it will be ignored.
<delayed_photon_scaling>
¶
Determines whether to scale the fission photon yield to account for delayed photon energy. The photon yields are scaled as (EGP + EGD)/EGP where EGP and EGD are the prompt and delayed photon components of energy release, respectively, from MF=1, MT=458 on an ENDF evaluation.
Default: true
<electron_treatment>
Element¶
When photon transport is enabled, the <electron_treatment>
element tells
OpenMC whether to deposit all energy from electrons locally (led
) or create
secondary bremsstrahlung photons (ttb
).
Default: ttb
<energy_mode>
Element¶
The <energy_mode>
element tells OpenMC if the run-mode should be
continuous-energy or multi-group. Options for entry are: continuous-energy
or multi-group
.
Default: continuous-energy
<entropy_mesh>
Element¶
The <entropy_mesh>
element indicates the ID of a mesh that is to be used for
calculating Shannon entropy. The mesh should cover all possible fissionable
materials in the problem and is specified using a <mesh> Element.
<event_based>
¶
Determines whether to use event-based parallelism instead of the default history-based parallelism.
Default: false
<generations_per_batch>
Element¶
The <generations_per_batch>
element indicates the number of total fission
source iterations per batch for an eigenvalue calculation. This element is
ignored for all run modes other than “eigenvalue”.
Default: 1
<inactive>
Element¶
The <inactive>
element indicates the number of inactive batches used in a
k-eigenvalue calculation. In general, the starting fission source iterations in
an eigenvalue calculation can not be used to contribute to tallies since the
fission source distribution and eigenvalue are generally not converged
immediately. This element is ignored for all run modes other than “eigenvalue”.
Default: 0
<keff_trigger>
Element¶
The <keff_trigger>
element (ignored for all run modes other than
“eigenvalue”.) specifies a precision trigger on the combined
\(k_{eff}\). The trigger is a convergence criterion on the uncertainty of
the estimated eigenvalue. It has the following attributes/sub-elements:
type: The type of precision trigger. Accepted options are “variance”, “std_dev”, and “rel_err”.
variance: Variance of the batch mean \(\sigma^2\) std_dev: Standard deviation of the batch mean \(\sigma\) rel_err: Relative error of the batch mean \(\frac{\sigma}{\mu}\) Default: None
threshold: The precision trigger’s convergence criterion for the combined \(k_{eff}\).
Default: None
Note
See section on the <trigger> Element for more information.
<log_grid_bins>
Element¶
The <log_grid_bins>
element indicates the number of bins to use for the
logarithmic-mapped energy grid. Using more bins will result in energy grid
searches over a smaller range at the expense of more memory. The default is
based on the recommended value in LA-UR-14-24530.
Default: 8000
Note
This element is not used in the multi-group <energy_mode> Element.
<material_cell_offsets>
¶
By default, OpenMC will count the number of instances of each cell filled with a
material and generate “offset tables” that are used for cell instance tallies.
The <material_cell_offsets>
element allows a user to override this default
setting and turn off the generation of offset tables, if desired, by setting it
to false.
Default: true
<max_particles_in_flight>
Element¶
This element indicates the number of neutrons to run in flight concurrently when using event-based parallelism. A higher value uses more memory, but may be more efficient computationally.
Default: 100000
<max_order>
Element¶
The <max_order>
element allows the user to set a maximum scattering order
to apply to every nuclide/material in the problem. That is, if the data
library has \(P_3\) data available, but <max_order>
was set to 1
,
then, OpenMC will only use up to the \(P_1\) data.
Default: Use the maximum order in the data library
Note
This element is not used in the continuous-energy <energy_mode> Element.
<mesh>
Element¶
The <mesh>
element describes a mesh that is used either for calculating
Shannon entropy, applying the uniform fission site method, or in tallies. For
Shannon entropy meshes, the mesh should cover all possible fissionable materials
in the problem. It has the following attributes/sub-elements:
id: A unique integer that is used to identify the mesh.
dimension: The number of mesh cells in the x, y, and z directions, respectively.
Default: If this tag is not present, the number of mesh cells is automatically determined by the code.
lower_left: The Cartesian coordinates of the lower-left corner of the mesh.
Default: None
upper_right: The Cartesian coordinates of the upper-right corner of the mesh.
Default: None
<no_reduce>
Element¶
The <no_reduce>
element has no attributes and has an accepted value of
“true” or “false”. If set to “true”, all user-defined tallies and global tallies
will not be reduced across processors in a parallel calculation. This means that
the accumulate score in one batch on a single processor is considered as an
independent realization for the tally random variable. For a problem with large
tally data, this option can significantly improve the parallel efficiency.
Default: false
<output>
Element¶
The <output>
element determines what output files should be written to disk
during the run. The sub-elements are described below, where “true” will write
out the file and “false” will not.
summary: Writes out an HDF5 summary file describing all of the user input files that were read in.
Default: true
tallies: Write out an ASCII file of tally results.
Default: true
Note
The tally results will always be written to a binary/HDF5 state point file.
path: Absolute or relative path where all output files should be written to. The specified path must exist or else OpenMC will abort.
Default: Current working directory
<particles>
Element¶
This element indicates the number of neutrons to simulate per fission source iteration when a k-eigenvalue calculation is performed or the number of particles per batch for a fixed source simulation.
Default: None
<photon_transport>
Element¶
The <photon_transport>
element determines whether photon transport is
enabled. This element has no attributes or sub-elements and can be set to
either “false” or “true”.
Default: false
<ptables>
Element¶
The <ptables>
element determines whether probability tables should be used
in the unresolved resonance range if available. This element has no attributes
or sub-elements and can be set to either “false” or “true”.
Default: true
Note
This element is not used in the multi-group <energy_mode> Element.
<resonance_scattering>
Element¶
The resonance_scattering
element indicates to OpenMC that a method be used
to properly account for resonance elastic scattering (typically for nuclides
with Z > 40). This element can contain one or more of the following attributes
or sub-elements:
enable: Indicates whether a resonance elastic scattering method should be turned on. Accepts values of “true” or “false”.
Default: If the
<resonance_scattering>
element is present, “true”.method: Which resonance elastic scattering method is to be applied: “rvs” (relative velocity sampling) or “dbrc” (Doppler broadening rejection correction). Descriptions of each of these methods are documented here.
Default: “rvs”
energy_min: The energy in eV above which the resonance elastic scattering method should be applied.
Default: 0.01 eV
energy_max: The energy in eV below which the resonance elastic scattering method should be applied.
Default: 1000.0 eV
nuclides: A list of nuclides to which the resonance elastic scattering method should be applied.
Default: If
<resonance_scattering>
is present but the<nuclides>
sub-element is not given, the method is applied to all nuclides with 0 K elastic scattering data present.Note
If the
resonance_scattering
element is not given, the free gas, constant cross section scattering model, which has historically been used by Monte Carlo codes to sample target velocities, is used to treat the target motion of all nuclides. Ifresonance_scattering
is present, the constant cross section method is applied belowenergy_min
and the target-at-rest (asymptotic) kernel is used aboveenergy_max
.Note
This element is not used in the multi-group <energy_mode> Element.
<run_mode>
Element¶
The <run_mode>
element indicates which run mode should be used when OpenMC
is executed. This element has no attributes or sub-elements and can be set to
“eigenvalue”, “fixed source”, “plot”, “volume”, or “particle restart”.
Default: None
<seed>
Element¶
The seed
element is used to set the seed used for the linear congruential
pseudo-random number generator.
Default: 1
<source>
Element¶
The source
element gives information on an external source distribution to
be used either as the source for a fixed source calculation or the initial
source guess for criticality calculations. Multiple <source>
elements may be
specified to define different source distributions. Each one takes the following
attributes/sub-elements:
strength: The strength of the source. If multiple sources are present, the source strength indicates the relative probability of choosing one source over the other.
Default: 1.0
particle: The source particle type, either
neutron
orphoton
.Default: neutron
file: If this attribute is given, it indicates that the source is to be read from a binary source file whose path is given by the value of this element. Note, the number of source sites needs to be the same as the number of particles simulated in a fission source generation.
Default: None
library: If this attribute is given, it indicates that the source is to be instantiated from an externally compiled source function. This source can be as complex as is required to define the source for your problem. The library has a few basic requirements:
- It must contain a class that inherits from
openmc::Source
;- The class must implement a function called
sample()
;- There must be an
openmc_create_source()
function that creates the source as a unique pointer. This function can be used to pass parameters through to the source from the XML, if needed.More documentation on how to build sources can be found in Custom Sources.
Default: None
parameters: If this attribute is given, it provides the parameters to pass through to the class generated using the
library
parameter . More documentation on how to build parametrized sources can be found in Custom Parameterized Sources.Default: None
space: An element specifying the spatial distribution of source sites. This element has the following attributes:
type: The type of spatial distribution. Valid options are “box”, “fission”, “point”, “cartesian”, “cylindrical”, and “spherical”. A “box” spatial distribution has coordinates sampled uniformly in a parallelepiped. A “fission” spatial distribution samples locations from a “box” distribution but only locations in fissionable materials are accepted. A “point” spatial distribution has coordinates specified by a triplet. A “cartesian” spatial distribution specifies independent distributions of x-, y-, and z-coordinates. A “cylindrical” spatial distribution specifies independent distributions of r-, phi-, and z-coordinates where phi is the azimuthal angle and the origin for the cylindrical coordinate system is specified by origin. A “spherical” spatial distribution specifies independent distributions of r-, theta-, and phi-coordinates where theta is the angle with respect to the z-axis, phi is the azimuthal angle, and the sphere is centered on the coordinate (x0,y0,z0).
Default: None
parameters: For a “box” or “fission” spatial distribution,
parameters
should be given as six real numbers, the first three of which specify the lower-left corner of a parallelepiped and the last three of which specify the upper-right corner. Source sites are sampled uniformly through that parallelepiped.For a “point” spatial distribution,
parameters
should be given as three real numbers which specify the (x,y,z) location of an isotropic point source.For an “cartesian” distribution, no parameters are specified. Instead, the
x
,y
, andz
elements must be specified.For a “cylindrical” distribution, no parameters are specified. Instead, the
r
,phi
,z
, andorigin
elements must be specified.For a “spherical” distribution, no parameters are specified. Instead, the
r
,theta
,phi
, andorigin
elements must be specified.Default: None
x: For an “cartesian” distribution, this element specifies the distribution of x-coordinates. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
y: For an “cartesian” distribution, this element specifies the distribution of y-coordinates. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
z: For both “cartesian” and “cylindrical” distributions, this element specifies the distribution of z-coordinates. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
r: For “cylindrical” and “spherical” distributions, this element specifies the distribution of r-coordinates (cylindrical radius and spherical radius, respectively). The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
theta: For a “spherical” distribution, this element specifies the distribution of theta-coordinates. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
phi: For “cylindrical” and “spherical” distributions, this element specifies the distribution of phi-coordinates. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
origin: For “cylindrical and “spherical” distributions, this element specifies the coordinates for the origin of the coordinate system.
angle: An element specifying the angular distribution of source sites. This element has the following attributes:
type: The type of angular distribution. Valid options are “isotropic”, “monodirectional”, and “mu-phi”. The angle of the particle emitted from a source site is isotropic if the “isotropic” option is given. The angle of the particle emitted from a source site is the direction specified in the
reference_uvw
element/attribute if “monodirectional” option is given. The “mu-phi” option produces directions with the cosine of the polar angle and the azimuthal angle explicitly specified.Default: isotropic
reference_uvw: The direction from which the polar angle is measured. Represented by the x-, y-, and z-components of a unit vector. For a monodirectional distribution, this defines the direction of all sampled particles.
mu: An element specifying the distribution of the cosine of the polar angle. Only relevant when the type is “mu-phi”. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
phi: An element specifying the distribution of the azimuthal angle. Only relevant when the type is “mu-phi”. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
energy: An element specifying the energy distribution of source sites. The necessary sub-elements/attributes are those of a univariate probability distribution (see the description in Univariate Probability Distributions).
Default: Watt spectrum with \(a\) = 0.988 MeV and \(b\) = 2.249 MeV -1
write_initial: An element specifying whether to write out the initial source bank used at the beginning of the first batch. The output file is named “initial_source.h5”
Default: false
Univariate Probability Distributions¶
Various components of a source distribution involve probability distributions of a single random variable, e.g. the distribution of the energy, the distribution of the polar angle, and the distribution of x-coordinates. Each of these components supports the same syntax with an element whose tag signifies the variable and whose sub-elements/attributes are as follows:
type: | The type of the distribution. Valid options are “uniform”, “discrete”, “tabular”, “maxwell”, and “watt”. The “uniform” option produces variates sampled from a uniform distribution over a finite interval. The “discrete” option produces random variates that can assume a finite number of values (i.e., a distribution characterized by a probability mass function). The “tabular” option produces random variates sampled from a tabulated distribution where the density function is either a histogram or linearly-interpolated between tabulated points. The “watt” option produces random variates is sampled from a Watt fission spectrum (only used for energies). The “maxwell” option produce variates sampled from a Maxwell fission spectrum (only used for energies). Default: None |
---|---|
parameters: | For a “uniform” distribution, For a “discrete” or “tabular” distribution, For a “watt” distribution, For a “maxwell” distribution, Note The above format should be used even when using the multi-group <energy_mode> Element. |
interpolation: | For a “tabular” distribution, Default: histogram |
<state_point>
Element¶
The <state_point>
element indicates at what batches a state point file
should be written. A state point file can be used to restart a run or to get
tally results at any batch. The default behavior when using this tag is to
write out the source bank in the state_point file. This behavior can be
customized by using the <source_point>
element. This element has the
following attributes/sub-elements:
batches: A list of integers separated by spaces indicating at what batches a state point file should be written.
Default: Last batch only
<source_point>
Element¶
The <source_point>
element indicates at what batches the source bank
should be written. The source bank can be either written out within a state
point file or separately in a source point file. This element has the following
attributes/sub-elements:
batches: A list of integers separated by spaces indicating at what batches a state point file should be written. It should be noted that if the
separate
attribute is not set to “true”, this list must be a subset of state point batches.Default: Last batch only
separate: If this element is set to “true”, a separate binary source point file will be written. Otherwise, the source sites will be written in the state point directly.
Default: false
write: If this element is set to “false”, source sites are not written to the state point or source point file. This can substantially reduce the size of state points if large numbers of particles per batch are used.
Default: true
overwrite_latest: If this element is set to “true”, a source point file containing the source bank will be written out to a separate file named
source.binary
orsource.h5
depending on if HDF5 is enabled. This file will be overwritten at every single batch so that the latest source bank will be available. It should be noted that a user can set both this element to “true” and specify batches to write a permanent source bank.Default: false
<surf_src_read>
Element¶
The <surf_src_read>
element specifies a surface source file for OpenMC to
read source bank for initializing histories.
This element has the following attributes/sub-elements:
path: Absolute or relative path to a surface source file to read in source bank.
Default:
surface_source.h5
in current working directory
<surf_src_write>
Element¶
The <surf_src_write>
element triggers OpenMC to bank particles crossing
certain surfaces and write out the source bank in a separate file called
surface_source.h5
.
This element has the following attributes/sub-elements:
surface_ids: A list of integers separated by spaces indicating the unique IDs of surfaces for which crossing particles will be banked.
Default: None
max_particles: An integer indicating the maximum number of particles to be banked on specified surfaces per processor. The size of source bank in
surface_source.h5
is limited to this value times the number of processors.Default: None
<survival_biasing>
Element¶
The <survival_biasing>
element has no attributes and has an accepted value
of “true” or “false”. If set to “true”, this option will enable the use of
survival biasing, otherwise known as implicit capture or absorption.
Default: false
<tabular_legendre>
Element¶
The optional <tabular_legendre>
element specifies how the multi-group
Legendre scattering kernel is represented if encountered in a multi-group
problem. Specifically, the options are to either convert the Legendre
expansion to a tabular representation or leave it as a set of Legendre
coefficients. Converting to a tabular representation will cost memory but can
allow for a decrease in runtime compared to leaving as a set of Legendre
coefficients. This element has the following attributes/sub-elements:
enable: This attribute/sub-element denotes whether or not the conversion of a Legendre scattering expansion to the tabular format should be performed or not. A value of “true” means the conversion should be performed, “false” means it will not.
Default: true
num_points: If the conversion is to take place the number of tabular points is required. This attribute/sub-element allows the user to set the desired number of points.
Default: 33
Note
This element is only used in the multi-group <energy_mode> Element.
<temperature_default>
Element¶
The <temperature_default>
element specifies a default temperature in Kelvin
that is to be applied to cells in the absence of an explicit cell temperature or
a material default temperature.
Default: 293.6 K
<temperature_method>
Element¶
The <temperature_method>
element has an accepted value of “nearest” or
“interpolation”. A value of “nearest” indicates that for each
cell, the nearest temperature at which cross sections are given is to be
applied, within a given tolerance (see <temperature_tolerance> Element). A value of
“interpolation” indicates that cross sections are to be linear-linear
interpolated between temperatures at which nuclear data are present (see
Temperature Treatment).
Default: “nearest”
<temperature_multipole>
Element¶
The <temperature_multipole>
element toggles the windowed multipole
capability on or off. If this element is set to “True” and the relevant data is
available, OpenMC will use the windowed multipole method to evaluate and Doppler
broaden cross sections in the resolved resonance range. This override other
methods like “nearest” and “interpolation” in the resolved resonance range.
Default: False
<temperature_range>
Element¶
The <temperature_range>
element specifies a minimum and maximum temperature
in Kelvin above and below which cross sections should be loaded for all nuclides
and thermal scattering tables. This can be used for multi-physics simulations
where the temperatures might change from one iteration to the next.
Default: None
<temperature_tolerance>
Element¶
The <temperature_tolerance>
element specifies a tolerance in Kelvin that is
to be applied when the “nearest” temperature method is used. For example, if a
cell temperature is 340 K and the tolerance is 15 K, then the closest
temperature in the range of 325 K to 355 K will be used to evaluate cross
sections.
Default: 10 K
<trace>
Element¶
The <trace>
element can be used to print out detailed information about a
single particle during a simulation. This element should be followed by three
integers: the batch number, generation number, and particle number.
Default: None
<track>
Element¶
The <track>
element specifies particles for which OpenMC will output binary
files describing particle position at every step of its transport. This element
should be followed by triplets of integers. Each triplet describes one
particle. The integers in each triplet specify the batch number, generation
number, and particle number, respectively.
Default: None
<trigger>
Element¶
OpenMC includes tally precision triggers which allow the user to define
uncertainty thresholds on \(k_{eff}\) in the <keff_trigger>
subelement
of settings.xml
, and/or tallies in tallies.xml
. When using triggers,
OpenMC will run until it completes as many batches as defined by <batches>
.
At this point, the uncertainties on all tallied values are computed and compared
with their corresponding trigger thresholds. If any triggers have not been met,
OpenMC will continue until either all trigger thresholds have been satisfied or
<max_batches>
has been reached.
The <trigger>
element provides an active “toggle switch” for tally
precision trigger(s), the maximum number of batches and the batch interval. It
has the following attributes/sub-elements:
active: This determines whether or not to use trigger(s). Trigger(s) are used when this tag is set to “true”.
max_batches: This describes the maximum number of batches allowed when using trigger(s).
Note
When max_batches is set, the number of
batches
shown in the<batches>
element represents minimum number of batches to simulate when using the trigger(s).batch_interval: This tag describes the number of batches in between convergence checks. OpenMC will check if the trigger has been reached at each batch defined by
batch_interval
after the minimum number of batches is reached.Note
If this tag is not present, the
batch_interval
is predicted dynamically by OpenMC for each convergence check. The predictive model assumes no correlation between fission sources distributions from batch-to-batch. This assumption is reasonable for fixed source and small criticality calculations, but is very optimistic for highly coupled full-core reactor problems.
<ufs_mesh>
Element¶
The <ufs_mesh>
element indicates the ID of a mesh that is used for
re-weighting source sites at every generation based on the uniform fission site
methodology described in Kelly et al., “MC21 Analysis of the Nuclear Energy
Agency Monte Carlo Performance Benchmark Problem,” Proceedings of Physor 2012,
Knoxville, TN (2012). The mesh should cover all possible fissionable materials
in the problem and is specified using a <mesh> Element.
<verbosity>
Element¶
The <verbosity>
element tells the code how much information to display to
the standard output. A higher verbosity corresponds to more information being
displayed. The text of this element should be an integer between between 1
and 10. The verbosity levels are defined as follows:
1: don’t display any output 2: only show OpenMC logo 3: all of the above + headers 4: all of the above + results 5: all of the above + file I/O 6: all of the above + timing statistics and initialization messages 7: all of the above + \(k\) by generation 9: all of the above + indicate when each particle starts 10: all of the above + event information Default: 7
<volume_calc>
Element¶
The <volume_calc>
element indicates that a stochastic volume calculation
should be run at the beginning of the simulation. This element has the following
sub-elements/attributes:
cells: The unique IDs of cells for which the volume should be estimated.
Default: None
samples: The number of samples used to estimate volumes.
Default: None
lower_left: The lower-left Cartesian coordinates of a bounding box that is used to sample points within.
Default: None
upper_right: The upper-right Cartesian coordinates of a bounding box that is used to sample points within.
Default: None
Tallies Specification – tallies.xml¶
The tallies.xml file allows the user to tell the code what results he/she is interested in, e.g. the fission rate in a given cell or the current across a given surface. There are two pieces of information that determine what quantities should be scored. First, one needs to specify what region of phase space should count towards the tally and secondly, the actual quantity to be scored also needs to be specified. The first set of parameters we call filters since they effectively serve to filter events, allowing some to score and preventing others from scoring to the tally.
The structure of tallies in OpenMC is flexible in that any combination of filters can be used for a tally. The following types of filter are available: cell, universe, material, surface, birth region, pre-collision energy, post-collision energy, and an arbitrary structured mesh.
The five valid elements in the tallies.xml file are <tally>
, <filter>
,
<mesh>
, <derivative>
, and <assume_separate>
.
<tally>
Element¶
The <tally>
element accepts the following sub-elements:
name: An optional string name to identify the tally in summary output files. This string is limited to 52 characters for formatting purposes.
Default: “”
filters: A space-separated list of the IDs of
filter
elements.nuclides: If specified, the scores listed will be for particular nuclides, not the summation of reactions from all nuclides. The format for nuclides should be [Atomic symbol]-[Mass number], e.g. “U-235”. The reaction rate for all nuclides can be obtained with “total”. For example, to obtain the reaction rates for U-235, Pu-239, and all nuclides in a material, this element should be:
<nuclides>U-235 Pu-239 total</nuclides>Default: total
estimator: The estimator element is used to force the use of either
analog
,collision
, ortracklength
tally estimation.analog
is generally the least efficient though it can be used with every score type.tracklength
is generally the most efficient, but neithertracklength
norcollision
can be used to score a tally that requires post-collision information. For example, a scattering tally with outgoing energy filters cannot be used withtracklength
orcollision
because the code will not know the outgoing energy distribution.Default:
tracklength
but will revert toanalog
if necessary.scores: A space-separated list of the desired responses to be accumulated. A full list of valid scores can be found in the user’s guide.
trigger: Precision trigger applied to all filter bins and nuclides for this tally. It must specify the trigger’s type, threshold and scores to which it will be applied. It has the following attributes/sub-elements:
type: The type of the trigger. Accepted options are “variance”, “std_dev”, and “rel_err”.
variance: Variance of the batch mean \(\sigma^2\) std_dev: Standard deviation of the batch mean \(\sigma\) rel_err: Relative error of the batch mean \(\frac{\sigma}{\mu}\) Default: None
threshold: The precision trigger’s convergence criterion for tallied values.
Default: None
scores: The score(s) in this tally to which the trigger should be applied.
Note
The
scores
intrigger
must have been defined inscores
intally
. An optional “all” may be used to select all scores in this tally.Default: “all”
derivative: The id of a
derivative
element. This derivative will be applied to all scores in the tally. Differential tallies are currently only implemented for collision and analog estimators.Default: None
<filter>
Element¶
Filters can be used to modify tally behavior. Most tallies (e.g. cell
,
energy
, and material
) restrict the tally so that only particles
within certain regions of phase space contribute to the tally. Others
(e.g. delayedgroup
and energyfunction
) can apply some other function
to the scored values. The filter
element has the following
attributes/sub-elements:
type: The type of the filter. Accepted options are “cell”, “cellfrom”, “cellborn”, “surface”, “material”, “universe”, “energy”, “energyout”, “mu”, “polar”, “azimuthal”, “mesh”, “distribcell”, “delayedgroup”, “energyfunction”, and “particle”. bins: A description of the bins for each type of filter can be found in Filter Types. energy: energyfunction
filters multiply tally scores by an arbitrary function. The function is described by a piecewise linear-linear set of (energy, y) values. This entry specifies the energy values. The function will be evaluated as zero outside of the bounds of this energy grid. (Only used forenergyfunction
filters)y: energyfunction
filters multiply tally scores by an arbitrary function. The function is described by a piecewise linear-linear set of (energy, y) values. This entry specifies the y values. (Only used forenergyfunction
filters)
Filter Types¶
For each filter type, the following table describes what the bins
attribute
should be set to:
cell: | A list of unique IDs for cells in which the tally should be accumulated. |
---|---|
surface: | This filter allows the tally to be scored when crossing a surface. A list of surface IDs should be given. By default, net currents are tallied, and to tally a partial current from one cell to another, this should be used in combination with a cell or cell_from filter that defines the other cell. This filter should not be used in combination with a meshfilter. |
cellfrom: | This filter allows the tally to be scored when crossing a surface and the particle came from a specified cell. A list of cell IDs should be given. To tally a partial current from a cell to another, this filter should be used in combination with a cell filter, to define the other cell. This filter should not be used in combination with a meshfilter. |
cellborn: | This filter allows the tally to be scored to only when particles were originally born in a specified cell. A list of cell IDs should be given. |
material: | A list of unique IDs for materials in which the tally should be accumulated. |
universe: | A list of unique IDs for universes in which the tally should be accumulated. |
energy: | In continuous-energy mode, this filter should be provided as a monotonically increasing list of bounding pre-collision energies for a number of groups. For example, if this filter is specified as <filter type="energy" bins="0.0 1.0e6 20.0e6" />
then two energy bins will be created, one with energies between 0 and 1 MeV and the other with energies between 1 and 20 MeV. In multi-group mode the bins provided must match group edges defined in the multi-group library. |
energyout: | In continuous-energy mode, this filter should be provided as a monotonically increasing list of bounding post-collision energies for a number of groups. For example, if this filter is specified as <filter type="energyout" bins="0.0 1.0e6 20.0e6" />
then two post-collision energy bins will be created, one with energies between 0 and 1 MeV and the other with energies between 1 and 20 MeV. In multi-group mode the bins provided must match group edges defined in the multi-group library. |
mu: | A monotonically increasing list of bounding post-collision cosines of the change in a particle’s angle (i.e., \(\mu = \hat{\Omega} \cdot \hat{\Omega}'\)), which represents a portion of the possible values of \([-1,1]\). For example, spanning all of \([-1,1]\) with five equi-width bins can be specified as: <filter type="mu" bins="-1.0 -0.6 -0.2 0.2 0.6 1.0" />
Alternatively, if only one value is provided as a bin, OpenMC will interpret this to mean the complete range of \([-1,1]\) should be automatically subdivided in to the provided value for the bin. That is, the above example of five equi-width bins spanning \([-1,1]\) can be instead written as: <filter type="mu" bins="5" />
|
polar: | A monotonically increasing list of bounding particle polar angles which represents a portion of the possible values of \([0,\pi]\). For example, spanning all of \([0,\pi]\) with five equi-width bins can be specified as: <filter type="polar" bins="0.0 0.6283 1.2566 1.8850 2.5132 3.1416"/>
Alternatively, if only one value is provided as a bin, OpenMC will interpret this to mean the complete range of \([0,\pi]\) should be automatically subdivided in to the provided value for the bin. That is, the above example of five equi-width bins spanning \([0,\pi]\) can be instead written as: <filter type="polar" bins="5" />
|
azimuthal: | A monotonically increasing list of bounding particle azimuthal angles which represents a portion of the possible values of \([-\pi,\pi)\). For example, spanning all of \([-\pi,\pi)\) with two equi-width bins can be specified as: <filter type="azimuthal" bins="0.0 3.1416 6.2832" />
Alternatively, if only one value is provided as a bin, OpenMC will interpret this to mean the complete range of \([-\pi,\pi)\) should be automatically subdivided in to the provided value for the bin. That is, the above example of five equi-width bins spanning \([-\pi,\pi)\) can be instead written as: <filter type="azimuthal" bins="2" />
|
mesh: | The unique ID of a mesh to be tallied over. |
distribcell: | The single cell which should be tallied uniquely for all instances. Note The distribcell filter will take a single cell ID and will tally each unique occurrence of that cell separately. This filter will not accept more than one cell ID. It is not recommended to combine this filter with a cell or mesh filter. |
delayedgroup: | A list of delayed neutron precursor groups for which the tally should be accumulated. For instance, to tally to all 6 delayed groups in the ENDF/B-VII.1 library the filter is specified as: <filter type="delayedgroup" bins="1 2 3 4 5 6" />
|
energyfunction: |
|
particle: | A list of integers indicating the type of particles to tally (‘neutron’ = 1, ‘photon’ = 2, ‘electron’ = 3, ‘positron’ = 4). |
<mesh>
Element¶
If a mesh is desired as a filter for a tally, it must be specified in a separate
element with the tag name <mesh>
. This element has the following
attributes/sub-elements:
type: The type of mesh. This can be either “regular”, “rectilinear”, or “unstructured”. dimension: The number of mesh cells in each direction. (For regular mesh only.) lower_left: The lower-left corner of the structured mesh. If only two coordinates are given, it is assumed that the mesh is an x-y mesh. (For regular mesh only.) upper_right: The upper-right corner of the structured mesh. If only two coordinates are given, it is assumed that the mesh is an x-y mesh. (For regular mesh only.) width: The width of mesh cells in each direction. (For regular mesh only.) x_grid: The mesh divisions along the x-axis. (For rectilinear mesh only.) y_grid: The mesh divisions along the y-axis. (For rectilinear mesh only.) z_grid: The mesh divisions along the z-axis. (For rectilinear mesh only.) library: The mesh library used to represent an unstructured mesh. This can be either “moab” or “libmesh”. (For unstructured mesh only.) filename: The name of the mesh file to be loaded at runtime. (For unstructured mesh only.) Note
One of
<upper_right>
or<width>
must be specified, but not both (even if they are consistent with one another).
<derivative>
Element¶
OpenMC can take the first-order derivative of many tallies with respect to material perturbations. It works by propagating a derivative through the transport equation. Essentially, OpenMC keeps track of how each particle’s weight would change as materials are perturbed, and then accounts for that weight change in the tallies. Note that this assumes material perturbations are small enough not to change the distribution of fission sites. This element has the following attributes/sub-elements:
id: A unique integer that can be used to identify the derivative. variable: The independent variable of the derivative. Accepted options are “density”, “nuclide_density”, and “temperature”. A “density” derivative will give the derivative with respect to the density of the material in [g / cm^3]. A “nuclide_density” derivative will give the derivative with respect to the density of a particular nuclide in units of [atom / b / cm]. A “temperature” derivative is with respect to a material temperature in units of [K]. The temperature derivative requires windowed multipole to be turned on. Note also that the temperature derivative only accounts for resolved resonance Doppler broadening. It does not account for thermal expansion, S(a, b) scattering, resonance scattering, or unresolved Doppler broadening. material: The perturbed material. (Necessary for all derivative types) nuclide: The perturbed nuclide. (Necessary only for “nuclide_density”)
<assume_separate>
Element¶
In cases where the user needs to specify many different tallies each of which are spatially separate, this tag can be used to cut down on some of the tally overhead. The effect of assuming all tallies are spatially separate is that once one tally is scored to, the same event is assumed not to score to any other tallies. This element should be followed by “true” or “false”.
Warning
If used incorrectly, the assumption that all tallies are spatially separate can lead to incorrect results.
Default: false
Geometry Plotting Specification – plots.xml¶
Basic plotting capabilities are available in OpenMC by creating a plots.xml file
and subsequently running with the --plot
command-line flag. The root element
of the plots.xml is simply <plots>
and any number output plots can be
defined with <plot>
sub-elements. Two plot types are currently implemented
in openMC:
slice
2D pixel plot along one of the major axes. Produces a PPM image file.voxel
3D voxel data dump. Produces a binary file containing voxel xyz position and cell or material id.
<plot>
Element¶
Each plot is specified by a combination of the following attributes or sub-elements:
id: The unique
id
of the plot.Default: None - Required entry
filename: Filename for the output plot file.
Default: “plot”
color_by: Keyword for plot coloring. This can be either “cell” or “material”, which colors regions by cells and materials, respectively. For voxel plots, this determines which id (cell or material) is associated with each position.
Default: “cell”
level: Universe depth to plot at (optional). This parameter controls how many universe levels deep to pull cell and material ids from when setting plot colors. If a given location does not have as many levels as specified, colors will be taken from the lowest level at that location. For example, if
level
is set to zero colors will be taken from top-level (universe zero) cells only. However, iflevel
is set to 1 colors will be taken from cells in universes that fill top-level fill-cells, and from top-level cells that contain materials.Default: Whatever the deepest universe is in the model
origin: Specifies the (x,y,z) coordinate of the center of the plot. Should be three floats separated by spaces.
Default: None - Required entry
width: Specifies the width of the plot along each of the basis directions. Should be two or three floats separated by spaces for 2D plots and 3D plots, respectively.
Default: None - Required entry
type: Keyword for type of plot to be produced. Currently only “slice” and “voxel” plots are implemented. The “slice” plot type creates 2D pixel maps saved in the PPM file format. PPM files can be displayed in most viewers (e.g. the default Gnome viewer, IrfanView, etc.). The “voxel” plot type produces a binary datafile containing voxel grid positioning and the cell or material (specified by the
color
tag) at the center of each voxel. These datafiles can be processed into VTK files using the openmc-voxel-to-vtk script provided with OpenMC, and subsequently viewed with a 3D viewer such as VISIT or Paraview. See the Voxel Plot File Format for information about the datafile structure.Note
Since the PPM format is saved without any kind of compression, the resulting file sizes can be quite large. Saving the image in the PNG format can often times reduce the file size by orders of magnitude without any loss of image quality. Likewise, high-resolution voxel files produced by OpenMC can be quite large, but the equivalent VTK files will be significantly smaller.
Default: “slice”
<plot>
elements of type
“slice” and “voxel” must contain the pixels
attribute or sub-element:
pixels: Specifies the number of pixels or voxels to be used along each of the basis directions for “slice” and “voxel” plots, respectively. Should be two or three integers separated by spaces.
Warning
The
pixels
input determines the output file size. For the PPM format, 10 million pixels will result in a file just under 30 MB in size. A 10 million voxel binary file will be around 40 MB.Warning
If the aspect ratio defined in
pixels
does not match the aspect ratio defined inwidth
the plot may appear stretched or squeezed.Warning
Geometry features along a basis direction smaller than
width
/pixels
along that basis direction may not appear in the plot.Default: None - Required entry for “slice” and “voxel” plots
<plot>
elements of type
“slice” can also contain the following
attributes or sub-elements. These are not used in “voxel” plots:
basis: Keyword specifying the plane of the plot for “slice” type plots. Can be one of: “xy”, “xz”, “yz”.
Default: “xy”
background: Specifies the RGB color of the regions where no OpenMC cell can be found. Should be three integers separated by spaces.
Default: 0 0 0 (black)
color: Any number of this optional tag may be included in each
<plot>
element, which can override the default random colors for cells or materials. Eachcolor
element must containid
andrgb
sub-elements.
id: Specifies the cell or material unique id for the color specification. rgb: Specifies the custom color for the cell or material. Should be 3 integers separated by spaces. As an example, if your plot is colored by material and you want material 23 to be blue, the corresponding
color
element would look like:<color id="23" rgb="0 0 255" />Default: None
mask: The special
mask
sub-element allows for the selective plotting of only user-specified cells or materials. Only onemask
element is allowed perplot
element, and it must contain as attributes or sub-elements a background masking color and a list of cells or materials to plot:
components: List of unique id
numbers of the cells or materials to plot. Should be any number of integers separated by spaces.background: Color to apply to all cells or materials not in the components
list of cells or materials to plot. This overrides anycolor
color specifications.Default: 255 255 255 (white)
meshlines: The
meshlines
sub-element allows for plotting the boundaries of a regular mesh on top of a plot. Only onemeshlines
element is allowed perplot
element, and it must contain as attributes or sub-elements a mesh type and a linewidth. Optionally, a color may be specified for the overlay:
meshtype: The type of the mesh to be plotted. Valid options are “tally”, “entropy”, “ufs”, and “cmfd”. If plotting “tally” meshes, the id of the mesh to plot must be specified with the
id
sub-element.id: A single integer id number for the mesh specified on
tallies.xml
that should be plotted. This element is only required formeshtype="tally"
.linewidth: A single integer number of pixels of linewidth to specify for the mesh boundaries. Specifying this as 0 indicates that lines will be 1 pixel thick, specifying 1 indicates 3 pixels thick, specifying 2 indicates 5 pixels thick, etc.
color: Specifies the custom color for the meshlines boundaries. Should be 3 integers separated by whitespace. This element is optional.
Default: 0 0 0 (black)
Default: None
Data Files¶
Cross Sections Listing – cross_sections.xml¶
<directory>
Element¶
The <directory>
element specifies a root directory to which the path for all
files listed in a <library> Element are given relative to. This element has
no attributes or sub-elements; the directory should be given within the text
node. For example,
<directory>/opt/data/cross_sections/</directory>
<library>
Element¶
The <library>
element indicates where an HDF5 data file is located, whether
it contains incident neutron, incident photon, thermal scattering, or windowed
multipole data, and what materials are listed within. It has the following
attributes:
materials: A space-separated list of nuclides or thermal scattering tables. For example,
<library materials="U234 U235 U238" /> <library materials="c_H_in_H2O c_D_in_G2O" />Often, just a single nuclide or thermal scattering table is contained in a given file.
path: Path to the HDF5 file. If the <directory> Element is specified, the path is relative to the directory given. Otherwise, it is relative to the directory containing the
cross_sections.xml
file.type: The type of data contained in the file. Accepted values are ‘neutron’, ‘thermal’, ‘photon’, and ‘wmp’.
<depletion_chain>
Element¶
The <depletion_chain>
element indicates the location of the depletion chain file.
This file contains information describing how nuclides decay and transmute to other
nuclides through the depletion process. This element has a single attribute, path
,
pointing to the location of the chain file.
<depletion_chain path="/opt/data/chain_endfb7.xml"/>
The structure of the depletion chain file is explained in Depletion Chain – chain.xml.
Depletion Chain – chain.xml¶
A depletion chain file has a <depletion_chain>
root element with one or more
<nuclide>
child elements. The decay, reaction, and fission product data for
each nuclide appears as child elements of <nuclide>
.
<nuclide>
Element¶
The <nuclide>
element contains information on the decay modes, reactions,
and fission product yields for a given nuclide in the depletion chain. This
element may have the following attributes:
name: Name of the nuclide half_life: Half-life of the nuclide in [s] decay_modes: Number of decay modes present decay_energy: Decay energy released in [eV] reactions: Number of reactions present
For each decay mode, a <decay> Element appears as a child of
<nuclide>
. For each reaction present, a <reaction> Element appears as
a child of <nuclide>
. If the nuclide is fissionable, a <neutron_fission_yields> Element
appears as well.
<decay>
Element¶
The <decay>
element represents a single decay mode and has the following
attributes:
type: The type of the decay, e.g. ‘ec/beta+’ target: The daughter nuclide produced from the decay branching_ratio: The branching ratio for this decay mode
<reaction>
Element¶
The <reaction>
element represents a single transmutation reaction. This
element has the following attributes:
type: The type of the reaction, e.g., ‘(n,gamma)’ Q: The Q value of the reaction in [eV] target: The nuclide produced in the reaction (absent if the type is ‘fission’) branching_ratio: The branching ratio for the reaction
<neutron_fission_yields>
Element¶
The <neutron_fission_yields>
element provides yields of fission products for
fissionable nuclides. Normally, it has the follow sub-elements:
energies: Energies in [eV] at which yields for products are tabulated
fission_yields: Fission product yields for a single energy point. This element itself has a number of attributes/sub-elements:
energy: Energy in [eV] at which yields are tabulated products: Names of fission products data: Independent yields for each fission product
In the event that a nuclide doesn’t have any known fission product yields, it is possible to have that nuclide borrow yields from another nuclide by indicating the other nuclide in a single parent attribute. For example:
<neutron_fission_yields parent="U235"/>
Nuclear Data File Formats¶
Incident Neutron Data¶
/
Attributes: |
|
---|
/<nuclide name>/
Attributes: |
|
---|---|
Datasets: |
|
/<nuclide name>/kTs/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Datasets: |
|
---|
/<nuclide name>/reactions/reaction_<mt>/
Attributes: |
|
---|
/<nuclide name>/reactions/reaction_<mt>/<TTT>K/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Datasets: |
|
---|
/<nuclide name>/reactions/reaction_<mt>/product_<j>/
Reaction product data is described in Reaction Products.
/<nuclide name>/urr/<TTT>K/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Attributes: |
|
---|---|
Datasets: |
|
/<nuclide name>/total_nu/
This special product is used to define the total number of neutrons produced from fission. It is formatted as a reaction product, described in Reaction Products.
/<nuclide name>/fission_energy_release/
Datasets: |
|
---|
Incident Photon Data¶
/
Attributes: |
|
---|
/<element>/
Attributes: |
|
---|---|
Datasets: |
|
/<element>/bremsstrahlung/
Attributes: |
|
---|---|
Datasets: |
|
/<element>/coherent/
Datasets: |
---|
/<element>/compton_profiles/
Datasets: |
|
---|
/<element>/heating/
Datasets: |
|
---|
/<element>/incoherent/
Datasets: |
|
---|
/<element>/pair_production_electron/
Datasets: |
|
---|
/<element>/pair_production_nuclear/
Datasets: |
|
---|
/<element>/photoelectric/
Datasets: |
|
---|
/<element>/subshells/
Attributes: |
|
---|
/<element>/subshells/<designator>/
Attributes: |
|
||
---|---|---|---|
Datasets: |
|
Thermal Neutron Scattering Data¶
/
Attributes: |
|
---|
/<thermal name>/
Attributes: |
|
---|
/<thermal name>/kTs/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Datasets: |
|
---|
/<thermal name>/elastic/<TTT>K/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Datasets: |
|
---|---|
Groups: |
|
/<thermal name>/inelastic/<TTT>K/
<TTT>K is the temperature in Kelvin, rounded to the nearest integer, of the temperature-dependent data set. For example, the data set corresponding to 300 Kelvin would be located at 300K.
Datasets: |
|
---|---|
Groups: |
|
Reaction Products¶
Object type: | Group |
||
---|---|---|---|
Attributes: |
|
||
Datasets: |
|
||
Groups: |
|
One-dimensional Functions¶
Scalar¶
Object type: | Dataset |
---|---|
Datatype: | double |
Attributes: |
|
Tabulated¶
Object type: | Dataset |
---|---|
Datatype: | double[2][] |
Description: | x-values are listed first followed by corresponding y-values |
Attributes: |
|
Polynomial¶
Object type: | Dataset |
---|---|
Datatype: | double[] |
Description: | Polynomial coefficients listed in order of increasing power |
Attributes: |
|
Coherent elastic scattering¶
Object type: | Dataset |
---|---|
Datatype: | double[2][] |
Description: | The first row lists Bragg edges and the second row lists structure factor cumulative sums. |
Attributes: |
|
Incoherent elastic scattering¶
Object type: | Dataset |
---|---|
Datatype: | double[2] |
Description: | The first value is the characteristic bound cross section in [b] and the second value is the Debye-Waller integral in [eV\(^{-1}\)]. |
Attributes: |
|
Angle-Energy Distributions¶
Kalbach-Mann¶
Object type: | Group |
||||
---|---|---|---|---|---|
Attributes: |
|
||||
Datasets: |
|
N-Body Phase Space¶
Object type: | Group |
---|---|
Attributes: |
|
Coherent Elastic¶
This angle-energy distribution is used specifically for coherent elastic thermal neutron scattering.
Object type: | Group |
---|---|
Attributes: |
|
Hard link: |
|
Incoherent Elastic¶
This angle-energy distribution is used specifically for incoherent elastic thermal neutron scattering (derived from an ENDF file directly).
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
|
Incoherent Elastic (Discrete)¶
This angle-energy distribution is used for discretized incoherent elastic thermal neutron scattering distributions that are present in ACE files.
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
|
Incoherent Inelastic¶
This angle-energy distribution is used specifically for (continuous) incoherent inelastic thermal neutron scattering.
Object type: | Group |
---|---|
Attributes: |
|
Datasets: | The datasets for this angle-energy distribution are the same as for correlated angle-energy distributions. |
Incoherent Inelastic (Discrete)¶
This angle-energy distribution is used specifically for incoherent inelastic thermal neutron scattering where the distributions have been discretized into equiprobable bins.
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
|
Energy Distributions¶
Maxwell¶
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
|
Evaporation¶
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
|
Watt Fission Spectrum¶
Object type: | Group |
---|---|
Attributes: |
|
Datasets: |
Madland-Nix¶
Object type: | Group |
---|---|
Attributes: |
|
Discrete Photon¶
Object type: | Group |
---|---|
Attributes: |
|
Level Inelastic¶
Object type: | Group |
---|---|
Attributes: |
|
Continuous Tabular¶
Object type: | Group |
||||
---|---|---|---|---|---|
Attributes: |
|
||||
Datasets: |
|
Multi-Group Cross Section Library Format¶
OpenMC can be run in continuous-energy mode or multi-group mode, provided the
nuclear data is available. In continuous-energy mode, the
cross_sections.xml
file contains necessary meta-data for each dataset,
including the name and a file system location where the complete library
can be found. In multi-group mode, the multi-group meta-data and the
nuclear data itself is contained within an mgxs.h5
file. This portion of
the manual describes the format of the multi-group data library required
to be used in the mgxs.h5
file.
The multi-group library is provided in the HDF5 format. This library must provide some meta-data about the library itself (such as the number of energy groups, delayed groups, and the energy group structure, etc.) as well as the actual cross section data itself for each of the necessary nuclides or materials.
The current version of the multi-group library file format is 1.0.
MGXS Library Specification¶
/
Attributes: |
|
---|
/<library name>/
The data within <library name> contains the temperature-dependent multi-group data for the nuclide or material that it represents.
Attributes: |
|
---|
/<library name>/kTs/
Datasets: |
|
---|
/<library name>/<TTT>K/
Temperature-dependent data, provided for temperature <TTT>K.
Datasets: |
|
---|
/<library name>/<TTT>K/scatter_data/
Data specific to neutron scattering for the temperature <TTT>K
Datasets: |
|
---|
Windowed Multipole Library Format¶
/
Attributes: |
|
---|
/<nuclide name>/
Datasets: |
|
---|
Output Files¶
State Point File Format¶
The current version of the statepoint file format is 17.0.
/
Attributes: |
|
---|---|
Datasets: |
|
/tallies/
Attributes: |
|
---|
/tallies/meshes/
Attributes: |
|
---|
/tallies/meshes/mesh <uid>/
Datasets: |
|
---|
/tallies/filters/
Attributes: |
|
---|
/tallies/filters/filter <uid>/
Datasets: |
|
---|
/tallies/derivatives/derivative <id>/
Datasets: |
|
---|
/tallies/tally <uid>/
Attributes: |
|
---|---|
Datasets: |
|
/runtime/
All values are given in seconds and are measured on the master process.
Datasets: |
|
---|
Source File Format¶
Normally, source data is stored in a state point file. However, it is possible to request that the source be written separately, in which case the format used is that documented here.
When surface source writing is triggered, a source file named
surface_source.h5
is written with only the sources on specified surfaces,
following the same format.
/
Attributes: |
|
---|---|
Datasets: |
|
Summary File Format¶
The current version of the summary file format is 6.0.
/
Attributes: |
|
---|
/geometry/
Attributes: |
|
---|
/geometry/cells/cell <uid>/
Datasets: |
|
---|
/geometry/surfaces/surface <uid>/
Datasets: |
|
---|
/geometry/universes/universe <uid>/
Datasets: |
|
---|
/geometry/lattices/lattice <uid>/
Datasets: |
|
---|
/materials/
Attributes: |
|
---|
/materials/material <uid>/
Datasets: |
|
---|---|
Attributes: |
|
/nuclides/
Attributes: |
|
---|---|
Datasets: |
|
/macroscopics/
Attributes: |
|
---|---|
Datasets: |
|
/tallies/tally <uid>/
Datasets: |
|
---|
Depletion Results File Format¶
The current version of the depletion results file format is 1.1.
/
Attributes: |
|
---|---|
Datasets: |
|
/materials/<id>/
Attributes: |
|
---|
/nuclides/<name>/
Attributes: |
|
---|
/reactions/<name>/
Attributes: |
|
---|
Note
The reaction rates for some isotopes not originally present may
be non-zero, but should be negligible compared to other atoms.
This can be controlled by changing the
openmc.deplete.Operator
dilute_initial
attribute.
Particle Restart File Format¶
The current version of the particle restart file format is 2.0.
/
Attributes: |
|
---|---|
Datasets: |
|
Track File Format¶
The current revision of the particle track file format is 2.0.
/
Attributes: |
|
---|---|
Datasets: |
|
Voxel Plot File Format¶
The current version of the voxel file format is 1.0.
/
Attributes: |
|
---|---|
Datasets: |
|
Volume File Format¶
The current version of the volume file format is 1.0.
/
Attributes: |
|
---|
/domain_<id>/
Datasets: |
|
---|
Publications¶
Overviews¶
- Paul K. Romano, Nicholas E. Horelik, Bryan R. Herman, Adam G. Nelson, Benoit Forget, and Kord Smith, “OpenMC: A State-of-the-Art Monte Carlo Code for Research and Development,” Ann. Nucl. Energy, 82, 90–97 (2015).
- Paul K. Romano, Bryan R. Herman, Nicholas E. Horelik, Benoit Forget, Kord Smith, and Andrew R. Siegel, “Progress and Status of the OpenMC Monte Carlo Code,” Proc. Int. Conf. Mathematics and Computational Methods Applied to Nuclear Science and Engineering, Sun Valley, Idaho, May 5–9 (2013).
- Paul K. Romano and Benoit Forget, “The OpenMC Monte Carlo Particle Transport Code,” Ann. Nucl. Energy, 51, 274–281 (2013).
Benchmarking¶
- Travis J. Labossiere-Hickman and Benoit Forget, “Selected VERA Core Physics Benchmarks in OpenMC,” Trans. Am. Nucl. Soc., 117, 1520-1523 (2017).
- Khurrum S. Chaudri and Sikander M. Mirza, “Burnup dependent Monte Carlo neutron physics calculations of IAEA MTR benchmark,” Prog. Nucl. Energy, 81, 43-52 (2015).
- Daniel J. Kelly, Brian N. Aviles, Paul K. Romano, Bryan R. Herman, Nicholas E. Horelik, and Benoit Forget, “Analysis of select BEAVRS PWR benchmark cycle 1 results using MC21 and OpenMC,” Proc. PHYSOR, Kyoto, Japan, Sep. 28–Oct. 3 (2014).
- Bryan R. Herman, Benoit Forget, Kord Smith, Paul K. Romano, Thomas M. Sutton, Daniel J. Kelly, III, and Brian N. Aviles, “Analysis of tally correlations in large light water reactors,” Proc. PHYSOR, Kyoto, Japan, Sep. 28–Oct. 3 (2014).
- Nicholas Horelik, Bryan Herman, Benoit Forget, and Kord Smith, “Benchmark for Evaluation and Validation of Reactor Simulations,” Proc. Int. Conf. Mathematics and Computational Methods Applied to Nuclear Science and Engineering, Sun Valley, Idaho, May 5–9 (2013).
- Jonathan A. Walsh, Benoit Forget, and Kord S. Smith, “Validation of OpenMC Reactor Physics Simulations with the B&W 1810 Series Benchmarks,” Trans. Am. Nucl. Soc., 109, 1301–1304 (2013).
Coupling and Multi-physics¶
- Miriam A. Kreher, Benoit Forget, and Kord Smith, “Single-Batch Monte Carlo Multiphysics Coupling,” Proc. M&C, 1789-1797, Portland, Oregon, Aug. 25-29 (2019).
- Ze-Long Zhao, Yongwei Yang, and Shuang Hong, “Application of FLUKA and OpenMC in coupled physics calculation of target and subcritical reactor for ADS,” Nucl. Sci. Tech., 30: 10 (2019).
- April Novak, Paul Romano, Brycen Wendt, Ron Rahaman, Elia Merzari, Leslie Kerby, Cody Permann, Richard Martineau, and Rachel N. Slaybaugh, “Preliminary Coupling of OpenMC and Nek5000 within the MOOSE Framework,” Proc. PHYSOR, Cancun, Mexico, Apr. 22-26 (2018).
- Sterling Harper, Kord Smith, and Benoit Forget, “Faster Monte Carlo multiphysics using temperature derivatives,” Proc. PHYSOR, Cancun, Mexico, Apr. 22-26 (2018).
- Jun Chen, Liangzhi Cao, Chuanqi Zhao, and Zhouyu Liu, “Development of Subchannel Code SUBSC for high-fidelity multi-physics coupling application”, Energy Procedia, 127, 264-274 (2017).
- Tianliang Hu, Liangzhu Cao, Hongchun Wu, Xianan Du, and Mingtao He, “Coupled neutronics and thermal-hydraulics simulation of molten salt reactors based on OpenMC/TANSY,” Ann. Nucl. Energy, 109, 260-276 (2017).
- Matthew Ellis, Derek Gaston, Benoit Forget, and Kord Smith, “Preliminary Coupling of the Monte Carlo Code OpenMC and the Multiphysics Object-Oriented Simulation Environment for Analyzing Doppler Feedback in Monte Carlo Simulations,” Nucl. Sci. Eng., 185, 184-193 (2017).
- Matthew Ellis, Benoit Forget, Kord Smith, and Derek Gaston, “Continuous Temperature Representation in Coupled OpenMC/MOOSE Simulations,” Proc. PHYSOR 2016, Sun Valley, Idaho, May 1-5, 2016.
- Antonios G. Mylonakis, Melpomeni Varvayanni, and Nicolas Catsaros, “Investigating a Matrix-free, Newton-based, Neutron-Monte Carlo/Thermal-Hydraulic Coupling Scheme”, Proc. Int. Conf. Nuclear Energy for New Europe, Portoroz, Slovenia, Sep .14-17 (2015).
- Matt Ellis, Benoit Forget, Kord Smith, and Derek Gaston, “Preliminary coupling of the Monte Carlo code OpenMC and the Multiphysics Object-Oriented Simulation Environment (MOOSE) for analyzing Doppler feedback in Monte Carlo simulations,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Bryan R. Herman, Benoit Forget, and Kord Smith, “Progress toward Monte Carlo-thermal hydraulic coupling using low-order nonlinear diffusion acceleration methods,” Ann. Nucl. Energy, 84, 63-72 (2015).
- Bryan R. Herman, Benoit Forget, and Kord Smith, “Utilizing CMFD in OpenMC to Estimate Dominance Ratio and Adjoint,” Trans. Am. Nucl. Soc., 109, 1389-1392 (2013).
Geometry and Visualization¶
- Patrick C. Shriwise, Xiaokang Zhang, and Andrew Davis, “DAG-OpenMC: CAD-Based Geometry in OpenMC”, Trans. Am. Nucl. Soc., 122, 395-398 (2020).
- Sterling Harper, Paul Romano, Benoit Forget, and Kord Smith, “Efficient dynamic threadsafe neighbor lists for Monte Carlo ray tracing,” Proc. M&C, 918-926, Portland, Oregon, Aug. 25-29 (2019).
- Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and Chang-Ping Qin, “CAD modeling study on FLUKA and OpenMC for accelerator driven system simulation”, Ann. Nucl. Energy, 114, 329-341 (2018).
- Logan Abel, William Boyd, Benoit Forget, and Kord Smith, “Interactive Visualization of Multi-Group Cross Sections on High-Fidelity Spatial Meshes,” Trans. Am. Nucl. Soc., 114, 391-394 (2016).
- Derek M. Lax, “Memory efficient indexing algorithm for physical properties in OpenMC,” S. M. Thesis, Massachusetts Institute of Technology (2015).
- Derek Lax, William Boyd, Nicholas Horelik, Benoit Forget, and Kord Smith, “A memory efficient algorithm for classifying unique regions in constructive solid geometries,” Proc. PHYSOR, Kyoto, Japan, Sep. 28–Oct. 3 (2014).
Miscellaneous¶
- Shikhar Kumar, Benoit Forget, and Kord Smith, “Stationarity Diagnostic using Functional Expansion Tallies”, Ann. Nucl. Energy, 143, 107388 (2020).
- T. Eade, B. Colling, J. Naish, L. W. Packer, and A. Valentine, “Shutdown dose rate benchmarking using modern particle transport codes, Nucl. Fusion, 60, 056024 (2020).
- Jiankai Yu, Qiudong Wang, Ding She, and Benoit Forget, “Modelling of the HTR-PM Pebble-bed Reactor using OpenMC”, Trans. Am. Nucl. Soc., 122, 643-646 (2020).
- Sharif Abu Darda, Abdelfattah Y. Soliman, Mohammed S. Aljohani, and Ned Xoubi, “Technical feasibility study of BAEC TRIGA reactor (BTRR) as a neutron source for BNCT using OpenMC Monte Carlo code”, Prog. Nucl. Energy, 126, 103418 (2020).
- Stefano Segantin, Raffaella Testoni, and Massimo Zucchetti, “ARC reactor – Neutron irradiation analysis”, Fus. Eng. Design, 159, 111792 (2020).
- Muhammad Ilham, Helen Raflis, and Zaki Suud, “Full Core Optimization of Small Modular Gas-Cooled Fast Reactors Using OpenMC Program Code”, J. Phys.: Conf. Series, 1493, 012007 (2020).
- Ned Xoubi, Sharif Abu Darda, Abdelfattah Y. Soliman, and Tareq Abulfaraj, “An investigative study of enrichment reduction impact on the neutron flux in the in-core flux-trap facility of MTR research reactors”, Nucl. Eng. Technol., 52, 469-476 (2020).
- J. Rolando Granada, J. Ignacio Marquez Damian, and Christian Helman, “Studies on Reflector Materials for Cold Neutrons, Eur. Phys. J. Web Conf., 231, 04002 (2020).
- Govatsa Acharya, “Investigating the Application of Self-Actuated Passive Shutdown System in a Small Lead-Cooled Reactor,” M.S. Thesis, KTH Royal Institute of Technology (2019).
- Ilham Variansyah, Benjamin R. Betzler, and William R. Martin, “α-weighted transition rate matrix method”, Proc. M&C, 1368-1377, Portland, Oregon, Aug. 25-29 (2019).
- Shikhar Kumar, Benoit Forget, and Kord Smith, “Analysis of fission source convergence for a 3-D SMR core using functional expansion tallies,” Proc. M&C, 937-947, Portland, Oregon, Aug. 25-29 (2019).
- Faisal Qayyum, Muhammad R. Ali, Awais Zahur, and R. Khan, “Improvements in methodology to determine feedback reactivity coefficients,” Nucl. Sci. Tech., 30: 63 (2019).
- M. Sajjad, Muhammad Rizwan Ali, M. Naveed Ashraf, Rustam Khan, Tasneem Fatima, “KANUPP Reactor Core Model and its Validation,” International Conference on Power Generation Systems and Renewable Energy Technologies, Islamabad, Pakistan, Sep. 10-12 (2018).
- Muhammad Waqas Tariq, Muhammad Sohail, and Sikander Majid Mirza, “Calculation of Neutronic Parameters using OpenMC for Potential Dispersed Fuels of MNSR,” International Conference on Power Generation Systems and Renewable Energy Technologies, Islamabad, Pakistan, Sep. 10-12 (2018).
- Amanda L. Lund and Paul K. Romano, “Implementation and Validation of Photon Transport in OpenMC”, Argonne National Laboratory, Technical Report ANL/MCS-TM-381 (2018).
- Bruno Merk, Dzianis Litskevich, R. Gregg, and A. R. Mount, “Demand driven salt clean-up in a molten salt fast reactor – Defining a priority list”, PLOS One, 13, e0192020 (2018).
- Adam G. Nelson, Samuel Shaner, William Boyd, and Paul K. Romano, “Incorporation of a Multigroup Transport Capability in the OpenMC Monte Carlo Particle Transport Code,” Trans. Am. Nucl. Soc., 117, 679-681 (2017).
- Youqi Zheng, Yunlong Xiao, and Hongchun Wu, “Application of the virtual density theory in fast reactor analysis based on the neutron transport calculation,” Nucl. Eng. Des., 320, 200-206 (2017).
- Amanda L. Lund, Paul K. Romano, and Andrew R. Siegel, “Accelerating Source Convergence in Monte Carlo Criticality Calculations Using a Particle Ramp-Up Technique,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- Antonios G. Mylonakis, M. Varvayanni, D.G.E. Grigoriadis, and N. Catsaros, “Developing and investigating a pure Monte-Carlo module for transient neutron transport analysis,” Ann. Nucl. Energy, 104, 103-112 (2017).
- Timothy P. Burke, Brian C. Kiedrowski, William R. Martin, and Forrest B. Brown, “GPU Acceleration of Kernel Density Estimators in Monte Carlo Neutron Transport Simulations,” Trans. Am. Nucl. Soc., 115, 531-534 (2016).
- Timothy P. Burke, Brian C. Kiedrowski, and William R. Martin, “Cylindrical Kernel Density Estimators for Monte Carlo Neutron Transport Reactor Physics Problems,” Trans. Am. Nucl. Soc., 115, 563-566 (2016).
- Yunzhao Li, Qingming He, Liangzhi Cao, Hongchun Wu, and Tiejun Zu, “Resonance Elastic Scattering and Interference Effects Treatments in Subgroup Method,” Nucl. Eng. Tech., 48, 339-350 (2016).
- William Boyd, Sterling Harper, and Paul K. Romano, “Equipping OpenMC for the big data era,” Proc. PHYSOR, Sun Valley, Idaho, May 1-5, 2016.
- Michal Kostal, Vojtech Rypar, Jan Milcak, Vlastimil Juricek, Evzen Losa, Benoit Forget, and Sterling Harper, “Study of graphite reactivity worth on well-defined cores assembled on LR-0 reactor,” Ann. Nucl. Energy, 87, 601-611 (2016).
- Qicang Shen, William Boyd, Benoit Forget, and Kord Smith, “Tally precision triggers for the OpenMC Monte Carlo code,” Trans. Am. Nucl. Soc., 112, 637-640 (2015).
- Kyungkwan Noh and Deokjung Lee, “Whole Core Analysis using OpenMC Monte Carlo Code,” Trans. Kor. Nucl. Soc. Autumn Meeting, Gyeongju, Korea, Oct. 24-25, 2013.
- Timothy P. Burke, Brian C. Kiedrowski, and William R. Martin, “Flux and Reaction Rate Kernel Density Estimators in OpenMC,” Trans. Am. Nucl. Soc., 109, 683-686 (2013).
Multigroup Cross Section Generation¶
- Ilham Variansyah, Benjamin R. Betzler, and William R. Martin, “Multigroup Constant Calculation with Static α-Eigenvalue Monte Carlo for Time-Dependent Neutron Transport Simulation”, Nucl. Sci. Eng., 2020.
- Chenghui Wan, Tianliang Hu, and Liangzhi Cao, “Multi-physics numerical analysis of the fuel-addition transients in the liquid-fuel molten salt reactor”, Ann. Nucl. Energy, 144, 107514 (2020).
- William Boyd, Adam Nelson, Paul K. Romano, Samuel Shaner, Benoit Forget, and Kord Smith, “Multigroup Cross-Section Generation with the OpenMC Monte Carlo Particle Transport Code,” Nucl. Technol., 205, 928-944 (2019).
- William Boyd, Benoit Forget, and Kord Smith, “A single-step framework to generate spatially self-shielded multi-group cross sections from Monte Carlo transport simulations,” Ann. Nucl. Energy, 125, 261-271 (2019).
- Kun Zhuang, Xiaobin Tang, and Liangzhi Cao, “Development and verification of a model for generation of MSFR few-group homogenized cross-sections based on a Monte Carlo code OpenMC,” Ann. Nucl. Energy, 124, 187-197 (2019).
- Changho Lee and Yeon Sang Jung, “Verification of the Cross Section Library Generated Using OpenMC and MC2-3 for PROTEUS,” Proc. PHYSOR, Cancun, Mexico, Apr. 22-26 (2018).
- Zhaoyuan Liu, Kord Smith, Benoit Forget, and Javier Ortensi, “Cumulative migration method for computing rigorous diffusion coefficients and transport cross sections from Monte Carlo,” Ann. Nucl. Energy, 112, 507-516 (2018).
- Gang Yang, Tongkyu Park, and Won Sik Yang, “Effects of Fuel Salt Velocity Field on Neutronics Performances in Molten Salt Reactors with Open Flow Channels,” Trans. Am. Nucl. Soc., 117, 1339-1342 (2017).
- William Boyd, Nathan Gibson, Benoit Forget, and Kord Smith, “An analysis of condensation errors in multi-group cross section generation for fine-mesh neutron transport calculations,” Ann. Nucl. Energy, 112, 267-276 (2018).
- Hong Shuang, Yang Yongwei, Zhang Lu, and Gao Yucui, “Fabrication and validation of multigroup cross section library based on the OpenMC code,” Nucl. Techniques 40 (4), 040504 (2017). (in Mandarin)
- Nicholas E. Stauff, Changho Lee, Paul K. Romano, and Taek K. Kim, “Verification of Mixed Stochastic/Deterministic Approach for Fast and Thermal Reactor Analysis,” Proc. ICAPP, Fukui and Kyoto, Japan, Apr. 24-28, 2017.
- Zhauyuan Liu, Kord Smith, and Benoit Forget, “Progress of Cumulative Migration Method for Computing Diffusion Coefficients with OpenMC,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- Geoffrey Gunow, Samuel Shaner, William Boyd, Benoit Forget, and Kord Smith, “Accuracy and Performance of 3D MOC for Full-Core PWR Problems,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- Tianliang Hu, Liangzhi Cao, Hongchun Wu, and Kun Zhuang, “A coupled neutronics and thermal-hydraulic modeling approach to the steady-state and dynamic behavior of MSRs,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- William R. D. Boyd, “Reactor Agnostic Multi-Group Cross Section Generation for Fine-Mesh Deterministic Neutron Transport Simulations,” Ph.D. Thesis, Massachusetts Institute of Technology (2017).
- Zhaoyuan Liu, Kord Smith, and Benoit Forget, “A Cumulative Migration Method for Computing Rigorous Transport Cross Sections and Diffusion Coefficients for LWR Lattices with Monte Carlo,” Proc. PHYSOR, Sun Valley, Idaho, May 1-5, 2016.
- Adam G. Nelson and William R. Martin, “Improved Monte Carlo tallying of multi-group scattering moments using the NDPP code,” Trans. Am. Nucl. Soc., 113, 645-648 (2015)
- Adam G. Nelson and William R. Martin, “Improved Monte Carlo tallying of multi-group scattering moment matrices,” Trans. Am. Nucl. Soc., 110, 217-220 (2014).
- Adam G. Nelson and William R. Martin, “Improved Convergence of Monte Carlo Generated Multi-Group Scattering Moments,” Proc. Int. Conf. Mathematics and Computational Methods Applied to Nuclear Science and Engineering, Sun Valley, Idaho, May 5–9 (2013).
Doppler Broadening¶
- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, “On-the-fly Doppler broadening of unresolved resonance region cross sections,” Prog. Nucl. Energy, 101, 444-460 (2017).
- Colin Josey, Pablo Ducru, Benoit Forget, and Kord Smith, “Windowed multipole for cross section Doppler broadening,” J. Comput. Phys., 307, 715-727 (2016).
- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, “On-the-fly Doppler Broadening of Unresolved Resonance Region Cross Sections via Probability Band Interpolation,” Proc. PHYSOR, Sun Valley, Idaho, May 1-5, 2016.
- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and Forrest B. Brown, “Direct, on-the-fly calculation of unresolved resonance region cross sections in Monte Carlo simulations,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Colin Josey, Benoit Forget, and Kord Smith, “Windowed multipole sensitivity to target accuracy of the optimization procedure,” J. Nucl. Sci. Technol., 52, 987-992 (2015).
- Paul K. Romano and Timothy H. Trumbull, “Comparison of algorithms for Doppler broadening pointwise tabulated cross sections,” Ann. Nucl. Energy, 75, 358–364 (2015).
- Tuomas Viitanen, Jaakko Leppanen, and Benoit Forget, “Target motion sampling temperature treatment technique with track-length esimators in OpenMC – Preliminary results,” Proc. PHYSOR, Kyoto, Japan, Sep. 28–Oct. 3 (2014).
- Benoit Forget, Sheng Xu, and Kord Smith, “Direct Doppler broadening in Monte Carlo simulations using the multipole representation,” Ann. Nucl. Energy, 64, 78–85 (2014).
Nuclear Data¶
- Jonathan A. Walsh, “Comparison of Unresolved Resonance Region Cross Section Formalisms in Transport Simulations,” Trans. Am. Nucl. Soc., 117, 749-752 (2017).
- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, “Uncertainty in Fast Reactor-Relevant Critical Benchmark Simulations Due to Unresolved Resonance Structure,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- Vivian Y. Tran, Jonathan A. Walsh, and Benoit Forget, “Treatments for Neutron Resonance Elastic Scattering Using the Multipole Formalism in Monte Carlo Codes,” Trans. Am. Nucl. Soc., 115, 1133-1137 (2016).
- Paul K. Romano and Sterling M. Harper, “Nuclear data processing capabilities in OpenMC”, Proc. Nuclear Data, Sep. 11-16, 2016.
- Jonathan A. Walsh, Benoit Froget, Kord S. Smith, and Forrest B. Brown, “Neutron Cross Section Processing Methods for Improved Integral Benchmarking of Unresolved Resonance Region Evaluations,” Eur. Phys. J. Web Conf. 111, 06001 (2016).
- Jonathan A. Walsh, Paul K. Romano, Benoit Forget, and Kord S. Smith, “Optimizations of the energy grid search algorithm in continuous-energy Monte Carlo particle transport codes”, Comput. Phys. Commun., 196, 134-142 (2015).
- Amanda L. Lund, Andrew R. Siegel, Benoit Forget, Colin Josey, and Paul K. Romano, “Using fractional cascading to accelerate cross section lookups in Monte Carlo particle transport calculations,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Ronald O. Rahaman, Andrew R. Siegel, and Paul K. Romano, “Monte Carlo performance analysis for varying cross section parameter regimes,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Jonathan A. Walsh, Benoit Forget, and Kord S. Smith, “Accelerated sampling of the free gas resonance elastic scattering kernel,” Ann. Nucl. Energy, 69, 116–124 (2014).
Parallelism¶
- Paul K. Romano and Andrew R. Siegel, “Limits on the efficiency of event-based algorithms for Monte Carlo neutron transport,” Proc. Int. Conf. Mathematics & Computational Methods Applied to Nuclear Science and Engineering, Jeju, Korea, Apr. 16-20, 2017.
- Paul K. Romano, John R. Tramm, and Andrew R. Siegel, “Efficacy of hardware threading for Monte Carlo particle transport calculations on multi- and many-core systems,” PHYSOR 2016, Sun Valley, Idaho, May 1-5, 2016.
- David Ozog, Allen D. Malony, and Andrew R. Siegel, “A performance analysis of SIMD algorithms for Monte Carlo simulations of nuclear reactor cores,” Proc. IEEE Int. Parallel and Distributed Processing Symposium, Hyderabad, India, May 25–29 (2015).
- David Ozog, Allen D. Malony, and Andrew Siegel, “Full-core PWR transport simulations on Xeon Phi clusters,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Paul K. Romano, Andrew R. Siegel, and Ronald O. Rahaman, “Influence of the memory subsystem on Monte Carlo code performance,” Proc. Joint Int. Conf. M&C+SNA+MC, Nashville, Tennessee, Apr. 19–23 (2015).
- Hajime Fujita, Nan Dun, Aiman Fang, Zachary A. Rubinstein, Ziming Zheng, Kamil Iskra, Jeff Hammonds, Anshu Dubey, Pavan Balaji, and Andrew A. Chien, “Using Global View Resilience (GVR) to add Resilience to Exascale Applications,” Proc. Supercomputing, New Orleans, Louisiana, Nov. 16–21, 2014.
- Nicholas Horelik, Benoit Forget, Kord Smith, and Andrew Siegel, “Domain decomposition and terabyte tallies with the OpenMC Monte Carlo neutron transport code,” Proc. PHYSOR, Kyoto Japan, Sep. 28–Oct. 3 (2014).
- John R. Tramm, Andrew R. Siegel, Tanzima Islam, and Martin Schulz, “XSBench – the development and verification of a performance abstraction for Monte Carlo reactor analysis,” Proc. PHYSOR, Kyoto, Japan, Sep 28–Oct. 3, 2014.
- Nicholas Horelik, Andrew Siegel, Benoit Forget, and Kord Smith, “Monte Carlo domain decomposition for robust nuclear reactor analysis,” Parallel Comput., 40, 646–660 (2014).
- Andrew Siegel, Kord Smith, Kyle Felker, Paul Romano, Benoit Forget, and Peter Beckman, “Improved cache performance in Monte Carlo transport calculations using energy banding,” Comput. Phys. Commun., 185 (4), 1195–1199 (2014).
- Paul K. Romano, Benoit Forget, Kord Smith, and Andrew Siegel, “On the use of tally servers in Monte Carlo simulations of light-water reactors,” Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo, Paris, France, Oct. 27–31 (2013).
- Kyle G. Felker, Andrew R. Siegel, Kord S. Smith, Paul K. Romano, and Benoit Forget, “The energy band memory server algorithm for parallel Monte Carlo calculations,” Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo, Paris, France, Oct. 27–31 (2013).
- John R. Tramm and Andrew R. Siegel, “Memory Bottlenecks and Memory Contention in Multi-Core Monte Carlo Transport Codes,” Proc. Joint International Conference on Supercomputing in Nuclear Applications and Monte Carlo, Paris, France, Oct. 27–31 (2013).
- Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, “Multi-core performance studies of a Monte Carlo neutron transport code,” Int. J. High Perform. Comput. Appl., 28 (1), 87–96 (2014).
- Paul K. Romano, Andrew R. Siegel, Benoit Forget, and Kord Smith, “Data decomposition of Monte Carlo particle transport simulations via tally servers,” J. Comput. Phys., 252, 20–36 (2013).
- Andrew R. Siegel, Kord Smith, Paul K. Romano, Benoit Forget, and Kyle Felker, “The effect of load imbalances on the performance of Monte Carlo codes in LWR analysis,” J. Comput. Phys., 235, 901–911 (2013).
- Paul K. Romano and Benoit Forget, “Reducing Parallel Communication in Monte Carlo Simulations via Batch Statistics,” Trans. Am. Nucl. Soc., 107, 519–522 (2012).
- Paul K. Romano and Benoit Forget, “Parallel Fission Bank Algorithms in Monte Carlo Criticality Calculations,” Nucl. Sci. Eng., 170, 125–135 (2012).
Depletion¶
- Binhang Zhang, XianBao Yuan, Yonghong Zhang, Haibo Tang, and Liangzhi Cao, “Development of a versatile depletion code AMAC”, Ann. Nucl. Energy, 143, 107446 (2020).
- Zelong Zhao, Yongwei Yang, and Qingyu Gao, “Development and verification of code IMPC-Depletion for nuclide depletion calculation”, Nucl. Eng. Des., 363, 110616 (2020).
- Kun Zhuang, Ting Li, Qian Zhang, Qinghua He, and Tengfei Zhang, “Extended development of a Monte Carlo code OpenMC for fuel cycle simulation of molten salt reactor”, Prog. Nucl. Energy, 118, 103115 (2020).
- Jose L. Salcedo-Perez, Benoit Forget, Kord Smith, and Paul Romano, “Hybrid tallies to improve performance in depletion Monte Carlo simulations,” Proc. M&C, 927-936, Portland, Oregon, Aug. 25-29 (2019).
- Zhao-Qing Liu, Ze-Long Zhao, Yong-Wei Yang, Yu-Cui Gao, Hai-Yan Meng, and Qing-Yu Gao, “Development and validation of depletion code system IMPC-Burnup for ADS,” Nucl. Sci. Tech., 30: 44 (2019).
- Colin Josey, Benoit Forget, and Kord Smith, “High order methods for the integration of the Bateman equations and other problems of the form of y’ = F(y,t)y,” J. Comput. Phys., 350, 296-313 (2017).
- Matthew S. Ellis, Colin Josey, Benoit Forget, and Kord Smith, “Spatially Continuous Depletion Algorithm for Monte Carlo Simulations,” Trans. Am. Nucl. Soc., 115, 1221-1224 (2016).
- Anas Gul, K. S. Chaudri, R. Khan, and M. Azeen, “Development and verification of LOOP: A Linkage of ORIGEN2.2 and OpenMC,” Ann. Nucl. Energy, 99, 321–327 (2017).
- Kai Huang, Hongchun Wu, Yunzhao Li, and Liangzhi Cao, “Generalized depletion chain simplification based of significance analysis,” Proc. PHYSOR, Sun Valley, Idaho, May 1-5, 2016.
Sensitivity Analysis¶
- Abdulla Alhajri and Benoit Forget, “Eigenvalue Sensitivity in Monte Carlo Simulations to Nuclear Data Parameters using the Multipole Formalism,” Proc. M&C, 1895-1906, Portland, Oregon, Aug. 25-29 (2019).
- Xingjie Peng, Jingang Liang, Benoit Forget, and Kord Smith, “Calculation of adjoint-weighted reactor kinetics parameters in OpenMC”, Ann. Nucl. Energy, 128, 231-235 (2019).
- Zeyun Wu, Jingang Liang, Xingjie Peng, and Hany S. Abdel-Khalik, “GPT-Free Sensitivity Analysis for Monte Carlo Models”, Nucl. Technol. (2019).
- Xingjie Peng, Jingang Liang, Abdulla Alhajri, Benoit Forget, and Kord Smith, “Development of continuous-energy sensitivity analysis capability in OpenMC”, Ann. Nucl. Energy, 110, 362-383 (2017).
License Agreement¶
Copyright © 2011-2021 Massachusetts Institute of Technology and OpenMC contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.