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

7.1. 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’.

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

7.2.1. 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}\)).

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

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

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

7.3. 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 any of the subclasses of openmc.SourceBase (openmc.IndependentSource, openmc.FileSource, openmc.CompiledSource) 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.SourceBase objects.

The openmc.IndependentSource class is the primary class for defining source distributions and has four main attributes that one can set: IndependentSource.space, which defines the spatial distribution, IndependentSource.angle, which defines the angular distribution, IndependentSource.energy, which defines the energy distribution, and IndependentSource.time, which defines the time 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. Meshes can also be used to represent spatial distributions with openmc.stats.MeshSpatial by specifying a mesh and source strengths for each mesh element.

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.

The time distribution can be set equal to any univariate probability distribution. This could be a probability mass function (openmc.stats.Discrete), a uniform distribution (openmc.stats.Uniform), or a tabular distribution (openmc.stats.Tabular). By default, if no time distribution is specified, particles are started at \(t=0\).

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, and emitting a pulse of particles from 0 to 10 µs, one would run:

source = openmc.IndependentSource()
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])
source.time = openmc.stats.Uniform(0, 1e-6)
settings.source = source

All subclasses of openmc.SourceBase have a SourceBase.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.IndependentSource()
src1.strength = 0.7
...

src2 = openmc.IndependentSource()
src2.strength = 0.3
...

settings.source = [src1, src2]

Finally, the IndependentSource.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.IndependentSource()
source.particle = 'photon'
...

settings.source = source

For a full list of all classes related to statistical distributions, see openmc.stats – Statistics.

7.3.1. File-based Sources

OpenMC can use a pregenerated HDF5 source file through the openmc.FileSource class:

settings.source = openmc.FileSource('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.

7.3.1.1. Surface Sources

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. If no surface IDs are declared, particles crossing any surface of the model will be banked:

settings.surf_source_write = {'max_particles': 10000}

A cell ID can also be used to bank particles that are crossing any surface of a cell that particles are either coming from or going to:

settings.surf_source_write = {'cell': 1, 'max_particles': 10000}

In this example, particles that are crossing any surface that bounds cell 1 will be banked excluding any surface that does not use a ‘transmission’ or ‘vacuum’ boundary condition.

Note

Surfaces with boundary conditions that are not “transmission” or “vacuum” are not eligible to store any particles when using cell, cellfrom or cellto attributes. It is recommended to use surface IDs instead.

Surface IDs can be used in combination with a cell ID:

settings.surf_source_write = {
    'cell': 1,
    'surfaces_ids': [1, 2, 3],
    'max_particles': 10000
}

In that case, only particles that are crossing the declared surfaces coming from cell 1 or going to cell 1 will be banked. To account specifically for particles leaving or entering a given cell, cellfrom and cellto are also available to respectively account for particles coming from a cell:

settings.surf_source_write = {
    'cellfrom': 1,
    'max_particles': 10000
}

or particles going to a cell:

settings.surf_source_write = {
    'cellto': 1,
    'max_particles': 10000
}

Note

The cell, cellfrom and cellto attributes cannot be used simultaneously.

To generate more than one surface source files when the maximum number of stored particles is reached, max_source_files is available. The surface source bank will be cleared in simulation memory each time a surface source file is written. As an example, to write a maximum of three surface source files::

settings.surf_source_write = {
    'surfaces_ids': [1, 2, 3],
    'max_particles': 10000,
    'max_source_files': 3
}

7.3.2. Compiled 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 CompiledSource : 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<CompiledSource> openmc_create_source(std::string parameters)
{
  return std::make_unique<CompiledSource>();
}

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 a sample() function.

Note

The openmc_create_source() function signature must be declared extern "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. You can then use this as an external source during an OpenMC run by passing the path of the shared library to the openmc.CompiledSource class, which is then set as the Settings.source attribute:

settings.source = openmc.CompiledSource('libsource.so')

7.3.3. Parameterized Compiled Sources

Some compiled sources may have values (parameters) that can be changed between runs. This is supported by using the openmc_create_source() function to pass parameters to the source class when it is created:

#include <memory> // for unique_ptr

#include "openmc/source.h"
#include "openmc/particle.h"

class CompiledSource : public openmc::Source {
public:
  CompiledSource(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<CompiledSource> openmc_create_source(std::string parameter) {
  double energy = std::stod(parameter);
  return std::make_unique<CompiledSource>(energy);
}

When creating an instance of the openmc.CompiledSource class, you will need to pass both the path of the shared library as well as the parameters as a string, which gets passed down to the openmc_create_source() function:

settings.source = openmc.CompiledSource('libsource.so', '3.5e6')

7.3.4. Source Constraints

All source classes in OpenMC have the ability to apply a set of “constraints” that limit which sampled source sites are actually used for transport. The most common use case is to sample source sites over some simple spatial distribution (e.g., uniform over a box) and then only accept those that appear in a given cell or material. This can be done with a domain constraint, which can be specified as follows:

source_cell = openmc.Cell(...)
...

spatial_dist = openmc.stats.Box((-10., -10., -10.), (10., 10., 10.))
source = openmc.IndependentSource(
    space=spatial_dist,
    constraints={'domains': [source_cell]}
)

For k-eigenvalue problems, a convenient constraint is available that limits source sites to those sampled in a fissionable material:

source = openmc.IndependentSource(
    space=spatial_dist, constraints={'fissionable': True}
)

Constraints can also be placed on a range of energies or times:

# Only use source sites between 500 keV and 1 MeV and with times under 1 sec
source = openmc.FileSource(
    'source.h5',
    constraints={'energy_bounds': [500.0e3, 1.0e6], 'time_bounds': [0.0, 1.0]}
)

Normally, when a source site is rejected, a new one will be resampled until one is found that meets the constraints. However, the rejection strategy can be changed so that a rejected site will just not be simulated by specifying:

source = openmc.IndependentSource(
    space=spatial_dist,
    constraints={'domains': [cell], 'rejection_strategy': 'kill'}
)

In this case, the actual number of particles simulated may be less than what you specified in Settings.particles.

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

7.5. 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:

  • Generating a photon source from a neutron calculation that can be used for a later fixed source photon calculation.

  • Photoneutron reactions.

7.6. 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)}

7.6.1. Particle Track Files

OpenMC can generate a particle track file that contains track information (position, direction, energy, time, weight, cell ID, and material ID) for each state along a particle’s history. There are two ways to indicate which particles and/or how many particles should have their tracks written. First, you can identify specific source particles by their batch, generation, and particle ID numbers:

settings.tracks = [
  (1, 1, 50),
  (2, 1, 30),
  (5, 1, 75)
]

In this example, track information would be written for the 50th particle in the 1st generation of batch 1, the 30th particle in the first generation of batch 2, and the 75th particle in the 1st generation of batch 5. Unless you are using more than one generation per batch (see Run Strategy), the generation number should be 1. Alternatively, you can run OpenMC in a mode where track information is written for all particles, up to a user-specified limit:

openmc.run(tracks=True)

In this case, you can control the maximum number of source particles for which tracks will be written as follows:

settings.max_tracks = 1000

Particle track information is written to the tracks.h5 file, which can be analyzed using the Tracks class:

>>> tracks = openmc.Tracks('tracks.h5')
>>> tracks
[<Track (1, 1, 50): 151 particles>,
 <Track (2, 1, 30): 191 particles>,
 <Track (5, 1, 75): 81 particles>]

Each Track object stores a list of track information for every primary/secondary particle. In the above example, the first source particle produced 150 secondary particles for a total of 151 particles. Information for each primary/secondary particle can be accessed using the particle_tracks attribute:

>>> first_track = tracks[0]
>>> first_track.particle_tracks
[<ParticleTrack: neutron, 120 states>,
 <ParticleTrack: photon, 6 states>,
 <ParticleTrack: electron, 2 states>,
 <ParticleTrack: electron, 2 states>,
 <ParticleTrack: electron, 2 states>,
 ...
 <ParticleTrack: electron, 2 states>,
 <ParticleTrack: electron, 2 states>]
>>> photon = first_track.particle_tracks[1]

The ParticleTrack class is a named tuple indicating the particle type and then a NumPy array of the “states”. The states array is a compound type with a field for each physical quantity (position, direction, energy, time, weight, cell ID, and material ID). For example, to get the position for the above particle track:

>>> photon.states['r']
array([(-11.92987939, -12.28467295, 0.67837495),
       (-11.95213726, -12.2682    , 0.68783964),
       (-12.2682    , -12.03428339, 0.82223855),
       (-12.5913778 , -11.79510096, 0.95966298),
       (-12.6622572 , -11.74264344, 0.98980293),
       (-12.6907775 , -11.7215357 , 1.00193058)],
      dtype=[('x', '<f8'), ('y', '<f8'), ('z', '<f8')])

The full list of fields is as follows:

r

Position (each direction in [cm])

u

Direction

E

Energy in [eV]

time

Time in [s]

wgt

Weight

cell_id

Cell ID

cell_instance

Cell instance

material_id

Material ID

Both the Tracks and Track classes have a filter method that allows you to get a subset of tracks that meet a given criteria. For example, to get all tracks that involved a photon:

>>> tracks.filter(particle='photon')
[<Track (1, 1, 50): 151 particles>,
 <Track (2, 1, 30): 191 particles>,
 <Track (5, 1, 75): 81 particles>]

The openmc.Tracks.filter() method returns a new Tracks instance, whereas the openmc.Track.filter() method returns a new Track instance.

Note

If you are using an MPI-enabled install of OpenMC and run a simulation with more than one process, a separate track file will be written for each MPI process with the filename tracks_p#.h5 where # is the rank of the corresponding process. Multiple track files can be combined with the openmc-track-combine script:

openmc-track-combine tracks_p*.h5 --out tracks.h5

7.7. Restarting a Simulation

OpenMC can be run in a mode where it reads in a statepoint file and continues a simulation from the ending point of the statepoint file. A restart simulation can be performed by passing the path to the statepoint file to the OpenMC executable:

openmc -r statepoint.100.h5

From the Python API, the restart_file argument provides the same behavior:

openmc.run(restart_file='statepoint.100.h5')

or if using the Model class:

model.run(restart_file='statepoint.100.h5')

The restart simulation will execute until the number of batches specified in the Settings object on a model (or in the settings XML file) is satisfied. Note that if the number of batches in the statepoint file is the same as that specified in the settings object (i.e., if the inputs were not modified before the restart run), no particles will be transported and OpenMC will exit immediately.

Note

A statepoint file must match the input model to be successfully used in a restart simulation.