mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-21 14:35:27 -04:00
Co-authored-by: John Tramm <jtramm@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
348 lines
17 KiB
Markdown
348 lines
17 KiB
Markdown
# OpenMC AI Coding Agent Instructions
|
|
|
|
## Project Overview
|
|
|
|
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
|
|
|
|
## Architecture & Key Components
|
|
|
|
### C++ Component Structure
|
|
- **Global vectors of unique_ptrs**: Core objects like `model::cells`, `model::universes`, `nuclides` are stored as `vector<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`, and `make_unique` in the `openmc::` namespace (defined in `vector.h`, `array.h`, `memory.h`). These are currently typedefs to `std::` equivalents but may become custom implementations for accelerator support. Always use `openmc::vector`, not `std::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`)
|
|
- **Particle tracking**: `Particle` class with `GeometryState` manages particle transport through geometry
|
|
- **Tallies**: Score quantities during simulation via `Filter` and `Tally` objects
|
|
- **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.
|
|
|
|
### Python Component Structure
|
|
- **ID management**: All geometry objects (Cell, Surface, Material, etc.) inherit from `IDManagerMixin` which auto-assigns unique integer IDs and tracks them via class-level `used_ids` and `next_id`
|
|
- **Input validation**: Extensive use of `openmc.checkvalue` module functions (`check_type`, `check_value`, `check_length`) for all setters
|
|
- **XML I/O**: Most classes implement `to_xml_element()` and `from_xml_element()` for serialization to OpenMC's XML input format
|
|
- **HDF5 output**: Post-simulation data in statepoint files read via `openmc.StatePoint`
|
|
- **Depletion**: `openmc.deplete` implements burnup via operator-splitting with various integrators (Predictor, CECM, etc.)
|
|
- **Nuclear Data**: `openmc.data` provides programmatic access to nuclear data files (ENDF, ACE, HDF5)
|
|
|
|
## Git Branching Workflow
|
|
|
|
OpenMC uses a git flow branching model with two primary branches:
|
|
|
|
- **`develop` branch**: 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.
|
|
- **`master` branch**: The stable release branch containing the latest stable release of OpenMC. This branch only receives merges from `develop` when the development team decides a release should occur.
|
|
|
|
### Instructions for Code Review
|
|
|
|
When reviewing code changes in this repository, use the `reviewing-openmc-code` skill.
|
|
|
|
## Codebase Navigation Tools
|
|
|
|
Two MCP tools are registered in `.mcp.json` at the repo root and appear
|
|
automatically in any MCP-capable agent session.
|
|
|
|
**`openmc_rag_search`** — Semantic search across the codebase (C++, Python, RST
|
|
docs). Finds code by meaning, not just text match. Surfaces related code across
|
|
subsystems even when naming differs (e.g., "particle RNG seeding" finds code
|
|
across transport, restart, and random ray modes — files you would never find
|
|
with `grep "particle seed"`). The index uses a small 22M-param embedding model
|
|
(384-dim). Phrase-level natural-language queries work much better than single
|
|
keywords or symbol names.
|
|
|
|
**`openmc_rag_rebuild`** — Rebuild the RAG vector index. Call after pulling new
|
|
code or switching branches. The first RAG search of each session will report
|
|
the index status and ask whether to rebuild — you can also call this explicitly.
|
|
|
|
### Why RAG matters
|
|
|
|
OpenMC is large enough that changes in one subsystem can silently break
|
|
invariants that distant subsystems depend on — and those distant files often
|
|
use different naming, so grep won't find them. The RAG search finds code by
|
|
meaning, surfacing files you wouldn't have thought to open.
|
|
|
|
An agent reviewed a large OpenMC PR without RAG. It found 1 of 11 serious
|
|
bugs. Its post-mortem:
|
|
|
|
> **I treated the diff as a closed system.** I verified internal consistency of
|
|
> the changed code obsessively, but never built a global understanding of how
|
|
> the changed code fits into the wider codebase. The diff altered assumptions
|
|
> that code elsewhere silently relied on — but I couldn't see that because I
|
|
> never looked beyond the diff. I couldn't see the forest for the trees.
|
|
>
|
|
> **Why I resisted RAG:** Overconfidence. My internal model was "I can see the
|
|
> diff, I understand the data structures, I can trace the logic." The diff felt
|
|
> self-contained. RAG felt like it would return noisy results about tangentially
|
|
> related code. But in a codebase this large, changes in one subsystem can
|
|
> quietly break invariants that distant subsystems depend on — and you need
|
|
> global awareness to foresee that.
|
|
>
|
|
> **In the post-mortem**, I re-ran the RAG queries I should have run during the
|
|
> review. They directly surfaced the files containing the bugs I missed — files
|
|
> I never thought to open because they weren't in the diff.
|
|
|
|
The takeaway: when reviewing or modifying code, ask yourself "what else in this
|
|
codebase might depend on the behavior I'm changing?" If you aren't sure, that's
|
|
a good time for a RAG query. It won't replace the grep-based investigation you
|
|
should already be doing — but it can surface files you wouldn't have thought to
|
|
open.
|
|
|
|
### Workflow for contributors
|
|
|
|
1. Create a feature/bugfix branch off `develop`
|
|
2. Make changes and commit to the feature branch
|
|
3. Open a pull request to merge the feature branch into `develop`
|
|
4. A committer reviews and merges the PR into `develop`
|
|
|
|
## Critical Build & Test Workflows
|
|
|
|
### Build Dependencies
|
|
- **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.
|
|
|
|
### Building the C++ Library
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
### Python Development
|
|
```bash
|
|
# 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)
|
|
```
|
|
|
|
### Nuclear Data Setup (CRITICAL for Running OpenMC)
|
|
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:**
|
|
```bash
|
|
# 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.xml
|
|
```
|
|
|
|
**Alternative**: Use the provided download script (checks if data exists before downloading):
|
|
```bash
|
|
bash tools/ci/download-xs.sh # Downloads both NNDC HDF5 and ENDF/B-VII.1 data
|
|
```
|
|
|
|
Without 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.
|
|
|
|
## Testing Expectations
|
|
|
|
### Environment Requirements
|
|
|
|
- **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=2` should be set to avoid sporadic test failures.
|
|
- **Executable configuration**: The OpenMC executable should compiled with debug symbols enabled.
|
|
|
|
### C++ Tests
|
|
Located in `tests/cpp_unit_tests/`, use Catch2 framework. Run via `ctest` after building with `-DOPENMC_BUILD_TESTS=ON`.
|
|
|
|
### Python Unit Tests
|
|
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.lib` ctypes interface with `model.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)` or `pytest.raises(TypeError)`
|
|
- Use `run_in_tmpdir` fixture for tests that create files
|
|
- Tests with `openmc.lib` require calling `model.init_lib()` in try/finally with `model.finalize_lib()`
|
|
|
|
**Example**:
|
|
```python
|
|
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 type
|
|
```
|
|
|
|
Unit tests should be fast. For tests requiring simulation output, use regression tests instead.
|
|
|
|
### Python Regression Tests
|
|
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) and `results_true.dat` (statepoint k-eff and tally values). Requires `model.xml` generation.
|
|
- **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.h5` against `collision_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**:
|
|
```python
|
|
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 `-DOPENMC_ENABLE_STRICT_FP=on` to ensure reproducibility across platforms and optimization levels.
|
|
|
|
**Critical**: When modifying OpenMC code, regenerate affected test references with `pytest --update` and commit updated reference files.
|
|
|
|
### Test Configuration
|
|
|
|
`pytest.ini` sets: `python_files = test*.py`, `python_classes = NoThanks` (disables class-based test collection).
|
|
|
|
### Testing Options
|
|
|
|
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.
|
|
|
|
## Cross-Language Boundaries
|
|
|
|
The C API (defined in `include/openmc/capi.h`) exposes C++ functionality to Python via ctypes bindings in `openmc/lib/`. Example:
|
|
```cpp
|
|
// 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`.
|
|
|
|
## Code Style & Conventions
|
|
|
|
### C++ Style (enforced by .clang-format)
|
|
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_case` with trailing underscore for class members (e.g., `n_particles_`, `energy_`)
|
|
- Constants: `UPPER_SNAKE_CASE` (e.g., `SQRT_PI`)
|
|
- **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 18) before committing; install via `tools/dev/install-commit-hooks.sh`
|
|
|
|
### Python Style
|
|
- **PEP8** compliant
|
|
- **Docstrings**: numpydoc format for all public functions/methods
|
|
- **Type hints**: Use sparingly, primarily for complex signatures
|
|
- **Path handling**: Use `pathlib.Path` for filesystem operations, accept `str | os.PathLike` in function arguments
|
|
- **Dependencies**: Core dependencies only (numpy, scipy, h5py, pandas, matplotlib, lxml, ipython, uncertainties, endf). Other packages must be optional
|
|
- **Python version**: Minimum 3.11 (as of Nov 2025)
|
|
|
|
### ID Management Pattern (Python)
|
|
When creating geometry objects, IDs can be auto-assigned or explicit:
|
|
```python
|
|
# 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()
|
|
```
|
|
|
|
### Input Validation Pattern (Python)
|
|
All setters use checkvalue functions:
|
|
```python
|
|
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 = temp
|
|
```
|
|
|
|
### Working with HDF5 Files
|
|
C++ uses custom HDF5 wrappers in `src/hdf5_interface.cpp`. Python uses h5py directly. Statepoint format version is `VERSION_STATEPOINT` in `include/openmc/constants.h`.
|
|
|
|
### Conditional Compilation
|
|
Check for optional features:
|
|
```cpp
|
|
#ifdef OPENMC_MPI
|
|
// MPI-specific code
|
|
#endif
|
|
|
|
#ifdef OPENMC_DAGMC
|
|
// DAGMC-specific code
|
|
#endif
|
|
```
|
|
|
|
## Documentation
|
|
|
|
- **User docs**: Sphinx documentation in `docs/source/` hosted at https://docs.openmc.org
|
|
- **C++ docs**: Doxygen-style comments with `\brief`, `\param` tags
|
|
- **Python docs**: numpydoc format docstrings
|
|
|
|
## Common Pitfalls
|
|
|
|
1. **Forgetting nuclear data**: Tests fail without `OPENMC_CROSS_SECTIONS` environment variable
|
|
2. **ID conflicts**: Python objects with duplicate IDs trigger `IDWarning`, use `reset_auto_ids()` between tests
|
|
3. **MPI builds**: Code must work with and without MPI; use `#ifdef OPENMC_MPI` guards
|
|
4. **Path handling**: Use `pathlib.Path` in new Python code, not `os.path`
|
|
5. **Clang-format version**: CI uses version 18; other versions may produce different formatting
|