OpenMC is a Monte Carlo particle transport code for simulating nuclear reactors, fusion devices, or other systems with neutron/photon radiation. It's a hybrid C++17/Python codebase where:
- C++ core (
src/,include/openmc/) handles the computationally intensive transport simulation - Python API (
openmc/) provides user-facing model building, post-processing, and depletion capabilities - C API bindings (
openmc/lib/) wrap the C++ library via ctypes for runtime control
- Global vectors of unique_ptrs: Core objects like
model::cells,model::universes,nuclidesare stored asvector<unique_ptr<T>>in nested namespaces (openmc::model,openmc::simulation,openmc::settings,openmc::data) - Custom container types: OpenMC provides its own
vector,array,unique_ptr, andmake_uniquein theopenmc::namespace (defined invector.h,array.h,memory.h). These are currently typedefs tostd::equivalents but may become custom implementations for accelerator support. Always useopenmc::vector, notstd::vector. - Geometry systems:
- CSG (default): Arbitrarily complex Constructive Solid Geometry using
Surface,Region,Cell,Universe,Lattice - DAGMC: CAD-based geometry via Direct Accelerated Geometry Monte Carlo (optional, requires
OPENMC_USE_DAGMC) - Unstructured mesh: libMesh-based geometry (optional, requires
OPENMC_USE_LIBMESH)
- CSG (default): Arbitrarily complex Constructive Solid Geometry using
- Particle tracking:
Particleclass withGeometryStatemanages particle transport through geometry - Tallies: Score quantities during simulation via
FilterandTallyobjects - Random ray solver: Alternative deterministic method in
src/random_ray/ - Optional features: DAGMC (CAD geometry), libMesh (unstructured mesh), MPI, all controlled by
#ifdef OPENMC_MPI, etc.
- ID management: All geometry objects (Cell, Surface, Material, etc.) inherit from
IDManagerMixinwhich auto-assigns unique integer IDs and tracks them via class-levelused_idsandnext_id - Input validation: Extensive use of
openmc.checkvaluemodule functions (check_type,check_value,check_length) for all setters - XML I/O: Most classes implement
to_xml_element()andfrom_xml_element()for serialization to OpenMC's XML input format - HDF5 output: Post-simulation data in statepoint files read via
openmc.StatePoint - Depletion:
openmc.depleteimplements burnup via operator-splitting with various integrators (Predictor, CECM, etc.) - Nuclear Data:
openmc.dataprovides programmatic access to nuclear data files (ENDF, ACE, HDF5)
OpenMC uses a git flow branching model with two primary branches:
developbranch: The main development branch where all ongoing development takes place. This is the primary branch against which pull requests are submitted and merged. This branch is not guaranteed to be stable and may contain work-in-progress features.masterbranch: The stable release branch containing the latest stable release of OpenMC. This branch only receives merges fromdevelopwhen the development team decides a release should occur.
When analyzing code changes on a feature or bugfix branch (e.g., when a user asks "what do you think of these changes?"), compare the branch changes against develop, not master. Pull requests are submitted to merge into develop, so differences relative to develop represent the actual proposed changes. Comparing against master will include unrelated changes from other features that have already been merged to develop.
- Create a feature/bugfix branch off
develop - Make changes and commit to the feature branch
- Open a pull request to merge the feature branch into
develop - A committer reviews and merges the PR into
develop
- C++17 compiler: GCC, Clang, or Intel
- CMake (3.16+): Required for configuring and building the C++ library
- HDF5: Required for cross section data and output file formats
- libpng: Used for generating visualization when OpenMC is run in plotting mode
Without CMake and HDF5, OpenMC cannot be compiled.
# Configure with CMake (from build/ directory)
cmake .. -DOPENMC_USE_MPI=ON -DOPENMC_USE_OPENMP=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo
# Available CMake options (all default OFF except OPENMC_USE_OPENMP and OPENMC_BUILD_TESTS):
# -DOPENMC_USE_OPENMP=ON/OFF # OpenMP parallelism
# -DOPENMC_USE_MPI=ON/OFF # MPI support
# -DOPENMC_USE_DAGMC=ON/OFF # CAD geometry support
# -DOPENMC_USE_LIBMESH=ON/OFF # Unstructured mesh
# -DOPENMC_ENABLE_PROFILE=ON/OFF # Profiling flags
# -DOPENMC_ENABLE_COVERAGE=ON/OFF # Coverage analysis
# Build
make -j
# C++ unit tests (uses Catch2)
ctest# Install in development mode (requires building C++ library first)
pip install -e .
# Python tests (uses pytest)
pytest tests/unit_tests/ # Fast unit tests
pytest tests/regression_tests/ # Full regression suite (requires nuclear data)Most tests require the NNDC HDF5 nuclear cross-section library.
Important: Check if OPENMC_CROSS_SECTIONS is already set in the user's
environment before downloading, as many users already have nuclear data
installed. Though do note that if this variable is present that it may point to
different cross section data and that the NNDC data is required for tests to
pass.
If not already configured, download and setup:
# Download NNDC HDF5 cross section library (~800 MB compressed)
wget -q -O - https://anl.box.com/shared/static/teaup95cqv8s9nn56hfn7ku8mmelr95p.xz | tar -C $HOME -xJ
# Set environment variable (add to ~/.bashrc or ~/.zshrc for persistence)
export OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xmlAlternative: Use the provided download script (checks if data exists before downloading):
bash tools/ci/download-xs.sh # Downloads both NNDC HDF5 and ENDF/B-VII.1 dataWithout this data, regression tests will fail with "No cross_sections.xml file
found" errors, or, in the case that alternative cross section data is configured
the tests will execute but will not pass. The cross_sections.xml file is an
index listing paths to individual HDF5 nuclear data files for each nuclide.
- Data: As described above, OpenMC's test suite requires OpenMC to be configured with NNDC data.
- OpenMP Settings: OpenMC's tests may fail is more than two OpenMP threads are used. The environment variable
OMP_NUM_THREADS=2should be set to avoid sporadic test failures. - Executable configuration: The OpenMC executable should compiled with debug symbols enabled.
Located in tests/cpp_unit_tests/, use Catch2 framework. Run via ctest after building with -DOPENMC_BUILD_TESTS=ON.
Located in tests/unit_tests/, these are fast, standalone tests that verify Python API functionality without running full simulations. Use standard pytest patterns:
Categories:
- API validation: Test object creation, property setters/getters, XML serialization (e.g.,
test_material.py,test_cell.py,test_source.py) - Data processing: Test nuclear data handling, cross sections, depletion chains (e.g.,
test_data_neutron.py,test_deplete_chain.py) - Library bindings: Test
openmc.libctypes interface withmodel.init_lib()/model.finalize_lib()(e.g.,test_lib.py) - Geometry operations: Test bounding boxes, containment, lattice generation (e.g.,
test_bounding_box.py,test_lattice.py)
Common patterns:
- Use fixtures from
tests/unit_tests/conftest.py(e.g.,uo2,water,sphere_model) - Test invalid inputs with
pytest.raises(ValueError)orpytest.raises(TypeError) - Use
run_in_tmpdirfixture for tests that create files - Tests with
openmc.librequire callingmodel.init_lib()in try/finally withmodel.finalize_lib()
Example:
def test_material_properties():
m = openmc.Material()
m.add_nuclide('U235', 1.0)
assert 'U235' in m.nuclides
with pytest.raises(TypeError):
m.add_nuclide('H1', '1.0') # Invalid typeUnit tests should be fast. For tests requiring simulation output, use regression tests instead.
Regression tests compare OpenMC output against reference data. Prefer using existing models from openmc.examples or those found in tests/unit_tests/conftest.py (like pwr_pin_cell(), pwr_assembly(), slab_mg()) rather than building from scratch.
Test Harness Types (in tests/testing_harness.py):
- PyAPITestHarness: Standard harness for Python API tests. Compares
inputs_true.dat(XML hash) andresults_true.dat(statepoint k-eff and tally values). Requiresmodel.xmlgeneration. - HashedPyAPITestHarness: Like PyAPITestHarness but hashes the results for compact comparison
- TolerantPyAPITestHarness: For tests with floating-point non-associativity (e.g., random ray solver with single precision). Uses relative tolerance comparisons.
- WeightWindowPyAPITestHarness: Compares weight window bounds from
weight_windows.h5 - CollisionTrackTestHarness: Compares collision track data from
collision_track.h5againstcollision_track_true.h5 - TestHarness: Base harness for XML-based tests (no Python model building)
- PlotTestHarness: Compares plot output files (PNG or voxel HDF5)
- CMFDTestHarness: Specialized for CMFD acceleration tests
- ParticleRestartTestHarness: Tests particle restart functionality
Almost all cases use either PyAPITestHarness or HashedPyAPITestHarness
Example Test:
from openmc.examples import pwr_pin_cell
from tests.testing_harness import PyAPITestHarness
def test_my_feature():
model = pwr_pin_cell()
model.settings.particles = 1000 # Modify to exercise feature
harness = PyAPITestHarness('statepoint.10.h5', model)
harness.main()Workflow: Create test.py and __init__.py in tests/regression_tests/my_test/, run pytest --update to generate reference files (inputs_true.dat, results_true.dat, etc.), then verify with pytest without --update. Test results should be generated with a debug build (-DCMAKE_BUILD_TYPE=Debug)
Critical: When modifying OpenMC code, regenerate affected test references with pytest --update and commit updated reference files.
pytest.ini sets: python_files = test*.py, python_classes = NoThanks (disables class-based test collection).
For builds of OpenMC with MPI enabled, the --mpi flag should be passed to the test suite to ensure that appropriate tests are executed using two MPI processes.
The entire test suite can be executed with OpenMC running in event-based mode (instead of the default history-based mode) by providing the --event flag to the pytest command.
The C API (defined in include/openmc/capi.h) exposes C++ functionality to Python via ctypes bindings in openmc/lib/. Example:
// C++ API in capi.h
extern "C" int openmc_run();
// Python binding in openmc/lib/core.py
_dll.openmc_run.restype = c_int
def run():
_dll.openmc_run()When modifying C++ public APIs, update corresponding ctypes signatures in openmc/lib/*.py.
OpenMC generally tries to follow C++ core guidelines where possible (https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) and follow modern C++ practices (e.g. RAII) whenever possible.
- Naming:
- Classes:
CamelCase(e.g.,HexLattice) - Functions/methods:
snake_case(e.g.,get_indices) - Variables:
snake_casewith trailing underscore for class members (e.g.,n_particles_,energy_) - Constants:
UPPER_SNAKE_CASE(e.g.,SQRT_PI)
- Classes:
- Namespaces: All code in
openmc::namespace, global state in sub-namespaces - Include order: Related header first, then C/C++ stdlib, third-party libs, local headers
- Comments: C++-style (
//) only, never C-style (/* */) - Standard: C++17 features allowed
- Formatting: Run
clang-format(version 15) before committing; install viatools/dev/install-commit-hooks.sh
- PEP8 compliant
- Docstrings: numpydoc format for all public functions/methods
- Type hints: Use sparingly, primarily for complex signatures
- Path handling: Use
pathlib.Pathfor filesystem operations, acceptstr | os.PathLikein function arguments - Dependencies: Core dependencies only (numpy, scipy, h5py, pandas, matplotlib, lxml, ipython, uncertainties, setuptools, endf). Other packages must be optional
- Python version: Minimum 3.11 (as of Nov 2025)
When creating geometry objects, IDs can be auto-assigned or explicit:
# Auto-assigned ID
cell = openmc.Cell() # Gets next available ID
# Explicit ID
cell = openmc.Cell(id=10) # Warning if ID already used
# Reset all IDs (useful in test fixtures)
openmc.reset_auto_ids()All setters use checkvalue functions:
import openmc.checkvalue as cv
@property
def temperature(self):
return self._temperature
@temperature.setter
def temperature(self, temp):
cv.check_type('temperature', temp, Real)
cv.check_greater_than('temperature', temp, 0.0)
self._temperature = tempC++ uses custom HDF5 wrappers in src/hdf5_interface.cpp. Python uses h5py directly. Statepoint format version is VERSION_STATEPOINT in include/openmc/constants.h.
Check for optional features:
#ifdef OPENMC_MPI
// MPI-specific code
#endif
#ifdef OPENMC_DAGMC
// DAGMC-specific code
#endif- User docs: Sphinx documentation in
docs/source/hosted at https://docs.openmc.org - C++ docs: Doxygen-style comments with
\brief,\paramtags - Python docs: numpydoc format docstrings
- Forgetting nuclear data: Tests fail without
OPENMC_CROSS_SECTIONSenvironment variable - ID conflicts: Python objects with duplicate IDs trigger
IDWarning, usereset_auto_ids()between tests - MPI builds: Code must work with and without MPI; use
#ifdef OPENMC_MPIguards - Path handling: Use
pathlib.Pathin new Python code, notos.path - Clang-format version: CI uses version 15; other versions may produce different formatting