rewrite README with full project and Geant4 modification docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,31 +1,285 @@
|
|||||||
# miniCaloSim
|
# miniCaloSim
|
||||||
|
|
||||||
|
A Geant4-based calorimeter simulator for teaching and research. Supports arbitrary sampling or homogeneous calorimeter geometries, runs batches of particle showers, and writes ROOT output for analysis.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Getting started
|
## Contents
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Repository layout](#repository-layout)
|
||||||
|
- [Build](#build)
|
||||||
|
- [Option A — system Geant4](#option-a--system-geant4)
|
||||||
|
- [Option B — build Geant4 from submodule](#option-b--build-geant4-from-submodule)
|
||||||
|
- [Running](#running)
|
||||||
|
- [ROOT output format](#root-output-format)
|
||||||
|
- [Hits tree](#hits-tree)
|
||||||
|
- [Steps tree](#steps-tree)
|
||||||
|
- [Spawning tree](#spawning-tree)
|
||||||
|
- [Calorimeter materials](#calorimeter-materials)
|
||||||
|
- [Docker](#docker)
|
||||||
|
- [Geant4 modifications](#geant4-modifications)
|
||||||
|
|
||||||
### Materials
|
---
|
||||||
|
|
||||||
Below are some useful material choices. Not all information is provided, but can be found on the internet ;)
|
## Overview
|
||||||
|
|
||||||
## Scintillator materials:
|
miniCaloSim wraps the standard Geant4 B4a example into a configurable geometry driven by a `GeometryDescriptor` object. A geometry is a stack of layers; each layer is a slab of named NIST material with an optional sensor grid that accumulates deposited energy. The primary particle type and energy are configurable at run time.
|
||||||
|
|
||||||
* G4_POLYSTYRENE: a polymer of styrene with a density of 1.06 g/cm3 and a refractive index of 1.57. Cost: basically nothing.
|
The default executable `run_pbwo4` simulates electron showers in a 20 cm PbWO₄ crystal with a 10×10 sensor grid and writes a ROOT file.
|
||||||
* G4_PLASTIC_SC_VINYLTOLUENE: a plastic scintillator based on vinyltoluene with a density of 1.032 g/cm3 and a refractive index of 1.58. Cost: basically nothing.
|
|
||||||
* G4_BGO: bismuth germanate, an inorganic crystal scintillator with a density of 7.13 g/cm3 and a refractive index of 2.15. Cost 0.8 CHF/cm3
|
|
||||||
* G4_LSO: lutetium oxyorthosilicate, an inorganic crystal scintillator with a density of 7.4 g/cm3 and a refractive index of 1.82. Cost: 6 CHF/cm3
|
|
||||||
* G4_LYSO: lutetium yttrium oxyorthosilicate, an inorganic crystal scintillator with a density of 7.1 g/cm3 and a refractive index of 1.81. 30CHF / cm3
|
|
||||||
|
|
||||||
## Absorber materials
|
---
|
||||||
|
|
||||||
* G4_Pb: lead, a high-density and high-Z material that is commonly used as an absorber for electromagnetic calorimeters. It has a density of 11.35 g/cm3 and a radiation length of 0.56 cm. Cost: 0.03 CHF/cm3.
|
## Repository layout
|
||||||
* G4_Fe: iron, a medium-density and medium-Z material that is often used as an absorber for hadronic calorimeters. It has a density of 7.87 g/cm3 and an interaction length of 16.77 cm. Cost: 0.005 CHF/cm3.
|
|
||||||
* G4_W: tungsten, a very high-density and very high-Z material that is suitable for compact and high-resolution electromagnetic calorimeters. It has a density of 19.3 g/cm3 and a radiation length of 0.35 cm. 0.6 CHF/cm3.
|
|
||||||
* G4_Cu: copper, a high-density and high-Z material that is also used as an absorber for electromagnetic calorimeters. It has a density of 8.96 g/cm3 and a radiation length of 1.43 cm. 0.08 CHF/cm3
|
|
||||||
* G4_BRASS: It has a density of 8.53 g/cm3 and a composition of 70% copper and 30% zinc by mass. It has a radiation length of 1.46 cm and an interaction length of 16.95 cm. Cost: 0.02 CHF/cm3
|
|
||||||
|
|
||||||
## For homogenous calorimeters
|
```
|
||||||
|
run_pbwo4.cc main executable entry point
|
||||||
|
src/ Geant4 application sources
|
||||||
|
ActionInitialization.cc
|
||||||
|
DetectorConstruction.cc reads GeometryDescriptor, builds Geant4 volumes
|
||||||
|
EventAction.cc fills the Hits ntuple at end of each event
|
||||||
|
SteppingAction.cc fills Steps and Spawning ntuples per step
|
||||||
|
RunAction.cc creates all three ROOT ntuples
|
||||||
|
G4System.cc owns the Geant4 run manager lifetime
|
||||||
|
GeometryDescriptor.cc geometry data model (layers + sensors)
|
||||||
|
PrimaryGeneratorAction.cc
|
||||||
|
include/ corresponding headers
|
||||||
|
bind/ Python bindings (pybind11 + pure-Python helpers)
|
||||||
|
bindings.cpp exposes Sensor, Layer, GeometryDescriptor, G4System
|
||||||
|
G4Calo.py high-level Python API (run_batch, display_event)
|
||||||
|
G4Calo_exec.py worker subprocess entry point
|
||||||
|
minicalo_tools.py TempFileManager for subprocess IPC via /dev/shm
|
||||||
|
minipandas.py MiniFrame: minimal NumPy-backed dataframe
|
||||||
|
lib/
|
||||||
|
geant4/ Geant4 source submodule (fork with modifications)
|
||||||
|
pybind11/ pybind11 submodule
|
||||||
|
superbuild/ CMake superbuild to compile Geant4 from source
|
||||||
|
docker/ Dockerfile-mini
|
||||||
|
docs/ design documents
|
||||||
|
```
|
||||||
|
|
||||||
* G4_CESIUM_IODIDE: cesium iodide, an inorganic crystal scintillator with a density of 4.51 g/cm3 and a refractive index of 1.79. radiation length about 1.9 cm. Cost: about 2 CHF/cm3
|
---
|
||||||
* G4_PbWO4: lead tungstate crystal (CMS ECAL). Lead tungstate has a very high density of 19.3 g/cm3 and a very short radiation length of 0.89 cm, which makes it suitable for compact and high-resolution electromagnetic calorimeters. Cost: about 3 CHF/cm3
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
### Option A — system Geant4
|
||||||
|
|
||||||
|
Requires a Geant4 ≥ 11 installation visible to CMake (e.g. via `Geant4_DIR` or `CMAKE_PREFIX_PATH`), plus pybind11 (submodule) and Python 3 development headers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -S .
|
||||||
|
cmake --build build --parallel $(nproc)
|
||||||
|
# executable: build/run_pbwo4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B — build Geant4 from submodule
|
||||||
|
|
||||||
|
The superbuild compiles Geant4 from `lib/geant4` into `build/geant4-install`, then builds minicalosim against it. Three targets are available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -S superbuild/ # configure once
|
||||||
|
|
||||||
|
cmake --build build --target geant4 # compile Geant4 (~30–60 min)
|
||||||
|
cmake --build build --target run_pbwo4 # compile minicalosim
|
||||||
|
cmake --build build # both
|
||||||
|
```
|
||||||
|
|
||||||
|
After the build, the executable is at `build/run_pbwo4` (symlink in the superbuild case).
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
|
||||||
|
| CMake option | Default | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `GEANT4_INSTALL_DATA` | `ON` | Download Geant4 physics data tables (~2 GB) |
|
||||||
|
|
||||||
|
The superbuild caps parallelism at `min(nproc, 16)` to avoid memory exhaustion.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# default: 10 events, writes pbwo4_10events_hits.root
|
||||||
|
build/run_pbwo4
|
||||||
|
|
||||||
|
# custom number of events
|
||||||
|
build/run_pbwo4 500
|
||||||
|
```
|
||||||
|
|
||||||
|
Output file name: `pbwo4_<nEvents>events_hits.root`.
|
||||||
|
|
||||||
|
The geometry is hardcoded in `run_pbwo4.cc`: a single 20 cm PbWO₄ layer with a 10×10 sensor grid, bombarded with 1 GeV electrons.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROOT output format
|
||||||
|
|
||||||
|
Each run produces a ROOT file with three TTrees.
|
||||||
|
|
||||||
|
### Hits tree
|
||||||
|
|
||||||
|
One row per simulated event. Contains integrated quantities over the whole calorimeter.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `true_energy` | `double` | Primary particle energy [MeV] |
|
||||||
|
| `total_dep_energy` | `double` | Total energy deposited in all sensors [MeV] |
|
||||||
|
| `N_layers` | `int` | Number of layers |
|
||||||
|
| `N_active_layers` | `int` | Number of active (sensitive) layers |
|
||||||
|
| `N_sensors` | `int` | Total number of sensors |
|
||||||
|
| `sensor_energy[N]` | `double[]` | Deposited energy per sensor [MeV] |
|
||||||
|
| `sensor_x/y/z[N]` | `double[]` | Sensor centre position [mm] |
|
||||||
|
| `sensor_dx/dy/dz[N]` | `double[]` | Sensor half-size [mm] |
|
||||||
|
| `sensor_layer[N]` | `int[]` | Layer index of each sensor |
|
||||||
|
| `sensor_copy_number[N]` | `int[]` | Copy number within its layer |
|
||||||
|
|
||||||
|
### Steps tree
|
||||||
|
|
||||||
|
One row per Geant4 step. Every step of every track of every event is recorded.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `event_id` | `int` | Event index within the run |
|
||||||
|
| `track_id` | `int` | Track ID (1 = primary, ≥2 = secondaries) |
|
||||||
|
| `step_no` | `int` | Step number within this track (starts at 1) |
|
||||||
|
| `pdg` | `int` | PDG encoding of the particle |
|
||||||
|
| `pre_x/y/z` | `double` | Pre-step position [mm] |
|
||||||
|
| `pre_E` | `double` | Kinetic energy at step start [MeV] |
|
||||||
|
| `post_x/y/z` | `double` | Post-step position [mm] |
|
||||||
|
| `post_E` | `double` | Kinetic energy at step end [MeV] |
|
||||||
|
| `edep` | `double` | Energy deposited in medium at this step [MeV] |
|
||||||
|
| `step_length` | `double` | Geometric step length [mm] |
|
||||||
|
| `process` | `string` | Physics process that ended this step |
|
||||||
|
| `layer_id` | `int` | Layer index at the pre-step point (-1 = outside all layers) |
|
||||||
|
| `material` | `string` | Material name at the pre-step point |
|
||||||
|
| `pre_dx/dy/dz` | `double` | Pre-step momentum unit direction |
|
||||||
|
| `Bx/By/Bz` | `double` | Magnetic field at pre-step position [T] |
|
||||||
|
| `Ex/Ey/Ez` | `double` | Electric field at pre-step position [V/m] |
|
||||||
|
| `child_track_ids` | `int[]` | Track IDs of all secondaries spawned during this step (empty if none) |
|
||||||
|
|
||||||
|
**Track IDs** are assigned sequentially per event starting from 1. The primary particle is always track 1. Within an event, `track_id` uniquely identifies a particle. See also the Spawning tree.
|
||||||
|
|
||||||
|
### Spawning tree
|
||||||
|
|
||||||
|
One row per secondary particle birth. Enables direct shower-tree reconstruction without position joins.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `event_id` | `int` | Event index |
|
||||||
|
| `parent_track_id` | `int` | Track ID of the parent particle |
|
||||||
|
| `parent_step_no` | `int` | Step number of the parent at which this secondary was created |
|
||||||
|
| `child_track_id` | `int` | Track ID of the newly born secondary |
|
||||||
|
|
||||||
|
**Example join:** to find all secondaries born at step 5 of track 3 in event 0:
|
||||||
|
```python
|
||||||
|
spawning[(spawning.event_id == 0) &
|
||||||
|
(spawning.parent_track_id == 3) &
|
||||||
|
(spawning.parent_step_no == 5)]
|
||||||
|
```
|
||||||
|
|
||||||
|
The same information is available in the Steps tree via the `child_track_ids` array column on the parent's row.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Calorimeter materials
|
||||||
|
|
||||||
|
A few material choices available via NIST names:
|
||||||
|
|
||||||
|
### Scintillators (active layers)
|
||||||
|
|
||||||
|
| NIST name | Density | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `G4_POLYSTYRENE` | 1.06 g/cm³ | cheap plastic scintillator |
|
||||||
|
| `G4_PLASTIC_SC_VINYLTOLUENE` | 1.032 g/cm³ | plastic scintillator |
|
||||||
|
| `G4_BGO` | 7.13 g/cm³ | inorganic crystal, ~0.8 CHF/cm³ |
|
||||||
|
| `G4_LSO` | 7.4 g/cm³ | inorganic crystal, ~6 CHF/cm³ |
|
||||||
|
| `G4_LYSO` | 7.1 g/cm³ | inorganic crystal, ~30 CHF/cm³ |
|
||||||
|
|
||||||
|
### Absorbers (passive layers)
|
||||||
|
|
||||||
|
| NIST name | Density | X₀ | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `G4_Pb` | 11.35 g/cm³ | 0.56 cm | standard EM calorimeter absorber |
|
||||||
|
| `G4_Fe` | 7.87 g/cm³ | — | hadronic calorimeter absorber |
|
||||||
|
| `G4_W` | 19.3 g/cm³ | 0.35 cm | compact high-resolution EM |
|
||||||
|
| `G4_Cu` | 8.96 g/cm³ | 1.43 cm | EM calorimeter absorber |
|
||||||
|
| `G4_BRASS` | 8.53 g/cm³ | 1.46 cm | 70% Cu / 30% Zn |
|
||||||
|
|
||||||
|
### Homogeneous calorimeters
|
||||||
|
|
||||||
|
| NIST name | Density | X₀ | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `G4_CESIUM_IODIDE` | 4.51 g/cm³ | ~1.9 cm | inorganic crystal |
|
||||||
|
| `G4_PbWO4` | 8.28 g/cm³ | 0.89 cm | lead tungstate (CMS ECAL) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
`docker/Dockerfile-mini` builds a minimal Ubuntu 22.04 image with:
|
||||||
|
- Geant4 compiled from the `lib/geant4` submodule (batch mode, no vis, no multithreading)
|
||||||
|
- The `minicalo` Python package installed system-wide
|
||||||
|
- Python dependencies: numpy, pandas, uproot, awkward, plotly
|
||||||
|
|
||||||
|
Build (from the repo root, passing `COMMIT` to pin the source):
|
||||||
|
```bash
|
||||||
|
docker build \
|
||||||
|
--build-arg COMMIT=$(git rev-parse HEAD) \
|
||||||
|
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||||
|
-f docker/Dockerfile-mini \
|
||||||
|
-t minicalosim:latest \
|
||||||
|
--build-context minicalosim=. \
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Geant4 modifications
|
||||||
|
|
||||||
|
The `lib/geant4` submodule is a fork of Geant4 with three small changes to enable pre-assigning track IDs to secondary particles at the moment they are created, so that `UserSteppingAction` can record which child track IDs a step spawned (the `child_track_ids` column and the Spawning tree).
|
||||||
|
|
||||||
|
### Background
|
||||||
|
|
||||||
|
In unmodified Geant4, track IDs are assigned in `G4EventManager::StackTracks()`, which is called only after the parent track has finished all its steps. At the time `UserSteppingAction` fires, the secondaries visible via `G4Step::GetSecondaryInCurrentStep()` have not yet received their IDs.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
All changes are on the `minicalosim-spawning` branch of `git@gitlab.etp.kit.edu:lbogner/geant4.git`.
|
||||||
|
|
||||||
|
**`source/tracking/include/G4SteppingManager.hh`**
|
||||||
|
|
||||||
|
Adds a `G4int* fTrackIDCounter` member and a `SetTrackIDCounter(G4int*)` setter. The pointer is set by `G4EventManager` (via `G4TrackingManager`) at the start of each event and points directly into `G4EventManager::trackIDCounter`.
|
||||||
|
|
||||||
|
**`source/tracking/include/G4TrackingManager.hh`**
|
||||||
|
|
||||||
|
Adds a `SetTrackIDCounter(G4int*)` pass-through that forwards the pointer into `G4SteppingManager`.
|
||||||
|
|
||||||
|
**`source/tracking/src/G4SteppingManager.cc`**
|
||||||
|
|
||||||
|
In `ProcessSecondariesFromParticleChange()`, after `SetParentID` and `SetCreatorProcess`, immediately assigns a track ID to each secondary by incrementing through the pointer:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (fTrackIDCounter) {
|
||||||
|
tempSecondaryTrack->SetTrackID(++(*fTrackIDCounter));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`source/event/src/G4EventManager.cc`**
|
||||||
|
|
||||||
|
- Calls `trackManager->SetTrackIDCounter(&trackIDCounter)` once before the event's tracking loop.
|
||||||
|
- `StackTracks()` is updated to skip counter increment and ID assignment for tracks that already have a non-zero ID (i.e. were pre-assigned above):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (IDhasAlreadySet) {
|
||||||
|
++trackIDCounter; // advance for primaries (ID set externally)
|
||||||
|
} else if (newTrack->GetTrackID() == 0) {
|
||||||
|
++trackIDCounter; // normal secondary: assign as before
|
||||||
|
newTrack->SetTrackID(trackIDCounter);
|
||||||
|
...
|
||||||
|
}
|
||||||
|
// else: pre-assigned secondary — counter already incremented, skip
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why not include `G4EventManager.hh` in `G4SteppingManager.cc`?
|
||||||
|
|
||||||
|
`G4event` already depends on `G4tracking` (`G4EventManager` uses `G4TrackingManager`), so including `G4EventManager.hh` in the tracking module would create a circular dependency. The pointer approach avoids any new include: `G4SteppingManager` only needs a `G4int*`, which requires no additional header.
|
||||||
|
|||||||
Reference in New Issue
Block a user