# 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
