Add per-step Spawning ntuple, Geant4 submodule superbuild, and standalone dataset executables #1

Merged
lbogner merged 33 commits from spawning-ntuple into main 2026-07-13 14:06:25 +02:00
42 changed files with 1154 additions and 2365 deletions
+3
View File
@@ -2,3 +2,6 @@
path = lib/pybind11
url = https://github.com/pybind/pybind11
branch = stable
[submodule "lib/geant4"]
path = lib/geant4
url = git@gitlab.etp.kit.edu:lbogner/geant4.git
+78
View File
@@ -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.
+9 -26
View File
@@ -55,35 +55,18 @@ target_link_libraries(minicalo PUBLIC ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
#----------------------------------------------------------------------------
# Add the executable, and link it to the Geant4 and Python libraries
#
add_executable(exampleB4a exampleB4a.cc ${sources} ${headers})
target_link_libraries(exampleB4a ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
#----------------------------------------------------------------------------
# Copy all scripts to the build directory, i.e. the directory in which we
# build B4a. This is so that we can run the executable directly because it
# relies on these scripts being in the current working directory.
#
set(EXAMPLEB4A_SCRIPTS
exampleB4a.out
exampleB4.in
gui.mac
init_vis.mac
plotHisto.C
plotNtuple.C
run1.mac
run2.mac
vis.mac
)
add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers})
target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
foreach(_script ${EXAMPLEB4A_SCRIPTS})
configure_file(
${PROJECT_SOURCE_DIR}/${_script}
${PROJECT_BINARY_DIR}/${_script}
COPYONLY
)
endforeach()
add_executable(run_sampling run_sampling.cc ${sources} ${headers})
target_link_libraries(run_sampling ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
add_executable(export_xsec export_xsec.cc ${sources} ${headers})
target_link_libraries(export_xsec ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
#----------------------------------------------------------------------------
# Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX
#
#install(TARGETS exampleB4a DESTINATION bin)
#install(TARGETS run_pbwo4 DESTINATION bin)
-21
View File
@@ -1,21 +0,0 @@
# --------------------------------------------------------------
# GNUmakefile for examples module. Gabriele Cosmo, 06/04/98.
# --------------------------------------------------------------
name := exampleB4a
G4TARGET := $(name)
G4EXLIB := true
ifndef G4INSTALL
G4INSTALL = ../../..
endif
.PHONY: all
all: lib bin
include $(G4INSTALL)/config/binmake.gmk
visclean:
rm -f g4*.prim g4*.eps g4*.wrl
rm -f .DAWN_*
+302 -19
View File
@@ -1,31 +1,314 @@
# 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.
* 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
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. A second executable, `run_sampling`, simulates the same kind of showers in a sampling calorimeter (alternating thin absorber and active layers); the layer configuration is selected by name at run time.
## 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.
* 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
## Repository layout
* 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
```
run_pbwo4.cc homogeneous PbWO4 crystal executable entry point
run_sampling.cc sampling calorimeter executable entry point (selectable layer configs)
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
```
---
## 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)
# executables: build/run_pbwo4, build/run_sampling
```
### Option B — build Geant4 from submodule
The superbuild compiles Geant4 from `lib/geant4` into `build/geant4-install`, then builds minicalosim against it. Four targets are available:
```bash
cmake -B build -S superbuild/ # configure once
cmake --build build --target geant4 # compile Geant4 (~3060 min)
cmake --build build --target run_pbwo4 # compile run_pbwo4
cmake --build build --target run_sampling # compile run_sampling
cmake --build build # all
```
After the build, the executables are at `build/run_pbwo4` and `build/run_sampling` (symlinks in the superbuild case).
**Options:**
| CMake option | Default | Meaning |
|---|---|---|
| `GEANT4_INSTALL_DATA` | `ON` | Download Geant4 physics data tables (~2 GB) |
| `GEANT4_USE_GDML` | `ON` | Build Geant4 with GDML support (requires Xerces-C); set `OFF` to build without Xerces-C |
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.
### Sampling calorimeter (`run_sampling`)
```bash
# default config (pb_scint), 10 events, writes sampling_pb_scint_10events_hits.root
build/run_sampling
# pick a config by name, default event count
build/run_sampling fe_scint
# pick a config by 1-based index, and a number of events
build/run_sampling 3 500
```
The config can be given either by name or by its 1-based index (`run_sampling --help`-style usage, including the index list, is printed on an invalid selection). Output file name: `sampling_<configName>_<nEvents>events_hits.root` (always uses the resolved name, even when selected by index).
All configs use a 10×10 sensor grid per active layer and are sized to reach ~2025 X₀ of absorber, comparable to `run_pbwo4`'s 20 cm PbWO₄ block. Because a sampling stack dilutes the absorber with inactive gaps, the lower the absorber's Z the physically deeper the stack has to be to reach the same number of radiation lengths — `fe_scint` is twice as deep as `pb_scint` for this reason. Configs, defined in `run_sampling.cc`:
| # | Config name | Layers | Total depth | ~X₀ | Notes |
|---|---|---|---|---|---|
| 1 | `pb_scint` | 60× (0.2 cm `G4_Pb` + 0.3 cm scint) | 30 cm | 21 | classic fine-sampling EM calo |
| 2 | `fe_scint` | 40× (1.0 cm `G4_Fe` + 0.5 cm scint) | 60 cm | 23 | coarse sampling, Tile-cal-like |
| 3 | `w_scint_ecal` | 1× 2.0 cm homogeneous scint, then 75× (0.1 cm `G4_W` + 0.2 cm scint) | 24.5 cm | 21 | thin homogeneous ECAL-like preshower (~0.05 X₀, sees MIPs/shower-start only) followed by a compact W/scint HCAL-like sampling section |
| 4 | `pb_lar` | 70× (0.2 cm `G4_Pb` + 0.4 cm `G4_lAr`) | 42 cm | 25 | same absorber as `pb_scint`, liquid argon active medium |
---
## 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 |
| `post_dx/dy/dz` | `double` | Post-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³ |
| `G4_lAr` | 1.40 g/cm³ | liquid argon, cryogenic (ATLAS EM calo-style) |
### 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.
+49 -9
View File
@@ -171,6 +171,27 @@ from IPython.display import Image, display
from minipandas import MiniFrame
def _get_commit():
try:
from minicalo_version import commit as _c
return _c
except ImportError:
pass
try:
r = subprocess.run(
['git', 'rev-parse', '--short', 'HEAD'],
capture_output=True, text=True,
cwd=os.path.dirname(os.path.abspath(__file__))
)
if r.returncode == 0:
return r.stdout.strip()
except Exception:
pass
return 'unknown'
print(f"miniCaloSim: commit {_get_commit()}")
#### helpers #####
class Stopwatch(object):
@@ -238,11 +259,14 @@ def _count_total_entries(root_paths, tree_name: str = "Hits") -> int:
total += int(f[tree_name].num_entries)
return total
def _assemble_results_to_mini_df(root_paths, tree_name: str = "Hits"):
def _assemble_results_to_mini_df(root_paths, tree_name: str = "Hits", event_id_offsets=None):
"""
Returns:
scalars: dict[col, np.ndarray] # shape (N,)
arrays: dict[col, np.ndarray] # shape (N, M[col])
event_id_offsets: optional list[int], one per file — added to the
'event_id' column so that IDs are globally unique across batches.
"""
# 1) learn schema & sizes from first file
scalar_cols, array_cols, array_sizes, dtypes_scalar, dtypes_array, _ = \
@@ -257,19 +281,22 @@ def _assemble_results_to_mini_df(root_paths, tree_name: str = "Hits"):
# 4) fill by slices
pos = 0
for p in root_paths:
for file_idx, p in enumerate(root_paths):
with uproot.open(p) as f:
arr = f[tree_name].arrays(library="ak")
n = len(arr)
sl = slice(pos, pos + n)
for c in scalar_cols:
scalars[c][sl] = np.asarray(arr[c])
data = np.asarray(arr[c])
if c == 'event_id' and event_id_offsets is not None:
data = data + event_id_offsets[file_idx]
scalars[c][sl] = data
for c in array_cols:
arrays[c][sl, :] = ak.to_numpy(arr[c]) # (n, fixed_size)
pos += n
#merge the dicts
merged = {**scalars, **arrays}
return MiniFrame(merged)
@@ -334,7 +361,8 @@ def run_batch(
maxEnergy_GeV: float = -1.0,
filename: str = "",
no_mp: bool = False,
manual_seed : int = -1):
manual_seed : int = -1,
return_steps: bool = False):
"""
Run a full Geant4 simulation batch with automatic parallelisation.
@@ -361,12 +389,17 @@ no_mp : bool, optional
Disable multiprocessing (run single-threaded) for debugging.
manual_seed : int, optional
Optional random seed. If < 0, seeds are generated automatically.
return_steps : bool, optional
If True, also return a MiniFrame of step-level data (one row per
Geant4 step across all events). Returns a tuple (hits, steps).
Returns
-------
MiniFrame
Table-like structure with one row per event, containing both scalar
values and fixed-size sensor arrays.
MiniFrame or tuple[MiniFrame, MiniFrame]
If return_steps is False (default): event-level MiniFrame with one
row per event. If return_steps is True: (hits, steps) where steps
has one row per Geant4 step with columns event_id, track_id,
step_no, pdg, pre_x/y/z, pre_E, post_x/y/z, post_E.
Notes
-----
@@ -406,6 +439,7 @@ Example
#print(f"Running {nEventsLastCore} events on last core")
nevents = [nEventsPerCore if i < nCores - 1 else nEventsLastCore for i in range(nCores)]
event_id_offsets = [sum(nevents[:i]) for i in range(nCores)]
if manual_seed >= 0:
seed = manual_seed
@@ -456,13 +490,19 @@ Example
sw.reset()
sw.start()
df = None
steps_df = None
try:
df = _assemble_results_to_mini_df(rp)
finally: #make sure to delete temp files
if return_steps:
steps_df = _assemble_results_to_mini_df(rp, tree_name="Steps",
event_id_offsets=event_id_offsets)
finally:
for f in tmpfile:
if os.path.exists(f):
os.remove(f)
print('G4Calo: concatenation finished after {:.2f} seconds'.format(sw.elapsed()))
if return_steps:
return df, steps_df
return df
def _fill_event(gd : GeometryDescriptor,
+17
View File
@@ -0,0 +1,17 @@
import os
from G4Calo import run_batch
from minicalo import GeometryDescriptor
output_dir = os.path.dirname(os.path.abspath(__file__))
gd = GeometryDescriptor()
gd.addLayer(20.0, "G4_PbWO4", True, 10, 10)
hits, steps = run_batch(gd, 10, "e-", 1.0, return_steps=True)
hits_out = os.path.join(output_dir, "pbwo4_10events_hits.pkl")
steps_out = os.path.join(output_dir, "pbwo4_10events_steps.pkl")
hits.to_pickle(hits_out)
steps.to_pickle(steps_out)
print(f"Saved {len(hits)} events to {hits_out}")
print(f"Saved {len(steps)} steps to {steps_out}")
+56 -118
View File
@@ -1,131 +1,69 @@
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
FROM ubuntu:24.04
SHELL ["/bin/sh", "-c"]
ARG DEBIAN_FRONTEND=noninteractive
USER root
# ---------------------------------------------------------------------------
# System dependencies (dnf instead of apt)
# ---------------------------------------------------------------------------
RUN apt-get update && apt-get install -y \
gcc \
g++ \
make \
cmake \
ninja-build \
libxerces-c-dev \
git \
curl \
ca-certificates \
python3 \
python3-dev \
python3-pip \
python3-venv \
&& rm -rf /var/lib/apt/lists/*
# ---------------------------------------------------------------------------
# uv
# ---------------------------------------------------------------------------
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"
ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /src
RUN /usr/bin/ls
RUN sed -i "s,# deb http://archive.canonical.com/ubuntu,deb http://archive.canonical.com/ubuntu,g" /etc/apt/sources.list
# ---------------------------------------------------------------------------
# Copy only files needed for the C++ build
# ---------------------------------------------------------------------------
COPY CMakeLists.txt .
COPY superbuild/ superbuild/
COPY src/ src/
COPY include/ include/
COPY bind/ bind/
COPY lib/ lib/
COPY run_pbwo4.cc .
COPY run_sampling.cc .
## Install some deps
#RUN ls /etc/apt/
RUN apt-get update -y
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install -y python3 python3-dev python3-venv python3-pip
#RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 100
# lib/geant4/.git is a submodule pointer to the host's .git/modules dir, which
# is not copied into the image. Replace it with a fresh repo so Geant4's cmake
# git status calls succeed.
RUN rm lib/geant4/.git && git init lib/geant4
RUN apt-get install -y dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev libxft-dev libxext-dev libssl-dev
# ---------------------------------------------------------------------------
# Build Geant4 + miniCaloSim
# ---------------------------------------------------------------------------
RUN cmake -S superbuild -B build -G Ninja \
-DGEANT4_INSTALL_DATA=ON
#RUN python3 --version && python3 -m ensurepip
RUN python3 -m pip install --upgrade pip
RUN cmake --build build --parallel
RUN python3 -m pip install pandas numpy matplotlib MarkupSafe wandb uproot setuptools awkward-pandas plotly
RUN python3 -m pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
RUN python3 -m pip install torch_geometric
RUN python3 -m pip install torch-cluster -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
# ---------------------------------------------------------------------------
# Python environment (uv)
# ---------------------------------------------------------------------------
RUN uv venv /opt/venv
# # # # GEANT
RUN apt-get -y install libxmu-dev \
libgl1-mesa-dev \
libxerces-c-dev \
libexpat1 \
libx11-dev \
libglu1-mesa-dev \
freeglut3-dev \
mesa-common-dev \
imagemagick \
gv wget autotools-dev libicu-dev libbz2-dev
ENV PATH="/opt/venv/bin:${PATH}"
ADD docker/pyproject.toml pyproject.toml
RUN uv sync
## DAWN FOR VIS
RUN mkdir dawn-source \
&& wget -q -O - http://geant4.kek.jp/~tanaka/src/dawn_3_91a.tgz | tar xzf - -C "dawn-source" \
&& export DAWN_PS_PREVIEWER=NONE && cd dawn-source/dawn_3_91a \
&& make clean && make guiclean && make && make install \
&& cd - && rm -rf "dawn-source"
## main package
RUN cd && wget -q https://gitlab.cern.ch/geant4/geant4/-/archive/v11.1.2/geant4-v11.1.2.tar.gz && \
tar -xzf geant4-v11.1.2.tar.gz -C /tmp && \
mkdir /tmp/geant4-v11.1.2-build && \
cd /tmp/geant4-v11.1.2-build && \
cmake -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 -DCMAKE_RULE_MESSAGES=OFF -DGEANT4_INSTALL_DATA=ON \
-DGEANT4_USE_GDML=ON -DGEANT4_USE_OPENGL_X11=ON \
-DGEANT4_BUILD_MULTITHREADED=OFF \
-DGEANT4_BUILD_TLS_MODEL=global-dynamic \
-DGEANT4_USE_RAYTRACER_X11=ON /tmp/geant4-v11.1.2 >/dev/null && \
make -j24 >/dev/null && \
make install >/dev/null
RUN rm -rf /tmp/geant4-v11.1.2 && \
rm -rf /tmp/geant4-v11.1.2-build
RUN python3 -m pip install geant4-pybind
ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}"
RUN apt-get install -y git
RUN python3 -m pip install ipython
RUN apt-get install -y tk # for dawn
# add jupyter packages for UC2 on top so they don't need to be installed every time
RUN python3 -m pip install jupyterlab jupyterhub==4.1.5 batchspawner
RUN ln -s /usr/bin/python3 /usr/bin/python
RUN python3 -m pip install torch-scatter -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
#finally the package; use the commit hash to check out the correct version and only get them here, such that the rest can be cached
ARG BUILD_DATE
LABEL org.label-schema.build-date=$BUILD_DATE
ARG COMMIT
ARG USER
# copy minicalosim and checkout the correct commit
ADD minicalosim /root/minicalosim
RUN cd /root/minicalosim && git checkout $COMMIT && \
mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4
RUN cp /root/minicalosim/build/minicalo* /root/minicalosim/bind/G4Calo.py /root/minicalosim/bind/minicalo_tools.py /root/minicalosim/bind/minipandas.py /usr/local/lib/python3.10/dist-packages/
RUN cp /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/
RUN cp /root/minicalosim/docker/start-notebook.py /usr/local/bin/
RUN cp /root/minicalosim/docker/start-notebook.sh /usr/local/bin/
RUN cp /root/minicalosim/docker/start-singleuser.py /usr/local/bin/
RUN cp /root/minicalosim/docker/start-singleuser.sh /usr/local/bin/
RUN cp /root/minicalosim/docker/fix-permissions /usr/local/bin/
RUN cp /root/minicalosim/docker/check_cuda_torch.py /usr/local/bin/
ARG NB_USER="jovyan"
ARG NB_UID="1000"
ARG NB_GID="100"
USER root
#RUN groupadd -g ${NB_GID} ${NB_USER} && \
RUN useradd -m -s /bin/bash -N -u ${NB_UID} -g ${NB_GID} ${NB_USER}
RUN mkdir /etc/jupyter
RUN fix-permissions /etc/jupyter/
# clean up
RUN rm -rf /root/minicalosim
RUN pip3 install pyarrow fastparquet
USER ${NB_UID}
CMD ["start-notebook.sh"]
WORKDIR /workspace
ENTRYPOINT ["/bin/bash", "-l"]
-127
View File
@@ -1,127 +0,0 @@
FROM ubuntu:20.04
SHELL ["/bin/sh", "-c"]
USER root
ENV DEBIAN_FRONTEND=noninteractive
RUN /usr/bin/ls
RUN sed -i "s,# deb http://archive.canonical.com/ubuntu,deb http://archive.canonical.com/ubuntu,g" /etc/apt/sources.list
## Install some deps
#RUN ls /etc/apt/
RUN apt-get update -y
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install -y python3 python3-dev python3-venv python3-pip
#RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 100
RUN apt-get install -y dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev libxft-dev libxext-dev libssl-dev
#RUN python3 --version && python3 -m ensurepip
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install pandas numpy matplotlib MarkupSafe wandb uproot setuptools awkward-pandas plotly
RUN python3 -m pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
RUN python3 -m pip install torch_geometric
RUN python3 -m pip install torch-cluster -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
# # # # GEANT
RUN apt-get -y install libxmu-dev \
libgl1-mesa-dev \
libxerces-c-dev \
libexpat1 \
libx11-dev \
libglu1-mesa-dev \
freeglut3-dev \
mesa-common-dev \
imagemagick \
gv wget autotools-dev libicu-dev libbz2-dev
## DAWN FOR VIS
RUN mkdir dawn-source \
&& wget -q -O - http://geant4.kek.jp/~tanaka/src/dawn_3_91a.tgz | tar xzf - -C "dawn-source" \
&& export DAWN_PS_PREVIEWER=NONE && cd dawn-source/dawn_3_91a \
&& make clean && make guiclean && make && make install \
&& cd - && rm -rf "dawn-source"
## main package
RUN cd && wget -q https://gitlab.cern.ch/geant4/geant4/-/archive/v11.1.2/geant4-v11.1.2.tar.gz && \
tar -xzf geant4-v11.1.2.tar.gz -C /tmp && \
mkdir /tmp/geant4-v11.1.2-build && \
cd /tmp/geant4-v11.1.2-build && \
cmake -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 -DCMAKE_RULE_MESSAGES=OFF -DGEANT4_INSTALL_DATA=ON \
-DGEANT4_USE_GDML=ON -DGEANT4_USE_OPENGL_X11=ON \
-DGEANT4_BUILD_MULTITHREADED=ON \
-DGEANT4_BUILD_TLS_MODEL=global-dynamic \
-DGEANT4_USE_RAYTRACER_X11=ON /tmp/geant4-v11.1.2 >/dev/null && \
make -j4 >/dev/null && \
make install >/dev/null
RUN rm -rf /tmp/geant4-v11.1.2 && \
rm -rf /tmp/geant4-v11.1.2-build
RUN python3 -m pip install geant4-pybind
ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}"
RUN apt-get install -y git
RUN python3 -m pip install ipython
RUN apt-get install -y tk # for dawn
# add jupyter packages for UC2 on top so they don't need to be installed every time
RUN python3 -m pip install jupyterlab jupyterhub==4.1.5 batchspawner
RUN ln -s /usr/bin/python3 /usr/bin/python
RUN python3 -m pip install torch-scatter -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
#finally the package; use the commit hash to check out the correct version and only get them here, such that the rest can be cached
ARG BUILD_DATE
LABEL org.label-schema.build-date=$BUILD_DATE
ARG COMMIT
ARG USER
# copy minicalosim and checkout the correct commit
ADD minicalosim /root/minicalosim
RUN cd /root/minicalosim && git checkout $COMMIT && \
mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 &&\
cp minicalo* ../bind/G4Calo.py /usr/local/lib/python3.8/dist-packages/
RUN cp /root/minicalosim/docker/start-notebook.py /usr/local/bin/
RUN cp /root/minicalosim/docker/start-notebook.sh /usr/local/bin/
RUN cp /root/minicalosim/docker/start-singleuser.py /usr/local/bin/
RUN cp /root/minicalosim/docker/start-singleuser.sh /usr/local/bin/
RUN cp /root/minicalosim/docker/fix-permissions /usr/local/bin/
RUN cp /root/minicalosim/docker/check_cuda_torch.py /usr/local/bin/
ARG NB_USER="jovyan"
ARG NB_UID="1000"
ARG NB_GID="100"
USER root
#RUN groupadd -g ${NB_GID} ${NB_USER} && \
RUN useradd -m -s /bin/bash -N -u ${NB_UID} -g ${NB_GID} ${NB_USER}
RUN mkdir /etc/jupyter
RUN fix-permissions /etc/jupyter/
# clean up
RUN rm -rf /root/minicalosim
USER ${NB_UID}
CMD ["start-notebook.sh"]
-95
View File
@@ -1,95 +0,0 @@
#!/usr/bin/bash
# a simple parser for the args <commit> and --no-cache
# if no commit is given, the latest commit is used
# if --no-cache is given, the --no-cache flag is passed to the docker build command
# if only --no-cache is given, the latest commit is used
# check if commit is given as argument, if not use the latest commit
# check if an argument is given at all
if [ -n "$1" ]; then
#check if the first arg is --no-cache
if [ "$1" == "--no-cache" ]; then
FORCE_NO_CACHE="--no-cache"
COMMIT=$(git rev-parse --short HEAD)
else
COMMIT=$1
#check if this is a valid commit
if ! git cat-file -e $COMMIT^{commit} 2>/dev/null; then
echo "ERROR: Commit $COMMIT not found in local repository."
exit 1
fi
fi
else #default to last commit
COMMIT=$(git rev-parse --short HEAD)
fi
# COMMIT is a valid commit at this point. If COMMIT is the last commit and changes are not commited, the user is warned.
#check if the commit is the last commit
if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then
# check if we have uncommitted changes and if so warn the user.
if [ -n "$(git status --porcelain)" ]; then
echo "WARNING: You are building the latest commit ${COMMIT}, but you have uncommitted changes."
echo " This means that the image will not be reproducible."
echo " If you want to build the latest commit, please commit your changes first."
read -p "Do you want to continue anyway? [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
fi
check_remote_commit_exists() {
# Usage: check_remote_commit_exists <repo-url> <commit-sha>
local url="$1"
local commit="$2"
# Create a temporary directory for a shallow fetch
local tmpdir
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' RETURN # auto-cleanup on function exit
git -C "$tmpdir" init -q
git -C "$tmpdir" remote add origin "$url"
if git -C "$tmpdir" fetch --quiet --depth=1 origin "$commit" 2>/dev/null; then
echo "✅ Commit $commit exists on remote."
return 0
else
echo "❌ ERROR: Commit $commit not found on remote."
echo "Refs currently visible on remote:"
git ls-remote --heads --tags "$url"
return 1
fi
}
# check if the commit has been pushed
if ! check_remote_commit_exists https://github.com/jkiesele/minicalosim $COMMIT; then
echo "ERROR: Commit $COMMIT not found in remote repository."
fi
# check if no-cache is given as second argument
if [ "$2" == "--no-cache" ]; then
FORCE_NO_CACHE="--no-cache"
fi
#switch to directory this script is located in
cd "$(dirname "$0")"
cd ../../ #switch to the root directory of the project
echo "Building minicalosim:$COMMIT"
#print the current working directory
echo "Current working directory: $(pwd)"
docker build $FORCE_NO_CACHE -t jkiesele/minicalosim:$COMMIT --build-arg USER=$USER --build-arg BUILD_DATE="$(date)" --build-arg COMMIT=$COMMIT -f minicalosim/docker/Dockerfile .
echo "sucessfully built container jkiesele/minicalosim:${COMMIT}"
docker tag jkiesele/minicalosim:$COMMIT jkiesele/minicalosim:latest
echo "also tagged as jkiesele/minicalosim:latest"
-73
View File
@@ -1,73 +0,0 @@
#!/usr/bin/bash
# a simple parser for the args <commit> and --no-cache
# if no commit is given, the latest commit is used
# if --no-cache is given, the --no-cache flag is passed to the docker build command
# if only --no-cache is given, the latest commit is used
# check if commit is given as argument, if not use the latest commit
# check if an argument is given at all
if [ -n "$1" ]; then
#check if the first arg is --no-cache
if [ "$1" == "--no-cache" ]; then
FORCE_NO_CACHE="--no-cache"
COMMIT=$(git rev-parse --short HEAD)
else
COMMIT=$1
#check if this is a valid commit
if ! git cat-file -e $COMMIT^{commit} 2>/dev/null; then
echo "ERROR: Commit $COMMIT not found in local repository."
exit 1
fi
fi
else #default to last commit
COMMIT=$(git rev-parse --short HEAD)
fi
# COMMIT is a valid commit at this point. If COMMIT is the last commit and changes are not commited, the user is warned.
#check if the commit is the last commit
if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then
# check if we have uncommitted changes and if so warn the user.
if [ -n "$(git status --porcelain)" ]; then
echo "WARNING: You are building the latest commit ${COMMIT}, but you have uncommitted changes."
echo " This means that the image will not be reproducible."
echo " If you want to build the latest commit, please commit your changes first."
read -p "Do you want to continue anyway? [y/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
fi
# check if the commit has been pushed
REMOTE_COMMIT=$(git ls-remote https://gitlab.etp.kit.edu/jkiesele/minicalosim.git | grep $COMMIT)
if [ -z "$REMOTE_COMMIT" ]; then
echo "ERROR: Commit $COMMIT not found in remote repository."
exit 1
fi
# check if no-cache is given as second argument
if [ "$2" == "--no-cache" ]; then
FORCE_NO_CACHE="--no-cache"
fi
#switch to directory this script is located in
cd "$(dirname "$0")"
cd ../../ #switch to the root directory of the project
echo "Building minicalosim_cpu:$COMMIT"
#print the current working directory
echo "Current working directory: $(pwd)"
docker build $FORCE_NO_CACHE -t jkiesele/minicalosim_cpu:$COMMIT --build-arg USER=$USER --build-arg BUILD_DATE="$(date)" --build-arg COMMIT=$COMMIT -f minicalosim/docker/Dockerfile-cpu .
echo "sucessfully built container jkiesele/minicalosim_cpu:${COMMIT}"
docker tag jkiesele/minicalosim_cpu:$COMMIT jkiesele/minicalosim_cpu:latest
echo "also tagged as jkiesele/minicalosim_cpu:latest"
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env python
'''
little script to check if cuda is available in pytorch and working
organised as a check that raises an exception if cuda is not available, and data cannot be moved to the gpu
'''
import torch
import numpy as np
import os
def check_cuda():
if not torch.cuda.is_available():
raise Exception("CUDA is not available. Please check if you have installed the correct version of CUDA and the correct version of the NVIDIA driver.")
try:
a = torch.tensor([1,2,3])
a = a.cuda()
a = a*a
except:
raise Exception("Cannot move data to the GPU. Please check if you have installed the correct version of CUDA and the correct version of the NVIDIA driver.")
print("CUDA is available and working correctly.")
if __name__ == '__main__':
check_cuda()
print("All checks passed.")
os._exit(0)
-33
View File
@@ -1,33 +0,0 @@
#!/bin/bash
# Set permissions on a directory
# After any installation, if a directory needs to be (human) user-writable, run this script on it.
# It will make everything in the directory owned by the group ${NB_GID} and writable by that group.
# Deployments that want to set a specific user id can preserve permissions
# by adding the `--group-add users` line to `docker run`.
# Uses find to avoid touching files that already have the right permissions,
# which would cause a massive image explosion
# Right permissions are:
# group=${NB_GID}
# AND permissions include group rwX (directory-execute)
# AND directories have setuid,setgid bits set
set -e
for d in "$@"; do
find "${d}" \
! \( \
-group "${NB_GID}" \
-a -perm -g+rwX \
\) \
-exec chgrp "${NB_GID}" -- {} \+ \
-exec chmod g+rwX -- {} \+
# setuid, setgid *on directories only*
find "${d}" \
\( \
-type d \
-a ! -perm -6000 \
\) \
-exec chmod +6000 -- {} \+
done
+21
View File
@@ -0,0 +1,21 @@
[project]
name = "minicalosim"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"awkward>=2.9.1",
"awkward-pandas>=2023.8.0",
"hist>=2.10.1",
"jupyter>=1.1.1",
"matplotlib>=3.10.9",
"mplhep>=1.2.0",
"networkx>=3.6.1",
"numpy>=2.4.6",
"pandas>=3.0.3",
"plotly>=6.8.0",
"polars>=1.41.2",
"seaborn>=0.13.2",
"uproot>=5.7.4",
]
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env python
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import shlex
import sys
# If we are in a JupyterHub, we pass on to `start-singleuser.py` instead so it does the right thing
if "JUPYTERHUB_API_TOKEN" in os.environ:
print(
"WARNING: using start-singleuser.py instead of start-notebook.py to start a server associated with JupyterHub."
)
command = ["/usr/local/bin/start-singleuser.py"] + sys.argv[1:]
os.execvp(command[0], command)
# Entrypoint is start.sh
command = []
# If we want to survive restarts, launch the command using `run-one-constantly`
if os.environ.get("RESTARTABLE") == "yes":
command.append("run-one-constantly")
# We always launch a jupyter subcommand from this script
command.append("jupyter")
# Launch the configured subcommand.
# Note that this should be a single string, so we don't split it.
# We default to `lab`.
jupyter_command = os.environ.get("DOCKER_STACKS_JUPYTER_CMD", "lab")
command.append(jupyter_command)
# Append any optional NOTEBOOK_ARGS we were passed in.
# This is supposed to be multiple args passed on to the notebook command,
# so we split it correctly with shlex
if "NOTEBOOK_ARGS" in os.environ:
command += shlex.split(os.environ["NOTEBOOK_ARGS"])
# Pass through any other args we were passed on the command line
command += sys.argv[1:]
# Execute the command!
print("Executing: " + " ".join(command))
os.execvp(command[0], command)
-35
View File
@@ -1,35 +0,0 @@
#!/bin/bash
# Shim to emit warning and call start-notebook.py
echo "WARNING: Use start-notebook.py instead"
if id jovyan &> /dev/null; then
if ! usermod --home "/home/${NB_USER}" --login "${NB_USER}" jovyan 2>&1 | grep "no changes" > /dev/null; then
echo "Updated the jovyan user:"
echo "- username: jovyan -> ${NB_USER}"
echo "- home dir: /home/jovyan -> /home/${NB_USER}"
fi
elif ! id -u "${NB_USER}" &> /dev/null; then
echo "ERROR: Neither the jovyan user nor '${NB_USER}' exists. This could be the result of stopping and starting, the container with a different NB_USER environment variable."
exit 1
fi
# Ensure the desired user (NB_USER) gets its desired user id (NB_UID) and is
# a member of the desired group (NB_GROUP, NB_GID)
if [ "${NB_UID}" != "$(id -u "${NB_USER}")" ] || [ "${NB_GID}" != "$(id -g "${NB_USER}")" ]; then
echo "Update ${NB_USER}'s UID:GID to ${NB_UID}:${NB_GID}"
# Ensure the desired group's existence
if [ "${NB_GID}" != "$(id -g "${NB_USER}")" ]; then
groupadd --force --gid "${NB_GID}" --non-unique "${NB_GROUP:-${NB_USER}}"
fi
# Recreate the desired user as we want it
userdel "${NB_USER}"
useradd --no-log-init --home "/home/${NB_USER}" --shell /bin/bash --uid "${NB_UID}" --gid "${NB_GID}" --groups 100 "${NB_USER}"
fi
# if root change user to jovyan
if [ "$EUID" -eq 0 ]; then
echo "WARNING: Running as root. Use start-notebook.py to run as ${NB_USER} user"
exec su ${NB_USER} -c "exec /usr/local/bin/start-notebook.py $@"
else
exec /usr/local/bin/start-notebook.py "$@"
fi
-26
View File
@@ -1,26 +0,0 @@
#!/usr/bin/env python
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import shlex
import sys
# Entrypoint is start.sh
command = ["jupyterhub-singleuser"]
# set default ip to 0.0.0.0
if "--ip=" not in os.environ.get("NOTEBOOK_ARGS", ""):
command.append("--ip=0.0.0.0")
# Append any optional NOTEBOOK_ARGS we were passed in.
# This is supposed to be multiple args passed on to the notebook command,
# so we split it correctly with shlex
if "NOTEBOOK_ARGS" in os.environ:
command += shlex.split(os.environ["NOTEBOOK_ARGS"])
# Pass any other args we have been passed through
command += sys.argv[1:]
# Execute the command!
print("Executing: " + " ".join(command))
os.execvp(command[0], command)
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# Shim to emit warning and call start-singleuser.py
echo "WARNING: Use start-singleuser.py instead"
exec /usr/local/bin/start-singleuser.py "$@"
+31
View File
@@ -0,0 +1,31 @@
# Design: `parent_step_no` Column on Steps Ntuple
## Problem
The Steps ntuple records `parent_id` (which track created a secondary) but not *at which step* of the parent the secondary was born. Finding this after the fact requires a position join: match the secondary's first pre-step point against the parent's post-step points across ntuple rows.
## Proposed solution
Add a `parent_step_no` integer column to the Steps ntuple. Value is `-1` for the primary particle and the parent's step number for every step of any secondary.
## Mechanism
Use `G4VUserTrackInformation` to carry the parent step number forward from creation time to the secondary's tracking phase:
1. **Tag at creation** — in `UserSteppingAction`, iterate `step->GetSecondaryInCurrentStep()` and attach a `TrackUserInfo(track->GetCurrentStepNumber())` object to each secondary via `const_cast<G4Track*>(sec)->SetUserInformation(...)`. Geant4 takes ownership and deletes it with the track.
2. **Read at tracking** — when filling a Steps ntuple row, retrieve `dynamic_cast<TrackUserInfo*>(track->GetUserInformation())` and write `info->parentStepNo`, or `-1` if null (primary).
## Files
- `include/TrackUserInfo.hh` — new: minimal `G4VUserTrackInformation` subclass storing `int parentStepNo`
- `src/RunAction.cc` — add column 27 `parent_step_no` (Integer) to ntuple 1 (`Steps`)
- `src/SteppingAction.cc` — tag new secondaries; fill column 27
## Note on `const_cast`
`GetSecondaryInCurrentStep()` returns `const G4Track*`. The `const` is a conservative API choice, not a physics invariant. `G4VUserTrackInformation` is an explicit user-side side-channel; using `const_cast` here is standard Geant4 practice.
## Superseded by
This approach was superseded by the `Spawning` ntuple (branch `spawning-ntuple`), which avoids the per-row overhead and `const_cast` by pre-assigning track IDs in `G4SteppingManager` and writing a dedicated secondary-birth table.
-23
View File
@@ -1,23 +0,0 @@
# Macro file for example B4 test
/run/initialize
# e+ 300MeV
/gun/particle e+
/gun/energy 300 MeV
/run/beamOn 1
#
# list the existing physics processes
/process/list
#
# switch off MultipleScattering
/process/inactivate msc
/run/beamOn 1
#
# switch on MultipleScattering
/process/activate msc
#
# change detector parameter
/gun/particle gamma
/gun/energy 500 MeV
/run/beamOn 1
-285
View File
@@ -1,285 +0,0 @@
//
// ********************************************************************
// * License and Disclaimer *
// * *
// * The Geant4 software is copyright of the Copyright Holders of *
// * the Geant4 Collaboration. It is provided under the terms and *
// * conditions of the Geant4 Software License, included in the file *
// * LICENSE and available at http://cern.ch/geant4/license . These *
// * include a list of copyright holders. *
// * *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work make any representation or warranty, express or implied, *
// * regarding this software system or assume any liability for its *
// * use. Please see the license in the file LICENSE and URL above *
// * for the full disclaimer and the limitation of liability. *
// * *
// * This code implementation is the result of the scientific and *
// * technical work of the GEANT4 collaboration. *
// * By using, copying, modifying or distributing the software (or *
// * any work based on the software) you agree to acknowledge its *
// * use in resulting scientific publications, and indicate your *
// * acceptance of all terms of the Geant4 Software license. *
// ********************************************************************
//
//
/// \file exampleB4a.cc
/// \brief Main program of the B4a example
#include "DetectorConstruction.hh"
#include "ActionInitialization.hh"
#include "G4RunManagerFactory.hh"
#include "G4SteppingVerbose.hh"
#include "G4UIcommand.hh"
#include "G4UImanager.hh"
#include "G4UIExecutive.hh"
#include "G4VisExecutive.hh"
#include "FTFP_BERT.hh"
#include "Randomize.hh"
#include "GeometryDescriptor.hh"
#include "G4System.hh"
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
namespace {
void PrintUsage() {
G4cerr << " Usage: " << G4endl;
G4cerr << " exampleB4a [-m macro ] [-u UIsession] [-t nThreads] [-vDefault]"
<< G4endl;
G4cerr << " note: -t option is available only for multi-threaded mode."
<< G4endl;
}
}
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
int main2(int argc,char** argv)
{
// Evaluate arguments
//
if ( argc > 7 ) {
PrintUsage();
return 1;
}
G4String macro;
G4String session;
G4bool verboseBestUnits = true;
#ifdef G4MULTITHREADED
G4int nThreads = 0;
#endif
for ( G4int i=1; i<argc; i=i+2 ) {
if ( G4String(argv[i]) == "-m" ) macro = argv[i+1];
else if ( G4String(argv[i]) == "-u" ) session = argv[i+1];
#ifdef G4MULTITHREADED
else if ( G4String(argv[i]) == "-t" ) {
nThreads = G4UIcommand::ConvertToInt(argv[i+1]);
}
#endif
else if ( G4String(argv[i]) == "-vDefault" ) {
verboseBestUnits = false;
--i; // this option is not followed with a parameter
}
else {
PrintUsage();
return 1;
}
}
// Detect interactive mode (if no macro provided) and define UI session
//
G4UIExecutive* ui = nullptr;
if ( ! macro.size() ) {
ui = new G4UIExecutive(argc, argv, session);
}
// Optionally: choose a different Random engine...
// G4Random::setTheEngine(new CLHEP::MTwistEngine);
// Use G4SteppingVerboseWithUnits
if ( verboseBestUnits ) {
G4int precision = 4;
G4SteppingVerbose::UseBestUnit(precision);
}
// Construct the default run manager
//
auto* runManager =
G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default);
#ifdef G4MULTITHREADED
if ( nThreads > 0 ) {
runManager->SetNumberOfThreads(nThreads);
}
#endif
GeometryDescriptor * cw = new GeometryDescriptor();
cw->addLayer(1, "G4_Pb", false);
cw->addLayer(10, "G4_Si", true, 9);
cw->addLayer(1, "G4_Pb", false);
cw->addLayer(20, "G4_Si", true, 15);
cw->addLayer(4, "G4_Pb", false);
cw->addLayer(3, "G4_Si", true, 23);
// Set mandatory initialization classes
//
auto detConstruction = new B4::DetectorConstruction(cw);
runManager->SetUserInitialization(detConstruction);
auto physicsList = new FTFP_BERT;
runManager->SetUserInitialization(physicsList);
auto actionInitialization = new B4a::ActionInitialization(detConstruction);
runManager->SetUserInitialization(actionInitialization);
actionInitialization->setGeneratorProperties(100, 1000, "e-");
// Initialize visualization
//
auto visManager = new G4VisExecutive;
// G4VisExecutive can take a verbosity argument - see /vis/verbose guidance.
// G4VisManager* visManager = new G4VisExecutive("Quiet");
visManager->Initialize();
// Get the pointer to the User Interface manager
auto UImanager = G4UImanager::GetUIpointer();
// Process macro or start UI session
//
if ( macro.size() ) {
// batch mode
G4String command = "/control/execute ";
UImanager->ApplyCommand(command+macro);
}
else {
// interactive mode : define UI session
UImanager->ApplyCommand("/control/execute init_vis.mac");
if (ui->IsGUI()) {
UImanager->ApplyCommand("/control/execute gui.mac");
}
ui->SessionStart();
delete ui;
}
// Job termination
// Free the store: user actions, physics_list and detector_description are
// owned and deleted by the run manager, so they should not be deleted
// in the main() program !
delete visManager;
delete runManager;
delete cw;
return 0;
}
//later
//class Runner {
//public:
// void initialize(GeometryDescriptor CW, bool gui=false);
// void run(int nEvents, std::string partSpecies, double minEnergy, double maxEnergy);
//}
void run(GeometryDescriptor CW, int nEvents, std::string partSpecies, double minEnergy, double maxEnergy, bool gui=false){
char* argv[]={(char*)"dummy"};
// Construct the default run manager
//
auto* runManager =
G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default);
runManager->SetNumberOfThreads(1);
GeometryDescriptor * cw = &CW;
G4String session;
G4UIExecutive* ui = nullptr;
if ( gui ) {
ui = new G4UIExecutive((int)1, argv, session);
}
// Set mandatory initialization classes
//
auto detConstruction = new B4::DetectorConstruction(cw);
runManager->SetUserInitialization(detConstruction);
auto physicsList = new FTFP_BERT;
runManager->SetUserInitialization(physicsList);
auto actionInitialization = new B4a::ActionInitialization(detConstruction);
runManager->SetUserInitialization(actionInitialization);
actionInitialization->setGeneratorProperties(minEnergy, maxEnergy, partSpecies);
// Initialize visualization
//
auto visManager = new G4VisExecutive;
// G4VisExecutive can take a verbosity argument - see /vis/verbose guidance.
// G4VisManager* visManager = new G4VisExecutive("Quiet");
visManager->Initialize();
// Get the pointer to the User Interface manager
auto UImanager = G4UImanager::GetUIpointer();
// Process macro or start UI session
//
if ( gui ) {
// interactive mode : define UI session
UImanager->ApplyCommand("/control/execute init_vis.mac");
if (ui->IsGUI()) {
UImanager->ApplyCommand("/control/execute gui.mac");
}
ui->SessionStart();
delete ui;
}
else {
G4String command = "/control/execute run1.mac";
UImanager->ApplyCommand("/run/initialize");
UImanager->ApplyCommand("/run/beamOn "+ std::to_string(nEvents));
}
// Job termination
// Free the store: user actions, physics_list and detector_description are
// owned and deleted by the run manager, so they should not be deleted
// in the main() program !
delete visManager;
delete runManager;
}
int main(){
GeometryDescriptor cw;
//to be moved
cw.addLayer(1, "G4_Pb", false);
cw.addLayer(10, "G4_Si", true, 9);
cw.addLayer(1, "G4_Pb", false);
cw.addLayer(20, "G4_Si", true, 15);
cw.addLayer(4, "G4_Pb", false);
cw.addLayer(3, "G4_Si", true, 23);
G4System builder(true);
builder.init(cw);
//builder.run_gui();
std::vector<std::string> parts = {"e-"};
builder.run_batch(10000,parts , 1, 100);
//run(cw, 1000, "e-", 1, 100, true);
return 0;
}
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo.....
-1111
View File
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
// Export material-specific gamma cross sections for PbWO4 using G4EmCalculator.
//
// Builds a minimal geometry, initialises FTFP_BERT, forces the EM physics
// tables to be built (via a fakeRun "/run/beamOn 0"), then sweeps photon
// energy from 1 eV to 1e5 eV and writes a CSV of the mass attenuation
// coefficients (cm^2/g) for each gamma process plus the total.
#include "GeometryDescriptor.hh"
#include "G4System.hh"
#include "G4EmCalculator.hh"
#include "G4Material.hh"
#include "G4NistManager.hh"
#include "G4Gamma.hh"
#include "G4SystemOfUnits.hh"
#include <cmath>
#include <fstream>
#include <iostream>
#include <vector>
int main(int argc, char** argv) {
const std::string materialName = "G4_PbWO4";
const std::string outfile = (argc > 1) ? argv[1] : "pbwo4_xsec.csv";
// Log-spaced energy grid: 1 eV -> 1e5 eV, points per decade (small steps).
const double eMin = 1.0 * eV;
const double eMax = 1.0e7 * eV; // 10 MeV
const int pointsPerDecade = 200;
const int nDecades = 7; // 1e0 .. 1e7 eV
const int nPoints = pointsPerDecade * nDecades + 1;
// Gamma processes registered by the EM standard physics in FTFP_BERT.
const std::vector<std::string> processes = {"phot", "compt", "conv", "Rayl"};
// Minimal valid geometry: one active PbWO4 layer so init() has something to build.
GeometryDescriptor gd;
gd.addLayer(2.0, materialName, true, 1, 1);
G4System g4;
g4.init(gd, -1);
// fakeRun: builds EM physics tables without invoking RunAction (no ROOT file).
g4.applyUICommand("/run/beamOn 0");
const G4Material* mat = G4NistManager::Instance()->FindOrBuildMaterial(materialName);
if (mat == nullptr) {
std::cerr << "Could not build material " << materialName << std::endl;
return 1;
}
const double density_g_cm3 = mat->GetDensity() / (g / cm3);
G4EmCalculator calc;
const G4ParticleDefinition* gamma = G4Gamma::Gamma();
std::ofstream out(outfile);
out << "# material=" << materialName
<< " density_g_per_cm3=" << density_g_cm3 << "\n";
out << "# mass attenuation coefficients mu/rho in cm^2/g\n";
out << "energy_eV,phot,compt,conv,rayl,total\n";
out.setf(std::ios::scientific);
out.precision(6);
const double logStep = (std::log10(eMax) - std::log10(eMin)) / (nPoints - 1);
for (int i = 0; i < nPoints; ++i) {
const double energy = std::pow(10.0, std::log10(eMin) + i * logStep);
double total_lin = 0.0; // linear attenuation, G4 units (1/mm)
std::vector<double> mu; // per-process mass attenuation (cm^2/g)
mu.reserve(processes.size());
for (const auto& proc : processes) {
// Macroscopic cross section Sigma [1/mm] = material-specific cross section.
const double sigmaVol = calc.ComputeCrossSectionPerVolume(energy, gamma, proc, mat);
total_lin += sigmaVol;
// Convert to mass attenuation coefficient: (Sigma in 1/cm) / density.
mu.push_back((sigmaVol * cm) / density_g_cm3);
}
const double total_mu = (total_lin * cm) / density_g_cm3;
out << energy / eV << "," << mu[0] << "," << mu[1] << ","
<< mu[2] << "," << mu[3] << "," << total_mu << "\n";
}
std::cout << "Wrote " << nPoints << " energy points for " << materialName
<< " to " << outfile << std::endl;
return 0;
}
-37
View File
@@ -1,37 +0,0 @@
#
# This file permits to customize, with commands,
# the menu bar of the G4UIXm, G4UIQt, G4UIWin32 sessions.
# It has no effect with G4UIterminal.
#
# File menu :
/gui/addMenu file File
/gui/addButton file Quit exit
#
# Run menu :
/gui/addMenu run Run
/gui/addButton run "beamOn 1" "/run/beamOn 1"
/gui/addButton run run1 "/control/execute run1.mac"
#
# Gun menu :
/gui/addMenu gun Gun
/gui/addButton gun "50 MeV" "/gun/energy 50 MeV"
/gui/addButton gun "1 GeV" "/gun/energy 1 GeV"
/gui/addButton gun "10 GeV" "/gun/energy 10 GeV"
/gui/addButton gun "e-" "/gun/particle e-"
/gui/addButton gun "pi0" "/gun/particle pi0"
/gui/addButton gun "pi+" "/gun/particle pi+"
/gui/addButton gun "neutron" "/gun/particle neutron"
/gui/addButton gun "proton" "/gun/particle proton"
#
# Viewer menu :
/gui/addMenu viewer Viewer
/gui/addButton viewer "Set style surface" "/vis/viewer/set/style surface"
/gui/addButton viewer "Set style wireframe" "/vis/viewer/set/style wireframe"
/gui/addButton viewer "Refresh viewer" "/vis/viewer/refresh"
/gui/addButton viewer "Update viewer (interaction or end-of-file)" "/vis/viewer/update"
/gui/addButton viewer "Flush viewer (= refresh + update)" "/vis/viewer/flush"
/gui/addButton viewer "Update scene" "/vis/scene/notifyHandlers"
#
# To limit the output flow in the "dump" widget :
/run/printProgress 100
#
+2
View File
@@ -32,6 +32,8 @@
#include "G4VUserPrimaryGeneratorAction.hh"
#include "globals.hh"
#include <vector>
#include <string>
class G4ParticleGun;
class G4Event;
+3
View File
@@ -32,6 +32,7 @@
#include "G4UserRunAction.hh"
#include "globals.hh"
#include <vector>
class G4Run;
@@ -82,6 +83,8 @@ class RunAction : public G4UserRunAction
mutable std::vector<int> hitLayer;
mutable std::vector<int> hitCopyNumber;
mutable std::vector<int> stepChildIds;
mutable G4String filename;
};
+4
View File
@@ -33,6 +33,7 @@
#include "G4UserSteppingAction.hh"
#include "GeometryDescriptor.hh"
#include "DetectorConstruction.hh"
#include "RunAction.hh"
namespace B4a
@@ -54,8 +55,11 @@ public:
void UserSteppingAction(const G4Step* step) override;
void setRunAction(const B4::RunAction* runAction) { fRunAction = runAction; }
private:
EventAction* fEventAction = nullptr;
const B4::RunAction* fRunAction = nullptr;
};
}
-17
View File
@@ -1,17 +0,0 @@
# Macro file for the initialization of example B4
# in interactive session
#
# Set some default verbose
#
/control/verbose 2
/control/saveHistory
/run/verbose 2
#
# Change the default number of threads (in multi-threaded mode)
#/run/numberOfThreads 4
#
# Initialize kernel
/run/initialize
#
# Visualization setting
/control/execute vis.mac
Submodule
+1
Submodule lib/geant4 added at 71c7582c45
-43
View File
@@ -1,43 +0,0 @@
// ROOT macro file for plotting example B4 histograms
//
// Can be run from ROOT session:
// root[0] .x plotHisto.C
{
gROOT->Reset();
gROOT->SetStyle("Plain");
// Draw histos filled by Geant4 simulation
//
// Open file filled by Geant4 simulation
TFile f("B4.root");
// Create a canvas and divide it into 2x2 pads
TCanvas* c1 = new TCanvas("c1", "", 20, 20, 1000, 1000);
c1->Divide(2,2);
// Draw Eabs histogram in the pad 1
c1->cd(1);
TH1D* hist1 = (TH1D*)f.Get("Eabs");
hist1->Draw("HIST");
// Draw Labs histogram in the pad 2
c1->cd(2);
TH1D* hist2 = (TH1D*)f.Get("Labs");
hist2->Draw("HIST");
// Draw Egap histogram in the pad 3
// with logaritmic scale for y
TH1D* hist3 = (TH1D*)f.Get("Egap");
c1->cd(3);
gPad->SetLogy(1);
hist3->Draw("HIST");
// Draw Lgap histogram in the pad 4
// with logaritmic scale for y
c1->cd(4);
gPad->SetLogy(1);
TH1D* hist4 = (TH1D*)f.Get("Lgap");
hist4->Draw("HIST");
}
-42
View File
@@ -1,42 +0,0 @@
// ROOT macro file for plotting example B4 ntuple
//
// Can be run from ROOT session:
// root[0] .x plotNtuple.C
{
gROOT->Reset();
gROOT->SetStyle("Plain");
// Draw histos filled by Geant4 simulation
//
// Open file filled by Geant4 simulation
TFile f("B4.root");
// Create a canvas and divide it into 2x2 pads
TCanvas* c1 = new TCanvas("c1", "", 20, 20, 1000, 1000);
c1->Divide(2,2);
// Get ntuple
TNtuple* ntuple = (TNtuple*)f.Get("B4");
// Draw Eabs histogram in the pad 1
c1->cd(1);
ntuple->Draw("Eabs");
// Draw Labs histogram in the pad 2
c1->cd(2);
ntuple->Draw("Labs");
// Draw Egap histogram in the pad 3
// with logaritmic scale for y ?? how to do this?
c1->cd(3);
gPad->SetLogy(1);
ntuple->Draw("Egap");
// Draw Lgap histogram in the pad 4
// with logaritmic scale for y ?? how to do this?
c1->cd(4);
gPad->SetLogy(1);
ntuple->Draw("Egap");
}
Executable
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
cmake -B build -S .
cmake --build build --parallel 8
for i in $(seq 0 9); do
build/run_pbwo4 10000
mv pbwo4_10000events_hits.root "pbwo4_10k_${i}.root"
done
mv pbwo4_10k_*.root /ceph/lbogner/geant_steps/
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
CONFIGS=(pb_scint fe_scint w_scint_ecal pb_lar)
RUNS_PER_CONFIG=4
NEVENTS=10000
MAX_PARALLEL=16
EXE="$(pwd)/build/run_sampling"
OUTDIR="$(pwd)/sampling_batch_output"
WORKROOT="$OUTDIR/.work"
mkdir -p "$WORKROOT"
run_one() {
local config="$1" idx="$2"
local workdir="$WORKROOT/${config}_${idx}"
mkdir -p "$workdir"
(
cd "$workdir"
"$EXE" "$config" "$NEVENTS"
mv "sampling_${config}_${NEVENTS}events_hits.root" "$OUTDIR/sampling_${config}_10k_${idx}.root"
)
rmdir "$workdir"
}
export -f run_one
export EXE NEVENTS OUTDIR WORKROOT
jobs=()
for config in "${CONFIGS[@]}"; do
for idx in $(seq 0 $((RUNS_PER_CONFIG - 1))); do
jobs+=("$config $idx")
done
done
printf '%s\n' "${jobs[@]}" | xargs -P "$MAX_PARALLEL" -L 1 bash -c 'run_one "$1" "$2"' _
rmdir "$WORKROOT"
-44
View File
@@ -1,44 +0,0 @@
# Macro file for example B4
#
# Can be run in batch, without graphic
# or interactively: Idle> /control/execute run1.mac
#
# Change the default number of workers (in multi-threading mode)
#/run/numberOfThreads 4
#
# Initialize kernel
/run/initialize
#
# Default kinematics:
# electron 50 MeV in direction (0.,0.,1.)
# 1 event with tracking/verbose
#
/tracking/verbose 1
/run/beamOn 1
#
#
# muon 300 MeV in direction (0.,0.,1.)
# 3 events
#
/gun/particle mu+
/gun/energy 3 MeV
/run/beamOn 3
#
# 20 events
#
/tracking/verbose 0
/run/printProgress 5
/run/beamOn 20
#
# Magnetic field
#
/globalField/setValue 0.2 0 0 tesla
/run/beamOn 3
#
# Activate/inactivate physics processes
#
/process/list
/process/inactivate eBrem
#
/run/beamOn 20
#
-17
View File
@@ -1,17 +0,0 @@
# Macro file for example B4
#
# To be run preferably in batch, without graphics:
# % exampleB4[a,b,c,d] run2.mac
#
#/run/numberOfThreads 4
#/control/cout/ignoreThreadsExcept 0
#
/run/initialize
#
# Default kinemtics:
# electron 50 MeV in direction (0.,0.,1.)
# 1000 events
#
/run/printProgress 100
/run/beamOn 1000
+59
View File
@@ -0,0 +1,59 @@
#include "GeometryDescriptor.hh"
#include "G4System.hh"
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
int main(int argc, char** argv) {
int nEvents = 10;
double energy_GeV = 1.0;
int seed = -1;
if (const char* seedEnv = std::getenv("MINICALOSIM_SEED")) {
try {
std::size_t pos = 0;
std::string seedStr(seedEnv);
int parsed = std::stoi(seedStr, &pos);
if (pos == seedStr.size()) seed = parsed;
} catch (const std::exception&) {
// Not a valid integer; fall back to time-based seed.
}
}
if (argc > 3) {
std::cerr << "Usage: run_pbwo4 [nEvents] [energy_GeV]" << std::endl;
return 1;
}
if (argc >= 2) {
try {
nEvents = std::stoi(argv[1]);
if (nEvents <= 0) throw std::invalid_argument("must be positive");
} catch (const std::exception& e) {
std::cerr << "Invalid nEvents '" << argv[1] << "': " << e.what() << std::endl;
return 1;
}
}
if (argc == 3) {
try {
energy_GeV = std::stod(argv[2]);
if (energy_GeV <= 0) throw std::invalid_argument("must be positive");
} catch (const std::exception& e) {
std::cerr << "Invalid energy_GeV '" << argv[2] << "': " << e.what() << std::endl;
return 1;
}
}
std::string outfile = "pbwo4_" + std::to_string(nEvents) + "events_hits.root";
GeometryDescriptor gd;
gd.addLayer(20.0, "G4_PbWO4", true, 10, 10);
G4System g4;
g4.init(gd, seed);
g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile);
std::cout << "Saved " << nEvents << " events to " << outfile << std::endl;
return 0;
}
+147
View File
@@ -0,0 +1,147 @@
#include "GeometryDescriptor.hh"
#include "G4System.hh"
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace {
GeometryDescriptor buildPbScint() {
GeometryDescriptor gd;
for (int i = 0; i < 60; ++i) {
gd.addLayer(0.2, "G4_Pb", false);
gd.addLayer(0.3, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
}
return gd;
}
GeometryDescriptor buildFeScint() {
GeometryDescriptor gd;
for (int i = 0; i < 40; ++i) {
gd.addLayer(1.0, "G4_Fe", false);
gd.addLayer(0.5, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
}
return gd;
}
GeometryDescriptor buildWScintEcal() {
GeometryDescriptor gd;
// Thin homogeneous preshower: ~0.05 X0, sees MIPs/shower-start, not containment.
gd.addLayer(2.0, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
for (int i = 0; i < 75; ++i) {
gd.addLayer(0.1, "G4_W", false);
gd.addLayer(0.2, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
}
return gd;
}
GeometryDescriptor buildPbLAr() {
GeometryDescriptor gd;
for (int i = 0; i < 70; ++i) {
gd.addLayer(0.2, "G4_Pb", false);
gd.addLayer(0.4, "G4_lAr", true, 10, 10);
}
return gd;
}
// Ordered (not alphabetical) so configs can also be selected by 1-based index.
const std::vector<std::pair<std::string, GeometryDescriptor (*)()>> kConfigs = {
{"pb_scint", buildPbScint},
{"fe_scint", buildFeScint},
{"w_scint_ecal", buildWScintEcal},
{"pb_lar", buildPbLAr},
};
void printUsage() {
std::cerr << "Usage: run_sampling [configName|configIndex] [nEvents] [energy_GeV]" << std::endl;
std::cerr << "Available configs:" << std::endl;
for (std::size_t i = 0; i < kConfigs.size(); ++i) {
std::cerr << " " << (i + 1) << ": " << kConfigs[i].first << std::endl;
}
}
} // namespace
int main(int argc, char** argv) {
std::string configName = kConfigs.front().first;
int nEvents = 10;
double energy_GeV = 1.0;
if (argc > 4) {
printUsage();
return 1;
}
if (argc >= 2) {
configName = argv[1];
}
if (argc >= 3) {
try {
nEvents = std::stoi(argv[2]);
if (nEvents <= 0) throw std::invalid_argument("must be positive");
} catch (const std::exception& e) {
std::cerr << "Invalid nEvents '" << argv[2] << "': " << e.what() << std::endl;
return 1;
}
}
if (argc == 4) {
try {
energy_GeV = std::stod(argv[3]);
if (energy_GeV <= 0) throw std::invalid_argument("must be positive");
} catch (const std::exception& e) {
std::cerr << "Invalid energy_GeV '" << argv[3] << "': " << e.what() << std::endl;
return 1;
}
}
GeometryDescriptor (*builder)() = nullptr;
try {
std::size_t pos = 0;
int index = std::stoi(configName, &pos);
if (pos == configName.size() && index >= 1 && index <= static_cast<int>(kConfigs.size())) {
configName = kConfigs[index - 1].first;
builder = kConfigs[index - 1].second;
}
} catch (const std::exception&) {
// Not a valid index; fall through to name lookup below.
}
if (builder == nullptr) {
for (const auto& kv : kConfigs) {
if (kv.first == configName) {
builder = kv.second;
break;
}
}
}
if (builder == nullptr) {
std::cerr << "Unknown config '" << configName << "'." << std::endl;
printUsage();
return 1;
}
int seed = -1;
if (const char* seedEnv = std::getenv("MINICALOSIM_SEED")) {
try {
std::size_t pos = 0;
std::string seedStr(seedEnv);
int parsed = std::stoi(seedStr, &pos);
if (pos == seedStr.size()) seed = parsed;
} catch (const std::exception&) {
// Not a valid integer; fall back to time-based seed.
}
}
GeometryDescriptor gd = builder();
std::string outfile = "sampling_" + configName + "_" + std::to_string(nEvents) + "events_hits.root";
G4System g4;
g4.init(gd, seed);
g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile);
std::cout << "Saved " << nEvents << " events to " << outfile << std::endl;
return 0;
}
+1
View File
@@ -70,6 +70,7 @@ void ActionInitialization::Build() const
eventAction->setPrimaryGeneratorAction(gen);
SetUserAction(eventAction);
auto steppingAction = new SteppingAction(eventAction);
steppingAction->setRunAction(runact);
SetUserAction(steppingAction);
}
+43
View File
@@ -80,7 +80,50 @@ RunAction::RunAction()
analysisManager->CreateNtupleIColumn("sensor_layer",hitLayer);
analysisManager->CreateNtupleIColumn("sensor_copy_number",hitCopyNumber);
analysisManager->FinishNtuple();
// Steps ntuple (one row per step, ntuple id=1)
analysisManager->CreateNtuple("Steps", "Steps");
analysisManager->CreateNtupleIColumn("event_id"); // col 0
analysisManager->CreateNtupleIColumn("track_id"); // col 1
analysisManager->CreateNtupleIColumn("step_no"); // col 2
analysisManager->CreateNtupleIColumn("pdg"); // col 3
analysisManager->CreateNtupleDColumn("pre_x"); // col 4
analysisManager->CreateNtupleDColumn("pre_y"); // col 5
analysisManager->CreateNtupleDColumn("pre_z"); // col 6
analysisManager->CreateNtupleDColumn("pre_E"); // col 7
analysisManager->CreateNtupleDColumn("post_x"); // col 8
analysisManager->CreateNtupleDColumn("post_y"); // col 9
analysisManager->CreateNtupleDColumn("post_z"); // col 10
analysisManager->CreateNtupleDColumn("post_E"); // col 11
analysisManager->CreateNtupleDColumn("edep"); // col 12 [MeV]
analysisManager->CreateNtupleDColumn("step_length"); // col 13 [mm]
analysisManager->CreateNtupleSColumn("process"); // col 14
analysisManager->CreateNtupleIColumn("layer_id"); // col 15 (-1 = outside all layers)
analysisManager->CreateNtupleSColumn("material"); // col 16
analysisManager->CreateNtupleDColumn("pre_dx"); // col 17
analysisManager->CreateNtupleDColumn("pre_dy"); // col 18
analysisManager->CreateNtupleDColumn("pre_dz"); // col 19
analysisManager->CreateNtupleDColumn("Bx"); // col 20 [T]
analysisManager->CreateNtupleDColumn("By"); // col 21 [T]
analysisManager->CreateNtupleDColumn("Bz"); // col 22 [T]
analysisManager->CreateNtupleDColumn("Ex"); // col 23 [V/m]
analysisManager->CreateNtupleDColumn("Ey"); // col 24 [V/m]
analysisManager->CreateNtupleDColumn("Ez"); // col 25 [V/m]
analysisManager->CreateNtupleIColumn("child_track_ids", stepChildIds); // col 26 (variable-length array)
analysisManager->CreateNtupleDColumn("post_dx"); // col 27
analysisManager->CreateNtupleDColumn("post_dy"); // col 28
analysisManager->CreateNtupleDColumn("post_dz"); // col 29
analysisManager->CreateNtupleSColumn("next_volume"); // col 30 (volume entered if step ended at a boundary, else "")
analysisManager->CreateNtupleSColumn("next_material"); // col 31 (material entered if step ended at a boundary, else "")
analysisManager->FinishNtuple();
// Spawning ntuple (one row per secondary born, ntuple id=2)
analysisManager->CreateNtuple("Spawning", "Spawning");
analysisManager->CreateNtupleIColumn("event_id"); // col 0
analysisManager->CreateNtupleIColumn("parent_track_id"); // col 1
analysisManager->CreateNtupleIColumn("parent_step_no"); // col 2
analysisManager->CreateNtupleIColumn("child_track_id"); // col 3
analysisManager->FinishNtuple();
}
+121 -7
View File
@@ -33,6 +33,13 @@
#include "G4Step.hh"
#include "G4RunManager.hh"
#include "G4AnalysisManager.hh"
#include "G4Track.hh"
#include "G4ParticleDefinition.hh"
#include "G4TransportationManager.hh"
#include "G4FieldManager.hh"
#include "G4Field.hh"
#include "G4SystemOfUnits.hh"
using namespace B4;
@@ -68,16 +75,123 @@ void SteppingAction::UserSteppingAction(const G4Step* step)
auto sensor = DetectorConstruction::getDetectorConstruction()->getGeometryDescriptor()->getSensorByVolume(volume);
if(sensor == nullptr){
//can simply be not an active volume, so no need to throw an error
//G4cout << "Sensor not found in" << volume->GetName() << G4endl; //DEBUG
return;
if(sensor != nullptr){
//G4cout << "Sensor found in " << volume->GetName() << " with copy number " << volume->GetCopyNo()<< G4endl; //DEBUG
sensor->energy += edep;
}
//G4cout << "Sensor found in " << volume->GetName() << " with copy number " << volume->GetCopyNo()<< G4endl; //DEBUG
sensor->energy += edep;
//find sensor by volume copy number
// Record step kinematics for every step
auto analysisManager = G4AnalysisManager::Instance();
auto pre = step->GetPreStepPoint();
auto post = step->GetPostStepPoint();
auto track = step->GetTrack();
int evtId = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID();
// Write one Spawning row per secondary born in this step (track IDs pre-assigned);
// also populate stepChildIds for the child_track_ids vector column in Steps.
fRunAction->stepChildIds.clear();
const auto* secondaries = step->GetSecondaryInCurrentStep();
if (secondaries) {
for (const G4Track* sec : *secondaries) {
fRunAction->stepChildIds.push_back(sec->GetTrackID());
analysisManager->FillNtupleIColumn(2, 0, evtId);
analysisManager->FillNtupleIColumn(2, 1, track->GetTrackID());
analysisManager->FillNtupleIColumn(2, 2, track->GetCurrentStepNumber());
analysisManager->FillNtupleIColumn(2, 3, sec->GetTrackID());
analysisManager->AddNtupleRow(2);
}
}
analysisManager->FillNtupleIColumn(1, 0, evtId);
analysisManager->FillNtupleIColumn(1, 1, track->GetTrackID());
analysisManager->FillNtupleIColumn(1, 2, track->GetCurrentStepNumber());
analysisManager->FillNtupleIColumn(1, 3, track->GetDefinition()->GetPDGEncoding());
analysisManager->FillNtupleDColumn(1, 4, pre->GetPosition().x());
analysisManager->FillNtupleDColumn(1, 5, pre->GetPosition().y());
analysisManager->FillNtupleDColumn(1, 6, pre->GetPosition().z());
analysisManager->FillNtupleDColumn(1, 7, pre->GetKineticEnergy());
analysisManager->FillNtupleDColumn(1, 8, post->GetPosition().x());
analysisManager->FillNtupleDColumn(1, 9, post->GetPosition().y());
analysisManager->FillNtupleDColumn(1, 10, post->GetPosition().z());
analysisManager->FillNtupleDColumn(1, 11, post->GetKineticEnergy());
// A: energy deposited in medium at this step (≠ pre_E - post_E when secondaries are created)
analysisManager->FillNtupleDColumn(1, 12, edep);
// B: geometric step length
analysisManager->FillNtupleDColumn(1, 13, step->GetStepLength());
// D: process that ended this step
G4String processName = "";
const auto* postProc = post->GetProcessDefinedStep();
if (postProc) processName = postProc->GetProcessName();
analysisManager->FillNtupleSColumn(1, 14, processName);
// E: layer index (-1 if outside all layers) and material name at pre-step point
int layerId = -1;
const auto& layers = cw->getLayers();
for (int i = 0; i < (int)layers.size(); i++) {
if (layers[i].physicalVolume == volume) { layerId = i; break; }
}
G4String materialName = pre->GetMaterial() ? pre->GetMaterial()->GetName() : "";
analysisManager->FillNtupleIColumn(1, 15, layerId);
analysisManager->FillNtupleSColumn(1, 16, materialName);
// F: pre-step momentum direction (unit vector)
const auto dir = pre->GetMomentumDirection();
analysisManager->FillNtupleDColumn(1, 17, dir.x());
analysisManager->FillNtupleDColumn(1, 18, dir.y());
analysisManager->FillNtupleDColumn(1, 19, dir.z());
// G: post-step momentum direction (unit vector)
const auto postDir = post->GetMomentumDirection();
analysisManager->FillNtupleDColumn(1, 27, postDir.x());
analysisManager->FillNtupleDColumn(1, 28, postDir.y());
analysisManager->FillNtupleDColumn(1, 29, postDir.z());
// H: volume/material the track is about to enter, if this step ended at a
// geometric boundary. post->GetPhysicalVolume() still refers to the volume
// the step occurred in, not the one being entered, so use track->GetNextVolume().
G4String nextVolumeName = "";
G4String nextMaterialName = "";
if (post->GetStepStatus() == fGeomBoundary) {
auto nextVolume = track->GetNextVolume();
if (nextVolume) {
nextVolumeName = nextVolume->GetName();
auto nextMaterial = nextVolume->GetLogicalVolume()->GetMaterial();
if (nextMaterial) nextMaterialName = nextMaterial->GetName();
}
}
analysisManager->FillNtupleSColumn(1, 30, nextVolumeName);
analysisManager->FillNtupleSColumn(1, 31, nextMaterialName);
// Field: B [T] and E [V/m] at pre-step position; zero if no field is registered
G4double Bx=0, By=0, Bz=0, Ex=0, Ey=0, Ez=0;
const auto* fm = G4TransportationManager::GetTransportationManager()->GetFieldManager();
if (fm && fm->DoesFieldExist()) {
const G4Field* field = fm->GetDetectorField();
if (field) {
const G4double point[4] = {pre->GetPosition().x(), pre->GetPosition().y(),
pre->GetPosition().z(), pre->GetGlobalTime()};
G4double fieldVal[6] = {0,0,0,0,0,0};
field->GetFieldValue(point, fieldVal);
Bx = fieldVal[0] / tesla;
By = fieldVal[1] / tesla;
Bz = fieldVal[2] / tesla;
Ex = fieldVal[3] / (volt/m);
Ey = fieldVal[4] / (volt/m);
Ez = fieldVal[5] / (volt/m);
}
}
analysisManager->FillNtupleDColumn(1, 20, Bx);
analysisManager->FillNtupleDColumn(1, 21, By);
analysisManager->FillNtupleDColumn(1, 22, Bz);
analysisManager->FillNtupleDColumn(1, 23, Ex);
analysisManager->FillNtupleDColumn(1, 24, Ey);
analysisManager->FillNtupleDColumn(1, 25, Ez);
// col 26 child_track_ids: vector column, auto-read from fRunAction->stepChildIds at AddNtupleRow
analysisManager->AddNtupleRow(1);
}
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
+67
View File
@@ -0,0 +1,67 @@
cmake_minimum_required(VERSION 3.16)
project(minicalosim-superbuild NONE)
include(ExternalProject)
include(ProcessorCount)
ProcessorCount(NPROC)
if(NOT NPROC OR NPROC EQUAL 0)
set(NPROC 4)
endif()
# Cap at 16 to avoid memory exhaustion on large machines (mirrors Dockerfile)
if(NPROC GREATER 16)
set(NPROC 16)
endif()
set(G4_INSTALL ${CMAKE_BINARY_DIR}/geant4-install)
set(G4_SRC ${CMAKE_SOURCE_DIR}/../lib/geant4)
set(MINI_SRC ${CMAKE_SOURCE_DIR}/..)
option(GEANT4_INSTALL_DATA "Download Geant4 physics data files (~2 GB)" ON)
option(GEANT4_USE_GDML "Build Geant4 with GDML support (requires Xerces-C)" ON)
# Geant4
ExternalProject_Add(geant4
SOURCE_DIR ${G4_SRC}
BINARY_DIR ${CMAKE_BINARY_DIR}/geant4-build
INSTALL_DIR ${G4_INSTALL}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DCMAKE_BUILD_TYPE=Release
-DGEANT4_INSTALL_DATA=${GEANT4_INSTALL_DATA}
-DGEANT4_USE_GDML=${GEANT4_USE_GDML}
-DGEANT4_BUILD_MULTITHREADED=OFF
-DGEANT4_BUILD_TLS_MODEL=global-dynamic
BUILD_COMMAND cmake --build <BINARY_DIR> --parallel ${NPROC}
INSTALL_COMMAND cmake --install <BINARY_DIR>
)
# minicalosim (run_pbwo4)
ExternalProject_Add(run_pbwo4
SOURCE_DIR ${MINI_SRC}
BINARY_DIR ${CMAKE_BINARY_DIR}/minicalosim-build
CMAKE_ARGS
-DCMAKE_PREFIX_PATH=${G4_INSTALL}
-DWITH_GEANT4_UIVIS=OFF
BUILD_COMMAND cmake --build <BINARY_DIR> --target run_pbwo4 --parallel ${NPROC}
INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_BINARY_DIR}/minicalosim-build/run_pbwo4
${CMAKE_BINARY_DIR}/run_pbwo4
DEPENDS geant4
)
# minicalosim (run_sampling)
# Shares run_pbwo4's BINARY_DIR (already configured against G4_INSTALL) and
# depends on it so the two ExternalProjects don't reconfigure that dir concurrently.
ExternalProject_Add(run_sampling
SOURCE_DIR ${MINI_SRC}
BINARY_DIR ${CMAKE_BINARY_DIR}/minicalosim-build
CMAKE_ARGS
-DCMAKE_PREFIX_PATH=${G4_INSTALL}
-DWITH_GEANT4_UIVIS=OFF
BUILD_COMMAND cmake --build <BINARY_DIR> --target run_sampling --parallel ${NPROC}
INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink
${CMAKE_BINARY_DIR}/minicalosim-build/run_sampling
${CMAKE_BINARY_DIR}/run_sampling
DEPENDS run_pbwo4
)
-82
View File
@@ -1,82 +0,0 @@
# Macro file for the visualization setting for the initialization phase
# of the B4 example when running in interactive mode
#
# Use these open statements to open selected visualization
#
# Use this open statement to create an OpenGL view:
/vis/open OGL 600x600-0+0
#
# Use this open statement to create an OpenInventor view:
#/vis/open OIX
#
# Use this open statement to create a .prim file suitable for
# viewing in DAWN:
#/vis/open DAWNFILE
#
# Use this open statement to create a .heprep file suitable for
# viewing in HepRApp:
#/vis/open HepRepFile
#
# Use this open statement to create a .wrl file suitable for
# viewing in a VRML viewer:
#/vis/open VRML2FILE
#
# Disable auto refresh and quieten vis messages whilst scene and
# trajectories are established:
/vis/viewer/set/autoRefresh false
/vis/verbose errors
#
# Draw geometry:
/vis/drawVolume
#
# Specify view angle:
/vis/viewer/set/viewpointThetaPhi 90. 180.
#
# Specify zoom value:
#/vis/viewer/zoom 2.
#
# Specify style (surface, wireframe, auxiliary edges,...)
#/vis/viewer/set/style wireframe
#/vis/viewer/set/auxiliaryEdge true
#/vis/viewer/set/lineSegmentsPerCircle 100
#
# Draw coordinate axes:
#/vis/scene/add/axes 0 0 0 1 m
#
# Draw smooth trajectories at end of event, showing trajectory points
# as markers 2 pixels wide:
/vis/scene/add/trajectories smooth
/vis/modeling/trajectories/create/drawByCharge
/vis/modeling/trajectories/drawByCharge-0/default/setDrawStepPts true
/vis/modeling/trajectories/drawByCharge-0/default/setStepPtsSize 1
# (if too many tracks cause core dump => /tracking/storeTrajectory 0)
#
# Draw hits at end of event:
#/vis/scene/add/hits
#
# To draw only gammas:
#/vis/filtering/trajectories/create/particleFilter
#/vis/filtering/trajectories/particleFilter-0/add gamma
#
# To invert the above, drawing all particles except gammas,
# keep the above two lines but also add:
#/vis/filtering/trajectories/particleFilter-0/invert true
#
# Many other options are available with /vis/modeling and /vis/filtering.
# For example, to select colour by particle ID:
#/vis/modeling/trajectories/create/drawByParticleID
#/vis/modeling/trajectories/drawByParticleID-0/default/setDrawStepPts true
# To select or override default colours (note: e+ is blue by default):
#/vis/modeling/trajectories/list
#/vis/modeling/trajectories/drawByParticleID-0/set e+ yellow
#
# To superimpose all of the events from a given run:
/vis/scene/endOfEventAction accumulate
#
# Re-establish auto refreshing and verbosity:
/vis/viewer/set/autoRefresh true
/vis/verbose warnings
#
# For file-based drivers, use this to create an empty detector view:
#/vis/viewer/flush