add CLAUDE.md with build, test, and architecture documentation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
miniCaloSim is a Geant4-based calorimeter simulator designed for teaching. It exposes a Python API via pybind11 so students can define calorimeter geometries, run batch simulations, and visualise results — all from a Jupyter notebook.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Requires Geant4 (v11.1.2), pybind11 (submodule at `lib/pybind11`), and Python 3 development headers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces two artifacts:
|
||||||
|
- `build/minicalo.so` — the pybind11 Python module
|
||||||
|
- `build/exampleB4a` — a standalone Geant4 executable (for reference only)
|
||||||
|
|
||||||
|
After building, copy the Python files to the Python path for use outside the `bind/` directory (the Dockerfile shows the canonical install paths):
|
||||||
|
```bash
|
||||||
|
cp build/minicalo*.so bind/G4Calo.py bind/minicalo_tools.py bind/minipandas.py /target/python/path/
|
||||||
|
cp bind/G4Calo_exec.py /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the minipandas unit tests (no Geant4 required):
|
||||||
|
```bash
|
||||||
|
cd bind && python3 test_minipandas.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the TempFileManager unit tests:
|
||||||
|
```bash
|
||||||
|
cd bind && python3 -m unittest minicalo_tools.TestTempFileManager
|
||||||
|
```
|
||||||
|
|
||||||
|
There are no automated tests for the full simulation — student usage is the primary validation path.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### C++ / Geant4 layer (`src/`, `include/`)
|
||||||
|
|
||||||
|
The simulation is a standard Geant4 application derived from example B4a. The key extension is that geometry is driven externally via `GeometryDescriptor` rather than hardcoded:
|
||||||
|
|
||||||
|
- **`GeometryDescriptor`** (`include/GeometryDescriptor.hh`) — the central geometry object. Holds a list of `Layer` objects, each of which contains `Sensor` objects after construction. Sensors accumulate deposited energy during a run. Fully pickle-able via `__getstate__`/`__setstate__`.
|
||||||
|
- **`Layer`** — describes one slab: material (NIST name), thickness (cm), active/passive, and an `nx × ny` sensor grid.
|
||||||
|
- **`Sensor`** — stores position, size, and `mutable energy` (reset between events via `GeometryDescriptor::resetSensorEnergies()`).
|
||||||
|
- **`DetectorConstruction`** (`src/DetectorConstruction.cc`) — reads `GeometryDescriptor` and builds Geant4 volumes. Uses `G4PVParameterised` + `LayerParametrisation` for the sensor grid. Active sensors have their `G4VPhysicalVolume*` assigned back into the `Layer`, enabling lookup in `SteppingAction`.
|
||||||
|
- **`SteppingAction`** — on each step, maps the physical volume back to a `Sensor` via `GeometryDescriptor::getSensorByVolume()` and accumulates energy.
|
||||||
|
- **`RunAction`** — writes a ROOT ntuple named `"Hits"` with per-event scalars (`true_energy`, `total_dep_energy`, `N_layers`, ...) and per-sensor arrays (`sensor_energy`, `sensor_x/y/z`, `sensor_dx/dy/dz`, `sensor_layer`, `sensor_copy_number`).
|
||||||
|
- **`G4System`** (`include/G4System.hh`) — owns the Geant4 run manager lifetime. `init(GeometryDescriptor&, seed)` sets everything up; `run_batch(...)` fires events to a ROOT file. One `G4System` per process — instantiated inside the subprocess (`G4Calo_exec.py`) to avoid Geant4 singleton issues.
|
||||||
|
|
||||||
|
### pybind11 bindings (`bind/bindings.cpp`)
|
||||||
|
|
||||||
|
Exposes `Sensor`, `Layer`, `GeometryDescriptor`, and `G4System` to Python as the `minicalo` module.
|
||||||
|
|
||||||
|
### Python layer (`bind/`)
|
||||||
|
|
||||||
|
- **`G4Calo.py`** — the public student API. `run_batch()` parallelises events across CPU cores using `ThreadPoolExecutor`; each worker subprocess calls `G4Calo_exec.py`, which instantiates a fresh `G4System` to avoid Geant4 singleton issues. Results are written as temporary ROOT files in `/dev/shm` (fallback: `/tmp`), read back with `uproot`/`awkward`, and merged into a `MiniFrame`. `display_event()` runs a single event and renders a 3D Plotly figure.
|
||||||
|
- **`G4Calo_exec.py`** — the worker subprocess entry point. Reads a pickled `GeometryDescriptor` + parameters, runs the simulation, writes a ROOT file, and returns the path. Must be on `$PATH` as an executable.
|
||||||
|
- **`minicalo_tools.py`** — `TempFileManager`: context manager for `/dev/shm`-backed pickle IPC files between the main process and worker subprocesses.
|
||||||
|
- **`minipandas.py`** — `MiniFrame`: a minimal NumPy-backed dataframe. Supports scalar columns `(N,)` and fixed-size vector columns `(N, M)`. Converts to `pandas.DataFrame` via `to_pandas()` and serialises with `to_pickle()`/`read_pickle()`.
|
||||||
|
|
||||||
|
### Subprocess isolation pattern
|
||||||
|
|
||||||
|
Geant4 uses global singletons that cannot be re-initialised within a process. Therefore, each simulation batch (even in "single-core" mode) runs in a fresh subprocess spawned by `G4Calo_exec.py`. Communication uses pickle files via `TempFileManager`; ROOT output files are used for event data.
|
||||||
|
|
||||||
|
### Coordinate conventions
|
||||||
|
|
||||||
|
Geant4 internal units are mm; sensor positions stored in `Sensor` are in Geant4 internal units. The Plotly visualisation multiplies layer dimensions by 10 to convert from cm to mm and labels axes with `[mm]`.
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
`docker/Dockerfile` builds a JupyterHub-based image with Geant4 v11.1.2 (multithreading disabled, `GEANT4_BUILD_MULTITHREADED=OFF`), the minicalo package, and ML dependencies (PyTorch, PyG). `docker/Dockerfile-cpu` is the CPU-only variant.
|
||||||
Reference in New Issue
Block a user