From 91020714e7bdb99be242298787f45aa7f7720225 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 11:20:31 +0200 Subject: [PATCH 01/33] add CLAUDE.md with build, test, and architecture documentation Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e266a11 --- /dev/null +++ b/CLAUDE.md @@ -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. From 19b1ef125bbb0c97ea19f542cb45e3e95faa1335 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 11:37:39 +0200 Subject: [PATCH 02/33] add per-step kinematics ntuple ("Steps") to ROOT output Adds a second ntuple alongside the existing "Hits" ntuple with one row per Geant4 step. Records event_id, track_id, step_no, PDG code, and pre/post position + kinetic energy for every step in the simulation. Also extends run_batch() with return_steps=False; when True, returns (hits_mf, steps_mf) instead of just hits_mf. Co-Authored-By: Claude Sonnet 4.6 --- bind/G4Calo.py | 17 +++++++++++++---- src/RunAction.cc | 15 +++++++++++++++ src/SteppingAction.cc | 32 +++++++++++++++++++++++++------- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/bind/G4Calo.py b/bind/G4Calo.py index 09a8951..eb93c4d 100644 --- a/bind/G4Calo.py +++ b/bind/G4Calo.py @@ -334,7 +334,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 +362,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 ----- @@ -463,6 +469,9 @@ Example if os.path.exists(f): os.remove(f) print('G4Calo: concatenation finished after {:.2f} seconds'.format(sw.elapsed())) + if return_steps: + steps_df = _assemble_results_to_mini_df(rp, tree_name="Steps") + return df, steps_df return df def _fill_event(gd : GeometryDescriptor, diff --git a/src/RunAction.cc b/src/RunAction.cc index 160f566..b19eed9 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -80,7 +80,22 @@ 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->FinishNtuple(); } diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index ec80e5d..1f84d52 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -33,6 +33,9 @@ #include "G4Step.hh" #include "G4RunManager.hh" +#include "G4AnalysisManager.hh" +#include "G4Track.hh" +#include "G4ParticleDefinition.hh" using namespace B4; @@ -68,16 +71,31 @@ 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(); + 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()); + analysisManager->AddNtupleRow(1); } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... From 4bc94278d85fef259194104b3737a25c758c908a Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 11:47:51 +0200 Subject: [PATCH 03/33] fix Docker build: initialise pybind11 submodule before cmake Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/Dockerfile b/docker/Dockerfile index aed03b6..8fe7345 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -94,6 +94,7 @@ ARG USER ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ + git submodule update --init && \ mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 From 0c3f68d0cfb20b2e597e6f467868a58ae62ba68e Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 12:30:47 +0200 Subject: [PATCH 04/33] change container entrypoint from start-notebook.sh to bash Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8fe7345..06ff5bb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -128,5 +128,5 @@ RUN rm -rf /root/minicalosim RUN pip3 install pyarrow fastparquet USER ${NB_UID} -CMD ["start-notebook.sh"] +CMD ["bash"] From aaf0b33413f4d1b47f3192c93253f9265d68b2ed Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 12:38:52 +0200 Subject: [PATCH 05/33] add minimal Docker image (no CUDA, no Jupyter, batch-only Geant4) Dockerfile-mini builds a lightweight image with only what's needed to run the bind/ examples: Geant4 v11.1.2 (batch mode, no visualization), Python 3.10, and numpy/pandas/uproot/awkward/plotly/ipython. Examples are copied to /examples which is also the working directory. Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile-mini | 67 ++++++++++++++++++++++++++++++++++++++ docker/build_mini.sh | 74 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 docker/Dockerfile-mini create mode 100755 docker/build_mini.sh diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini new file mode 100644 index 0000000..756d509 --- /dev/null +++ b/docker/Dockerfile-mini @@ -0,0 +1,67 @@ +FROM ubuntu:22.04 + +SHELL ["/bin/bash", "-c"] + +USER root +ENV DEBIAN_FRONTEND=noninteractive + +# Build tools + minimal Python +RUN apt-get update -y && apt-get install -y \ + python3 python3-dev python3-pip \ + cmake g++ gcc binutils \ + libssl-dev wget git \ + && rm -rf /var/lib/apt/lists/* + +RUN python3 -m pip install --upgrade pip && \ + python3 -m pip install numpy pandas uproot awkward plotly ipython && \ + ln -sf /usr/bin/python3 /usr/bin/python + +# Geant4 v11.1.2 — batch mode only (no visualization, no multithreading) +RUN 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 && \ + rm geant4-v11.1.2.tar.gz && \ + mkdir /tmp/geant4-v11.1.2-build && \ + cmake \ + -S /tmp/geant4-v11.1.2 \ + -B /tmp/geant4-v11.1.2-build \ + -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ + -DCMAKE_RULE_MESSAGES=OFF \ + -DGEANT4_INSTALL_DATA=ON \ + -DGEANT4_BUILD_MULTITHREADED=OFF \ + -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ + > /dev/null && \ + cmake --build /tmp/geant4-v11.1.2-build --parallel $(nproc) > /dev/null && \ + cmake --install /tmp/geant4-v11.1.2-build > /dev/null && \ + rm -rf /tmp/geant4-v11.1.2 /tmp/geant4-v11.1.2-build + +ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}" + +# Build minicalo +ARG BUILD_DATE +ARG COMMIT +ARG USER +LABEL org.label-schema.build-date=$BUILD_DATE + +ADD minicalosim /root/minicalosim + +RUN cd /root/minicalosim && git checkout $COMMIT && \ + git submodule update --init && \ + cmake \ + -S . -B build \ + -DWITH_GEANT4_UIVIS=OFF \ + > /dev/null && \ + cmake --build build --parallel $(nproc) && \ + cp build/minicalo*.so /usr/local/lib/python3.10/dist-packages/ + +RUN cp /root/minicalosim/bind/G4Calo.py \ + /root/minicalosim/bind/minicalo_tools.py \ + /root/minicalosim/bind/minipandas.py \ + /usr/local/lib/python3.10/dist-packages/ && \ + install -m 755 /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/G4Calo_exec.py + +# Copy examples +RUN cp -r /root/minicalosim/bind /examples && \ + rm -rf /root/minicalosim + +WORKDIR /examples +CMD ["bash"] diff --git a/docker/build_mini.sh b/docker/build_mini.sh new file mode 100755 index 0000000..3cd5f08 --- /dev/null +++ b/docker/build_mini.sh @@ -0,0 +1,74 @@ +#!/usr/bin/bash + +if [ -n "$1" ]; then + if [ "$1" == "--no-cache" ]; then + FORCE_NO_CACHE="--no-cache" + COMMIT=$(git rev-parse --short HEAD) + else + COMMIT=$1 + 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 + COMMIT=$(git rev-parse --short HEAD) +fi + +if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then + 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() { + local url="$1" + local commit="$2" + local tmpdir + tmpdir="$(mktemp -d)" + trap 'rm -rf "$tmpdir"' RETURN + 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 +} + +if ! check_remote_commit_exists https://github.com/jkiesele/minicalosim $COMMIT; then + echo "WARNING: Commit $COMMIT not found in remote repository." +fi + +if [ "$2" == "--no-cache" ]; then + FORCE_NO_CACHE="--no-cache" +fi + +cd "$(dirname "$0")" +cd ../../ +echo "Building minicalosim-mini:$COMMIT" +echo "Current working directory: $(pwd)" + +docker build $FORCE_NO_CACHE \ + -t jkiesele/minicalosim-mini:$COMMIT \ + --build-arg USER=$USER \ + --build-arg BUILD_DATE="$(date)" \ + --build-arg COMMIT=$COMMIT \ + -f minicalosim/docker/Dockerfile-mini . + +echo "successfully built container jkiesele/minicalosim-mini:${COMMIT}" + +docker tag jkiesele/minicalosim-mini:$COMMIT jkiesele/minicalosim-mini:latest + +echo "also tagged as jkiesele/minicalosim-mini:latest" From 03b615cd3abdf71fe68831b943a439a2253f00a8 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 12:57:38 +0200 Subject: [PATCH 06/33] fix LD_LIBRARY_PATH warning in Dockerfile-mini Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile-mini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini index 756d509..f795d3c 100644 --- a/docker/Dockerfile-mini +++ b/docker/Dockerfile-mini @@ -34,7 +34,7 @@ RUN wget -q https://gitlab.cern.ch/geant4/geant4/-/archive/v11.1.2/geant4-v11.1. cmake --install /tmp/geant4-v11.1.2-build > /dev/null && \ rm -rf /tmp/geant4-v11.1.2 /tmp/geant4-v11.1.2-build -ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}" +ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib" # Build minicalo ARG BUILD_DATE From 7a62a46fe6e92441165530f0b412e477f074a0c8 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 13:59:01 +0200 Subject: [PATCH 07/33] add run_pbwo4.py and include examples dir in both Docker images run_pbwo4.py simulates 10 events in a single 20cm G4_PbWO4 layer (10x10 sensor grid, 1 GeV e-) and saves results as a pickle next to the script. Both images now expose /examples containing all bind/ files. Co-Authored-By: Claude Sonnet 4.6 --- bind/run_pbwo4.py | 14 ++++++++++++++ docker/Dockerfile | 2 ++ 2 files changed, 16 insertions(+) create mode 100644 bind/run_pbwo4.py diff --git a/bind/run_pbwo4.py b/bind/run_pbwo4.py new file mode 100644 index 0000000..5eb80e4 --- /dev/null +++ b/bind/run_pbwo4.py @@ -0,0 +1,14 @@ +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) + +df = run_batch(gd, 10, "e-", 1.0) + +out = os.path.join(output_dir, "pbwo4_10events.pkl") +df.to_pickle(out) +print(f"Saved {len(df)} events to {out}") diff --git a/docker/Dockerfile b/docker/Dockerfile index 06ff5bb..843f2fe 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -102,6 +102,8 @@ RUN cp /root/minicalosim/build/minicalo* /root/minicalosim/bind/G4Calo.py /root/ RUN cp /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/ +RUN cp -r /root/minicalosim/bind /examples + 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/ From 5f01ab6d35573286603f9015a753a84061c53f02 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 14:18:05 +0200 Subject: [PATCH 08/33] save hits and steps to separate pickles in run_pbwo4.py Co-Authored-By: Claude Sonnet 4.6 --- bind/run_pbwo4.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bind/run_pbwo4.py b/bind/run_pbwo4.py index 5eb80e4..ce7218e 100644 --- a/bind/run_pbwo4.py +++ b/bind/run_pbwo4.py @@ -7,8 +7,11 @@ output_dir = os.path.dirname(os.path.abspath(__file__)) gd = GeometryDescriptor() gd.addLayer(20.0, "G4_PbWO4", True, 10, 10) -df = run_batch(gd, 10, "e-", 1.0) +hits, steps = run_batch(gd, 10, "e-", 1.0, return_steps=True) -out = os.path.join(output_dir, "pbwo4_10events.pkl") -df.to_pickle(out) -print(f"Saved {len(df)} events to {out}") +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}") From df3faa841ca819cd53117f9254204d99f647d270 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 14:18:40 +0200 Subject: [PATCH 09/33] assemble steps inside try block to ensure temp file cleanup Co-Authored-By: Claude Sonnet 4.6 --- bind/G4Calo.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bind/G4Calo.py b/bind/G4Calo.py index eb93c4d..e59c21a 100644 --- a/bind/G4Calo.py +++ b/bind/G4Calo.py @@ -462,15 +462,17 @@ 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") + 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: - steps_df = _assemble_results_to_mini_df(rp, tree_name="Steps") return df, steps_df return df From f9a7f2d1f14ffad1a289323e9fb386905bbc2c40 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 15:55:25 +0200 Subject: [PATCH 10/33] add Geant4 source as shallow submodule (lib/geant4) Tracks HEAD of https://github.com/Geant4/geant4.git at depth 1 for reference browsing. shallow=true ensures others also get a shallow clone. Co-Authored-By: Claude Sonnet 4.6 --- .gitmodules | 4 ++++ lib/geant4 | 1 + 2 files changed, 5 insertions(+) create mode 160000 lib/geant4 diff --git a/.gitmodules b/.gitmodules index f7047fb..50e41c1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = lib/pybind11 url = https://github.com/pybind/pybind11 branch = stable +[submodule "lib/geant4"] + path = lib/geant4 + url = https://github.com/Geant4/geant4.git + shallow = true diff --git a/lib/geant4 b/lib/geant4 new file mode 160000 index 0000000..41f2dd7 --- /dev/null +++ b/lib/geant4 @@ -0,0 +1 @@ +Subproject commit 41f2dd79968018de4efb966f2546970b30c4af78 From 4ffb2f78b8f8ad1fb09778102a13372212932ce1 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 16:07:17 +0200 Subject: [PATCH 11/33] extend Steps ntuple and switch Docker to Geant4 submodule source Add 15 new columns to the Steps ntuple: edep, step_length, parent_id, process name, layer_id, material, pre-step momentum direction, and B/E field components (in T and V/m) at the pre-step point. Replace wget Geant4 tarball download in all three Dockerfiles with ADD of the lib/geant4 submodule source; narrow submodule init to lib/pybind11 only so builds no longer require network access for Geant4. Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 34 ++++++++++++----------- docker/Dockerfile-cpu | 35 +++++++++++++----------- docker/Dockerfile-mini | 22 +++++++-------- src/RunAction.cc | 17 +++++++++++- src/SteppingAction.cc | 61 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 45 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 843f2fe..d9eace4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -50,22 +50,24 @@ RUN mkdir dawn-source \ && make clean && make guiclean && make && make install \ && cd - && rm -rf "dawn-source" -## main package +## main package — build Geant4 from submodule source +ADD minicalosim/lib/geant4 /tmp/geant4-src -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 cmake \ + -S /tmp/geant4-src \ + -B /tmp/geant4-build \ + -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 \ + >/dev/null && \ + cmake --build /tmp/geant4-build --parallel $(nproc) >/dev/null && \ + cmake --install /tmp/geant4-build >/dev/null && \ + rm -rf /tmp/geant4-src /tmp/geant4-build RUN python3 -m pip install geant4-pybind @@ -94,7 +96,7 @@ ARG USER ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ - git submodule update --init && \ + git submodule update --init lib/pybind11 && \ mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 diff --git a/docker/Dockerfile-cpu b/docker/Dockerfile-cpu index 62c7f4b..b6d00ed 100644 --- a/docker/Dockerfile-cpu +++ b/docker/Dockerfile-cpu @@ -50,22 +50,24 @@ RUN mkdir dawn-source \ && make clean && make guiclean && make && make install \ && cd - && rm -rf "dawn-source" -## main package +## main package — build Geant4 from submodule source +ADD minicalosim/lib/geant4 /tmp/geant4-src -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 cmake \ + -S /tmp/geant4-src \ + -B /tmp/geant4-build \ + -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 \ + >/dev/null && \ + cmake --build /tmp/geant4-build --parallel $(nproc) >/dev/null && \ + cmake --install /tmp/geant4-build >/dev/null && \ + rm -rf /tmp/geant4-src /tmp/geant4-build RUN python3 -m pip install geant4-pybind @@ -94,7 +96,8 @@ ARG USER ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ - mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 &&\ + git submodule update --init lib/pybind11 && \ + mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(nproc) &&\ cp minicalo* ../bind/G4Calo.py /usr/local/lib/python3.8/dist-packages/ diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini index f795d3c..941e968 100644 --- a/docker/Dockerfile-mini +++ b/docker/Dockerfile-mini @@ -16,23 +16,21 @@ RUN python3 -m pip install --upgrade pip && \ python3 -m pip install numpy pandas uproot awkward plotly ipython && \ ln -sf /usr/bin/python3 /usr/bin/python -# Geant4 v11.1.2 — batch mode only (no visualization, no multithreading) -RUN 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 && \ - rm geant4-v11.1.2.tar.gz && \ - mkdir /tmp/geant4-v11.1.2-build && \ - cmake \ - -S /tmp/geant4-v11.1.2 \ - -B /tmp/geant4-v11.1.2-build \ +# Geant4 — build from submodule source (batch mode only, no visualization, no multithreading) +ADD minicalosim/lib/geant4 /tmp/geant4-src + +RUN cmake \ + -S /tmp/geant4-src \ + -B /tmp/geant4-build \ -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ -DCMAKE_RULE_MESSAGES=OFF \ -DGEANT4_INSTALL_DATA=ON \ -DGEANT4_BUILD_MULTITHREADED=OFF \ -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ > /dev/null && \ - cmake --build /tmp/geant4-v11.1.2-build --parallel $(nproc) > /dev/null && \ - cmake --install /tmp/geant4-v11.1.2-build > /dev/null && \ - rm -rf /tmp/geant4-v11.1.2 /tmp/geant4-v11.1.2-build + cmake --build /tmp/geant4-build --parallel $(nproc) > /dev/null && \ + cmake --install /tmp/geant4-build > /dev/null && \ + rm -rf /tmp/geant4-src /tmp/geant4-build ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib" @@ -45,7 +43,7 @@ LABEL org.label-schema.build-date=$BUILD_DATE ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ - git submodule update --init && \ + git submodule update --init lib/pybind11 && \ cmake \ -S . -B build \ -DWITH_GEANT4_UIVIS=OFF \ diff --git a/src/RunAction.cc b/src/RunAction.cc index b19eed9..c14ef49 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -95,7 +95,22 @@ RunAction::RunAction() 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("post_E"); // col 11 + analysisManager->CreateNtupleDColumn("edep"); // col 12 [MeV] + analysisManager->CreateNtupleDColumn("step_length"); // col 13 [mm] + analysisManager->CreateNtupleIColumn("parent_id"); // col 14 + analysisManager->CreateNtupleSColumn("process"); // col 15 + analysisManager->CreateNtupleIColumn("layer_id"); // col 16 (-1 = outside all layers) + analysisManager->CreateNtupleSColumn("material"); // col 17 + analysisManager->CreateNtupleDColumn("pre_dx"); // col 18 + analysisManager->CreateNtupleDColumn("pre_dy"); // col 19 + analysisManager->CreateNtupleDColumn("pre_dz"); // col 20 + analysisManager->CreateNtupleDColumn("Bx"); // col 21 [T] + analysisManager->CreateNtupleDColumn("By"); // col 22 [T] + analysisManager->CreateNtupleDColumn("Bz"); // col 23 [T] + analysisManager->CreateNtupleDColumn("Ex"); // col 24 [V/m] + analysisManager->CreateNtupleDColumn("Ey"); // col 25 [V/m] + analysisManager->CreateNtupleDColumn("Ez"); // col 26 [V/m] analysisManager->FinishNtuple(); } diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index 1f84d52..964c28c 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -36,6 +36,10 @@ #include "G4AnalysisManager.hh" #include "G4Track.hh" #include "G4ParticleDefinition.hh" +#include "G4TransportationManager.hh" +#include "G4FieldManager.hh" +#include "G4Field.hh" +#include "G4SystemOfUnits.hh" using namespace B4; @@ -95,6 +99,63 @@ void SteppingAction::UserSteppingAction(const G4Step* step) 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()); + + // C: parent track ID (0 for primary) + analysisManager->FillNtupleIColumn(1, 14, track->GetParentID()); + + // D: process that ended this step + G4String processName = ""; + const auto* postProc = post->GetProcessDefinedStep(); + if (postProc) processName = postProc->GetProcessName(); + analysisManager->FillNtupleSColumn(1, 15, 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, 16, layerId); + analysisManager->FillNtupleSColumn(1, 17, materialName); + + // F: pre-step momentum direction (unit vector) + const auto dir = pre->GetMomentumDirection(); + analysisManager->FillNtupleDColumn(1, 18, dir.x()); + analysisManager->FillNtupleDColumn(1, 19, dir.y()); + analysisManager->FillNtupleDColumn(1, 20, dir.z()); + + // 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, 21, Bx); + analysisManager->FillNtupleDColumn(1, 22, By); + analysisManager->FillNtupleDColumn(1, 23, Bz); + analysisManager->FillNtupleDColumn(1, 24, Ex); + analysisManager->FillNtupleDColumn(1, 25, Ey); + analysisManager->FillNtupleDColumn(1, 26, Ez); + analysisManager->AddNtupleRow(1); } From f5c832fd44c9d5082c8e6904b8aaac2aa69676ba Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 16:09:55 +0200 Subject: [PATCH 12/33] fix Geant4 cmake git-dirty check when building from submodule ADD The submodule leaves a .git *file* (not directory) pointing to ../../.git/modules/lib/geant4, which passes G4GitUtilities' guard but causes a FATAL_ERROR when git status fails in the bare /tmp dir. Remove .git before cmake so the guard fires and the check is skipped. Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 3 ++- docker/Dockerfile-cpu | 3 ++- docker/Dockerfile-mini | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index d9eace4..3b91acf 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -53,7 +53,8 @@ RUN mkdir dawn-source \ ## main package — build Geant4 from submodule source ADD minicalosim/lib/geant4 /tmp/geant4-src -RUN cmake \ +RUN rm -f /tmp/geant4-src/.git && \ + cmake \ -S /tmp/geant4-src \ -B /tmp/geant4-build \ -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ diff --git a/docker/Dockerfile-cpu b/docker/Dockerfile-cpu index b6d00ed..2c60cf0 100644 --- a/docker/Dockerfile-cpu +++ b/docker/Dockerfile-cpu @@ -53,7 +53,8 @@ RUN mkdir dawn-source \ ## main package — build Geant4 from submodule source ADD minicalosim/lib/geant4 /tmp/geant4-src -RUN cmake \ +RUN rm -f /tmp/geant4-src/.git && \ + cmake \ -S /tmp/geant4-src \ -B /tmp/geant4-build \ -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini index 941e968..004314b 100644 --- a/docker/Dockerfile-mini +++ b/docker/Dockerfile-mini @@ -19,7 +19,8 @@ RUN python3 -m pip install --upgrade pip && \ # Geant4 — build from submodule source (batch mode only, no visualization, no multithreading) ADD minicalosim/lib/geant4 /tmp/geant4-src -RUN cmake \ +RUN rm -f /tmp/geant4-src/.git && \ + cmake \ -S /tmp/geant4-src \ -B /tmp/geant4-build \ -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ From 5fc50bcefc012925ed1289a3d329b07a7001af38 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 8 Jun 2026 16:35:31 +0200 Subject: [PATCH 13/33] print commit hash on import; fix event_id offset in multi-core steps G4Calo.py prints the commit at import time, resolved via a minicalo_version.py file (baked in by Docker) or git rev-parse from the file's directory as fallback. event_id in the Steps ntuple restarts from 0 in each subprocess; _assemble_results_to_mini_df now accepts per-file event_id_offsets so IDs are globally unique after merging. run_batch passes cumulative nevents as offsets when loading the Steps tree. Dockerfiles write minicalo_version.py before copying Python files. Co-Authored-By: Claude Sonnet 4.6 --- bind/G4Calo.py | 39 ++++++++++++++++++++++++++++++++++----- docker/Dockerfile | 4 +++- docker/Dockerfile-cpu | 3 ++- docker/Dockerfile-mini | 4 +++- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/bind/G4Calo.py b/bind/G4Calo.py index e59c21a..d3470d4 100644 --- a/bind/G4Calo.py +++ b/bind/G4Calo.py @@ -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) @@ -412,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 @@ -466,7 +494,8 @@ Example try: df = _assemble_results_to_mini_df(rp) if return_steps: - steps_df = _assemble_results_to_mini_df(rp, tree_name="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): diff --git a/docker/Dockerfile b/docker/Dockerfile index 3b91acf..ef4a2a4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -101,7 +101,9 @@ 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 echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py + +RUN cp /root/minicalosim/build/minicalo* /root/minicalosim/bind/G4Calo.py /root/minicalosim/bind/minicalo_tools.py /root/minicalosim/bind/minipandas.py /root/minicalosim/bind/minicalo_version.py /usr/local/lib/python3.10/dist-packages/ RUN cp /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/ diff --git a/docker/Dockerfile-cpu b/docker/Dockerfile-cpu index 2c60cf0..6ec54ca 100644 --- a/docker/Dockerfile-cpu +++ b/docker/Dockerfile-cpu @@ -99,7 +99,8 @@ ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ git submodule update --init lib/pybind11 && \ mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(nproc) &&\ - cp minicalo* ../bind/G4Calo.py /usr/local/lib/python3.8/dist-packages/ + echo "commit = '${COMMIT}'" > ../bind/minicalo_version.py && \ + cp minicalo* ../bind/G4Calo.py ../bind/minicalo_version.py /usr/local/lib/python3.8/dist-packages/ diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini index 004314b..7949860 100644 --- a/docker/Dockerfile-mini +++ b/docker/Dockerfile-mini @@ -52,9 +52,11 @@ RUN cd /root/minicalosim && git checkout $COMMIT && \ cmake --build build --parallel $(nproc) && \ cp build/minicalo*.so /usr/local/lib/python3.10/dist-packages/ -RUN cp /root/minicalosim/bind/G4Calo.py \ +RUN echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py && \ + cp /root/minicalosim/bind/G4Calo.py \ /root/minicalosim/bind/minicalo_tools.py \ /root/minicalosim/bind/minipandas.py \ + /root/minicalosim/bind/minicalo_version.py \ /usr/local/lib/python3.10/dist-packages/ && \ install -m 755 /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/G4Calo_exec.py From 18de7dd3db6226c00497f1a42b7e44a354cde196 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 10 Jun 2026 08:20:27 +0200 Subject: [PATCH 14/33] limit Docker build parallelism to min(nproc, 16) Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 4 ++-- docker/Dockerfile-cpu | 4 ++-- docker/Dockerfile-mini | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index ef4a2a4..a59ee64 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -66,7 +66,7 @@ RUN rm -f /tmp/geant4-src/.git && \ -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ -DGEANT4_USE_RAYTRACER_X11=ON \ >/dev/null && \ - cmake --build /tmp/geant4-build --parallel $(nproc) >/dev/null && \ + cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) >/dev/null && \ cmake --install /tmp/geant4-build >/dev/null && \ rm -rf /tmp/geant4-src /tmp/geant4-build @@ -98,7 +98,7 @@ ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ git submodule update --init lib/pybind11 && \ - mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 + mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(( $(nproc) < 16 ? $(nproc) : 16 )) RUN echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py diff --git a/docker/Dockerfile-cpu b/docker/Dockerfile-cpu index 6ec54ca..9f5d447 100644 --- a/docker/Dockerfile-cpu +++ b/docker/Dockerfile-cpu @@ -66,7 +66,7 @@ RUN rm -f /tmp/geant4-src/.git && \ -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ -DGEANT4_USE_RAYTRACER_X11=ON \ >/dev/null && \ - cmake --build /tmp/geant4-build --parallel $(nproc) >/dev/null && \ + cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) >/dev/null && \ cmake --install /tmp/geant4-build >/dev/null && \ rm -rf /tmp/geant4-src /tmp/geant4-build @@ -98,7 +98,7 @@ ADD minicalosim /root/minicalosim RUN cd /root/minicalosim && git checkout $COMMIT && \ git submodule update --init lib/pybind11 && \ - mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(nproc) &&\ + mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(( $(nproc) < 16 ? $(nproc) : 16 )) &&\ echo "commit = '${COMMIT}'" > ../bind/minicalo_version.py && \ cp minicalo* ../bind/G4Calo.py ../bind/minicalo_version.py /usr/local/lib/python3.8/dist-packages/ diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini index 7949860..7a45b7c 100644 --- a/docker/Dockerfile-mini +++ b/docker/Dockerfile-mini @@ -29,7 +29,7 @@ RUN rm -f /tmp/geant4-src/.git && \ -DGEANT4_BUILD_MULTITHREADED=OFF \ -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ > /dev/null && \ - cmake --build /tmp/geant4-build --parallel $(nproc) > /dev/null && \ + cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) > /dev/null && \ cmake --install /tmp/geant4-build > /dev/null && \ rm -rf /tmp/geant4-src /tmp/geant4-build @@ -49,7 +49,7 @@ RUN cd /root/minicalosim && git checkout $COMMIT && \ -S . -B build \ -DWITH_GEANT4_UIVIS=OFF \ > /dev/null && \ - cmake --build build --parallel $(nproc) && \ + cmake --build build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) && \ cp build/minicalo*.so /usr/local/lib/python3.10/dist-packages/ RUN echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py && \ From 01df646aa9ddc2877da07322ac9d8adb9bbb8291 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 10 Jun 2026 09:19:55 +0200 Subject: [PATCH 15/33] add run_pbwo4 C++ executable; fix missing std headers for Geant4 11.4 Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 3 +++ include/PrimaryGeneratorAction.hh | 2 ++ include/RunAction.hh | 1 + run_pbwo4.cc | 36 +++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 run_pbwo4.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index fb75b02..642a1a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,9 @@ target_link_libraries(minicalo PUBLIC ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) add_executable(exampleB4a exampleB4a.cc ${sources} ${headers}) target_link_libraries(exampleB4a ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) +add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers}) +target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) + #---------------------------------------------------------------------------- # 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 diff --git a/include/PrimaryGeneratorAction.hh b/include/PrimaryGeneratorAction.hh index 0366a7f..3cc9219 100644 --- a/include/PrimaryGeneratorAction.hh +++ b/include/PrimaryGeneratorAction.hh @@ -32,6 +32,8 @@ #include "G4VUserPrimaryGeneratorAction.hh" #include "globals.hh" +#include +#include class G4ParticleGun; class G4Event; diff --git a/include/RunAction.hh b/include/RunAction.hh index 6c89fc2..854bb55 100644 --- a/include/RunAction.hh +++ b/include/RunAction.hh @@ -32,6 +32,7 @@ #include "G4UserRunAction.hh" #include "globals.hh" +#include class G4Run; diff --git a/run_pbwo4.cc b/run_pbwo4.cc new file mode 100644 index 0000000..1754eb1 --- /dev/null +++ b/run_pbwo4.cc @@ -0,0 +1,36 @@ +#include "GeometryDescriptor.hh" +#include "G4System.hh" + +#include +#include +#include + +int main(int argc, char** argv) { + int nEvents = 10; + + if (argc > 2) { + std::cerr << "Usage: run_pbwo4 [nEvents]" << 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; + } + } + + 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, -1); + g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); + + std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; + return 0; +} From 718f9361f7af0d36035bcf7693076f9a5baf6820 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:22:43 +0200 Subject: [PATCH 16/33] docs: archive parent_step_no design plan Co-Authored-By: Claude Sonnet 4.6 --- docs/plan_parent_step_no.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 docs/plan_parent_step_no.md diff --git a/docs/plan_parent_step_no.md b/docs/plan_parent_step_no.md new file mode 100644 index 0000000..4198d59 --- /dev/null +++ b/docs/plan_parent_step_no.md @@ -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(sec)->SetUserInformation(...)`. Geant4 takes ownership and deletes it with the track. + +2. **Read at tracking** — when filling a Steps ntuple row, retrieve `dynamic_cast(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. From 9ea071813904e7629d33818469da70c93ce5daa4 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:25:08 +0200 Subject: [PATCH 17/33] add Spawning ntuple recording secondary births per step Each step that spawns secondaries now writes one row per secondary to a new Spawning ntuple (event_id, parent_track_id, parent_step_no, child_track_id). This enables direct shower-tree reconstruction without position joins. Requires the companion Geant4 submodule change that pre-assigns track IDs in ProcessSecondariesFromParticleChange. Co-Authored-By: Claude Sonnet 4.6 --- lib/geant4 | 2 +- src/RunAction.cc | 8 ++++++++ src/SteppingAction.cc | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/geant4 b/lib/geant4 index 41f2dd7..e7827c9 160000 --- a/lib/geant4 +++ b/lib/geant4 @@ -1 +1 @@ -Subproject commit 41f2dd79968018de4efb966f2546970b30c4af78 +Subproject commit e7827c9b07aa6788f37e70658302945d6f129c70 diff --git a/src/RunAction.cc b/src/RunAction.cc index c14ef49..d9d9afa 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -112,6 +112,14 @@ RunAction::RunAction() analysisManager->CreateNtupleDColumn("Ey"); // col 25 [V/m] analysisManager->CreateNtupleDColumn("Ez"); // col 26 [V/m] 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(); } //....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index 964c28c..4a6ac67 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -87,6 +87,18 @@ void SteppingAction::UserSteppingAction(const G4Step* step) auto track = step->GetTrack(); int evtId = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); + // Write one Spawning row per secondary born in this step (track IDs pre-assigned) + const auto* secondaries = step->GetSecondaryInCurrentStep(); + if (secondaries) { + for (const G4Track* sec : *secondaries) { + 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()); From 517ba2ecb2a9b6da29d021c051bb1fd7fa461422 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:30:36 +0200 Subject: [PATCH 18/33] add child_track_ids array column to Steps; remove parent_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each Steps row now carries a variable-length child_track_ids int array containing the track IDs of all secondaries spawned during that step. The parent_id column is removed — parentage is now expressed unidirectionally (parent→children) via child_track_ids and the Spawning ntuple, rather than bidirectionally. Co-Authored-By: Claude Sonnet 4.6 --- include/RunAction.hh | 2 ++ include/SteppingAction.hh | 4 ++++ src/ActionInitialization.cc | 1 + src/RunAction.cc | 26 +++++++++++++------------- src/SteppingAction.cc | 33 +++++++++++++++++---------------- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/include/RunAction.hh b/include/RunAction.hh index 854bb55..20a1e8d 100644 --- a/include/RunAction.hh +++ b/include/RunAction.hh @@ -83,6 +83,8 @@ class RunAction : public G4UserRunAction mutable std::vector hitLayer; mutable std::vector hitCopyNumber; + mutable std::vector stepChildIds; + mutable G4String filename; }; diff --git a/include/SteppingAction.hh b/include/SteppingAction.hh index 85cdd0f..6cf86cb 100644 --- a/include/SteppingAction.hh +++ b/include/SteppingAction.hh @@ -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; }; } diff --git a/src/ActionInitialization.cc b/src/ActionInitialization.cc index d862832..5383cea 100644 --- a/src/ActionInitialization.cc +++ b/src/ActionInitialization.cc @@ -70,6 +70,7 @@ void ActionInitialization::Build() const eventAction->setPrimaryGeneratorAction(gen); SetUserAction(eventAction); auto steppingAction = new SteppingAction(eventAction); + steppingAction->setRunAction(runact); SetUserAction(steppingAction); } diff --git a/src/RunAction.cc b/src/RunAction.cc index d9d9afa..2d7507f 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -98,19 +98,19 @@ RunAction::RunAction() analysisManager->CreateNtupleDColumn("post_E"); // col 11 analysisManager->CreateNtupleDColumn("edep"); // col 12 [MeV] analysisManager->CreateNtupleDColumn("step_length"); // col 13 [mm] - analysisManager->CreateNtupleIColumn("parent_id"); // col 14 - analysisManager->CreateNtupleSColumn("process"); // col 15 - analysisManager->CreateNtupleIColumn("layer_id"); // col 16 (-1 = outside all layers) - analysisManager->CreateNtupleSColumn("material"); // col 17 - analysisManager->CreateNtupleDColumn("pre_dx"); // col 18 - analysisManager->CreateNtupleDColumn("pre_dy"); // col 19 - analysisManager->CreateNtupleDColumn("pre_dz"); // col 20 - analysisManager->CreateNtupleDColumn("Bx"); // col 21 [T] - analysisManager->CreateNtupleDColumn("By"); // col 22 [T] - analysisManager->CreateNtupleDColumn("Bz"); // col 23 [T] - analysisManager->CreateNtupleDColumn("Ex"); // col 24 [V/m] - analysisManager->CreateNtupleDColumn("Ey"); // col 25 [V/m] - analysisManager->CreateNtupleDColumn("Ez"); // col 26 [V/m] + 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->FinishNtuple(); // Spawning ntuple (one row per secondary born, ntuple id=2) diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index 4a6ac67..cb4146f 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -87,10 +87,13 @@ void SteppingAction::UserSteppingAction(const G4Step* step) auto track = step->GetTrack(); int evtId = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID(); - // Write one Spawning row per secondary born in this step (track IDs pre-assigned) + // 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()); @@ -118,14 +121,11 @@ void SteppingAction::UserSteppingAction(const G4Step* step) // B: geometric step length analysisManager->FillNtupleDColumn(1, 13, step->GetStepLength()); - // C: parent track ID (0 for primary) - analysisManager->FillNtupleIColumn(1, 14, track->GetParentID()); - // D: process that ended this step G4String processName = ""; const auto* postProc = post->GetProcessDefinedStep(); if (postProc) processName = postProc->GetProcessName(); - analysisManager->FillNtupleSColumn(1, 15, processName); + analysisManager->FillNtupleSColumn(1, 14, processName); // E: layer index (-1 if outside all layers) and material name at pre-step point int layerId = -1; @@ -134,14 +134,14 @@ void SteppingAction::UserSteppingAction(const G4Step* step) if (layers[i].physicalVolume == volume) { layerId = i; break; } } G4String materialName = pre->GetMaterial() ? pre->GetMaterial()->GetName() : ""; - analysisManager->FillNtupleIColumn(1, 16, layerId); - analysisManager->FillNtupleSColumn(1, 17, materialName); + 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, 18, dir.x()); - analysisManager->FillNtupleDColumn(1, 19, dir.y()); - analysisManager->FillNtupleDColumn(1, 20, dir.z()); + analysisManager->FillNtupleDColumn(1, 17, dir.x()); + analysisManager->FillNtupleDColumn(1, 18, dir.y()); + analysisManager->FillNtupleDColumn(1, 19, dir.z()); // 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; @@ -161,12 +161,13 @@ void SteppingAction::UserSteppingAction(const G4Step* step) Ez = fieldVal[5] / (volt/m); } } - analysisManager->FillNtupleDColumn(1, 21, Bx); - analysisManager->FillNtupleDColumn(1, 22, By); - analysisManager->FillNtupleDColumn(1, 23, Bz); - analysisManager->FillNtupleDColumn(1, 24, Ex); - analysisManager->FillNtupleDColumn(1, 25, Ey); - analysisManager->FillNtupleDColumn(1, 26, Ez); + 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); } From 121aab961f4b2f00455d2ed7b1e9c0913f0cb4ec Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:36:05 +0200 Subject: [PATCH 19/33] point geant4 submodule at personal GitLab fork Switch from upstream Geant4/geant4 on GitHub to lbogner/geant4 on GitLab, which carries the minicalosim-spawning branch with the pre-assigned track ID changes. Co-Authored-By: Claude Sonnet 4.6 --- .gitmodules | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 50e41c1..ec6b94e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,5 +4,4 @@ branch = stable [submodule "lib/geant4"] path = lib/geant4 - url = https://github.com/Geant4/geant4.git - shallow = true + url = git@gitlab.etp.kit.edu:lbogner/geant4.git From e265a6aa6308d0a72057559e3eed3e193d8f9f6a Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:41:47 +0200 Subject: [PATCH 20/33] add superbuild to compile Geant4 from submodule source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit superbuild/CMakeLists.txt uses ExternalProject_Add to give three targets: cmake -B build -S superbuild/ cmake --build build --target geant4 # build Geant4 → build/geant4-install cmake --build build --target run_pbwo4 # build minicalosim against it cmake --build build # both The existing root CMakeLists.txt (find_package Geant4) is unchanged for the system-Geant4 workflow. Co-Authored-By: Claude Sonnet 4.6 --- superbuild/CMakeLists.txt | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 superbuild/CMakeLists.txt diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt new file mode 100644 index 0000000..4a64abe --- /dev/null +++ b/superbuild/CMakeLists.txt @@ -0,0 +1,48 @@ +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) + +# ── Geant4 ──────────────────────────────────────────────────────────────────── +ExternalProject_Add(geant4 + SOURCE_DIR ${G4_SRC} + BINARY_DIR ${CMAKE_BINARY_DIR}/geant4-build + INSTALL_DIR ${G4_INSTALL} + CMAKE_ARGS + -DCMAKE_INSTALL_PREFIX= + -DCMAKE_BUILD_TYPE=Release + -DGEANT4_INSTALL_DATA=${GEANT4_INSTALL_DATA} + -DGEANT4_USE_GDML=ON + -DGEANT4_BUILD_MULTITHREADED=OFF + -DGEANT4_BUILD_TLS_MODEL=global-dynamic + BUILD_COMMAND cmake --build --parallel ${NPROC} + INSTALL_COMMAND cmake --install +) + +# ── 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 --target run_pbwo4 --parallel ${NPROC} + INSTALL_COMMAND "" + DEPENDS geant4 +) From 2a0a23a45b4b98995ad299b84814a839880c2b08 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 10:45:17 +0200 Subject: [PATCH 21/33] remove example files, mac files, and unused docker variants Keep only the core C++ source and docker/Dockerfile-mini. Removes: - exampleB4a (example executable and associated scripts) - Geant4 macro (.mac) files - ROOT plotting scripts - GNUmakefile (old non-CMake build system) - All Dockerfiles and scripts except Dockerfile-mini Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 30 +- GNUmakefile | 21 - docker/Dockerfile | 139 ----- docker/Dockerfile-cpu | 132 ----- docker/build.sh | 95 --- docker/build_cpu.sh | 73 --- docker/build_mini.sh | 74 --- docker/check_cuda_torch.py | 26 - docker/fix-permissions | 33 -- docker/start-notebook.py | 44 -- docker/start-notebook.sh | 35 -- docker/start-singleuser.py | 26 - docker/start-singleuser.sh | 5 - exampleB4.in | 23 - exampleB4a.cc | 285 --------- exampleB4a.out | 1111 ------------------------------------ gui.mac | 37 -- init_vis.mac | 17 - plotHisto.C | 43 -- plotNtuple.C | 42 -- run1.mac | 44 -- run2.mac | 17 - vis.mac | 82 --- 23 files changed, 1 insertion(+), 2433 deletions(-) delete mode 100644 GNUmakefile delete mode 100644 docker/Dockerfile delete mode 100644 docker/Dockerfile-cpu delete mode 100755 docker/build.sh delete mode 100755 docker/build_cpu.sh delete mode 100755 docker/build_mini.sh delete mode 100755 docker/check_cuda_torch.py delete mode 100755 docker/fix-permissions delete mode 100755 docker/start-notebook.py delete mode 100755 docker/start-notebook.sh delete mode 100755 docker/start-singleuser.py delete mode 100755 docker/start-singleuser.sh delete mode 100644 exampleB4.in delete mode 100644 exampleB4a.cc delete mode 100644 exampleB4a.out delete mode 100644 gui.mac delete mode 100644 init_vis.mac delete mode 100644 plotHisto.C delete mode 100644 plotNtuple.C delete mode 100644 run1.mac delete mode 100644 run2.mac delete mode 100644 vis.mac diff --git a/CMakeLists.txt b/CMakeLists.txt index 642a1a4..b0133e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,38 +55,10 @@ 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}) - add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers}) target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) -#---------------------------------------------------------------------------- -# 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 -) - -foreach(_script ${EXAMPLEB4A_SCRIPTS}) - configure_file( - ${PROJECT_SOURCE_DIR}/${_script} - ${PROJECT_BINARY_DIR}/${_script} - COPYONLY - ) -endforeach() - #---------------------------------------------------------------------------- # Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX # -#install(TARGETS exampleB4a DESTINATION bin) +#install(TARGETS run_pbwo4 DESTINATION bin) diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index c84b365..0000000 --- a/GNUmakefile +++ /dev/null @@ -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_* - diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index a59ee64..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,139 +0,0 @@ -FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.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 — build Geant4 from submodule source -ADD minicalosim/lib/geant4 /tmp/geant4-src - -RUN rm -f /tmp/geant4-src/.git && \ - cmake \ - -S /tmp/geant4-src \ - -B /tmp/geant4-build \ - -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 \ - >/dev/null && \ - cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) >/dev/null && \ - cmake --install /tmp/geant4-build >/dev/null && \ - rm -rf /tmp/geant4-src /tmp/geant4-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 && \ - git submodule update --init lib/pybind11 && \ - mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(( $(nproc) < 16 ? $(nproc) : 16 )) - - -RUN echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py - -RUN cp /root/minicalosim/build/minicalo* /root/minicalosim/bind/G4Calo.py /root/minicalosim/bind/minicalo_tools.py /root/minicalosim/bind/minipandas.py /root/minicalosim/bind/minicalo_version.py /usr/local/lib/python3.10/dist-packages/ - -RUN cp /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/ - -RUN cp -r /root/minicalosim/bind /examples - -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 ["bash"] - diff --git a/docker/Dockerfile-cpu b/docker/Dockerfile-cpu deleted file mode 100644 index 9f5d447..0000000 --- a/docker/Dockerfile-cpu +++ /dev/null @@ -1,132 +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 — build Geant4 from submodule source -ADD minicalosim/lib/geant4 /tmp/geant4-src - -RUN rm -f /tmp/geant4-src/.git && \ - cmake \ - -S /tmp/geant4-src \ - -B /tmp/geant4-build \ - -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 \ - >/dev/null && \ - cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) >/dev/null && \ - cmake --install /tmp/geant4-build >/dev/null && \ - rm -rf /tmp/geant4-src /tmp/geant4-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 && \ - git submodule update --init lib/pybind11 && \ - mkdir -p build && cd build && rm -rf * && cmake ../ && make -j$(( $(nproc) < 16 ? $(nproc) : 16 )) &&\ - echo "commit = '${COMMIT}'" > ../bind/minicalo_version.py && \ - cp minicalo* ../bind/G4Calo.py ../bind/minicalo_version.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"] - diff --git a/docker/build.sh b/docker/build.sh deleted file mode 100755 index 2aa89fb..0000000 --- a/docker/build.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/bash - -# a simple parser for the args 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 - 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" \ No newline at end of file diff --git a/docker/build_cpu.sh b/docker/build_cpu.sh deleted file mode 100755 index d751096..0000000 --- a/docker/build_cpu.sh +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/bash - -# a simple parser for the args 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" \ No newline at end of file diff --git a/docker/build_mini.sh b/docker/build_mini.sh deleted file mode 100755 index 3cd5f08..0000000 --- a/docker/build_mini.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/bash - -if [ -n "$1" ]; then - if [ "$1" == "--no-cache" ]; then - FORCE_NO_CACHE="--no-cache" - COMMIT=$(git rev-parse --short HEAD) - else - COMMIT=$1 - 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 - COMMIT=$(git rev-parse --short HEAD) -fi - -if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then - 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() { - local url="$1" - local commit="$2" - local tmpdir - tmpdir="$(mktemp -d)" - trap 'rm -rf "$tmpdir"' RETURN - 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 -} - -if ! check_remote_commit_exists https://github.com/jkiesele/minicalosim $COMMIT; then - echo "WARNING: Commit $COMMIT not found in remote repository." -fi - -if [ "$2" == "--no-cache" ]; then - FORCE_NO_CACHE="--no-cache" -fi - -cd "$(dirname "$0")" -cd ../../ -echo "Building minicalosim-mini:$COMMIT" -echo "Current working directory: $(pwd)" - -docker build $FORCE_NO_CACHE \ - -t jkiesele/minicalosim-mini:$COMMIT \ - --build-arg USER=$USER \ - --build-arg BUILD_DATE="$(date)" \ - --build-arg COMMIT=$COMMIT \ - -f minicalosim/docker/Dockerfile-mini . - -echo "successfully built container jkiesele/minicalosim-mini:${COMMIT}" - -docker tag jkiesele/minicalosim-mini:$COMMIT jkiesele/minicalosim-mini:latest - -echo "also tagged as jkiesele/minicalosim-mini:latest" diff --git a/docker/check_cuda_torch.py b/docker/check_cuda_torch.py deleted file mode 100755 index 8ef2ce3..0000000 --- a/docker/check_cuda_torch.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/docker/fix-permissions b/docker/fix-permissions deleted file mode 100755 index d540462..0000000 --- a/docker/fix-permissions +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docker/start-notebook.py b/docker/start-notebook.py deleted file mode 100755 index 973da5a..0000000 --- a/docker/start-notebook.py +++ /dev/null @@ -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) diff --git a/docker/start-notebook.sh b/docker/start-notebook.sh deleted file mode 100755 index 41db342..0000000 --- a/docker/start-notebook.sh +++ /dev/null @@ -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 diff --git a/docker/start-singleuser.py b/docker/start-singleuser.py deleted file mode 100755 index c80339f..0000000 --- a/docker/start-singleuser.py +++ /dev/null @@ -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) diff --git a/docker/start-singleuser.sh b/docker/start-singleuser.sh deleted file mode 100755 index ecf0e06..0000000 --- a/docker/start-singleuser.sh +++ /dev/null @@ -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 "$@" diff --git a/exampleB4.in b/exampleB4.in deleted file mode 100644 index 02052f0..0000000 --- a/exampleB4.in +++ /dev/null @@ -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 diff --git a/exampleB4a.cc b/exampleB4a.cc deleted file mode 100644 index 55351f9..0000000 --- a/exampleB4a.cc +++ /dev/null @@ -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 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 parts = {"e-"}; - builder.run_batch(10000,parts , 1, 100); - - //run(cw, 1000, "e-", 1, 100, true); - - return 0; -} - -//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo..... - diff --git a/exampleB4a.out b/exampleB4a.out deleted file mode 100644 index 2c978fa..0000000 --- a/exampleB4a.out +++ /dev/null @@ -1,1111 +0,0 @@ -Environment variable "G4FORCE_RUN_MANAGER_TYPE" enabled with value == Serial. Forcing G4RunManager type... - - ############################################ - !!! WARNING - FPE detection is activated !!! - ############################################ - - - ################################ - !!! G4Backtrace is activated !!! - ################################ - - -************************************************************** - Geant4 version Name: geant4-11-01-patch-02 (15-June-2023) - Copyright : Geant4 Collaboration - References : NIM A 506 (2003), 250-303 - : IEEE-TNS 53 (2006), 270-278 - : NIM A 835 (2016), 186-225 - WWW : http://geant4.org/ -************************************************************** - -<<< Geant4 Physics List simulation engine: FTFP_BERT - -Visualization Manager instantiating with verbosity "warnings (3)"... -Visualization Manager initialising... -Registering graphics systems... - -You have successfully registered the following graphics systems. -Registered graphics systems are: - ASCIITree (ATree) - DAWNFILE (DAWNFILE) - G4HepRepFile (HepRepFile) - RayTracer (RayTracer) - VRML2FILE (VRML2FILE) - gMocrenFile (gMocrenFile) - TOOLSSG_OFFSCREEN (TSG_OFFSCREEN) - TOOLSSG_OFFSCREEN (TSG_OFFSCREEN, TSG_FILE) - OpenGLImmediateQt (OGLIQt, OGLI) - OpenGLStoredQt (OGLSQt, OGL, OGLS) - OpenGLImmediateXm (OGLIXm, OGLIQt_FALLBACK) - OpenGLStoredXm (OGLSXm, OGLSQt_FALLBACK) - OpenGLImmediateX (OGLIX, OGLIQt_FALLBACK, OGLIXm_FALLBACK) - OpenGLStoredX (OGLSX, OGLSQt_FALLBACK, OGLSXm_FALLBACK) - RayTracerX (RayTracerX) - Qt3D (Qt3D) - TOOLSSG_X11_GLES (TSG_X11_GLES, TSGX11, TSG_XT_GLES_FALLBACK) - TOOLSSG_XT_GLES (TSG_XT_GLES, TSGXt, TSG_QT_GLES_FALLBACK) - TOOLSSG_QT_GLES (TSG_QT_GLES, TSGQt, TSG) - -Registering model factories... - -You have successfully registered the following model factories. -Registered model factories: - generic - drawByAttribute - drawByCharge - drawByOriginVolume - drawByParticleID - drawByEncounteredVolume - -Registered models: - None - -Registered filter factories: - attributeFilter - chargeFilter - originVolumeFilter - particleFilter - encounteredVolumeFilter - -Registered filters: - None - -You have successfully registered the following user vis actions. -Run Duration User Vis Actions: none -End of Event User Vis Actions: none -End of Run User Vis Actions: none - -Some /vis commands (optionally) take a string to specify colour. -"/vis/list" to see available colours. - -***** Table : Nb of materials = 3 ***** - - Material: G4_Pb density: 11.350 g/cm3 RadL: 5.613 mm Nucl.Int.Length: 18.248 cm - Imean: 823.000 eV temperature: 293.15 K pressure: 1.00 atm - - ---> Element: Pb (Pb) Z = 82.0 N = 207 A = 207.217 g/mole - ---> Isotope: Pb204 Z = 82 N = 204 A = 203.97 g/mole abundance: 1.400 % - ---> Isotope: Pb206 Z = 82 N = 206 A = 205.97 g/mole abundance: 24.100 % - ---> Isotope: Pb207 Z = 82 N = 207 A = 206.98 g/mole abundance: 22.100 % - ---> Isotope: Pb208 Z = 82 N = 208 A = 207.98 g/mole abundance: 52.400 % - ElmMassFraction: 100.00 % ElmAbundance 100.00 % - - - Material: liquidArgon density: 1.390 g/cm3 RadL: 14.064 cm Nucl.Int.Length: 86.076 cm - Imean: 188.000 eV temperature: 293.15 K pressure: 1.00 atm - - ---> Element: Ar (Ar) Z = 18.0 N = 40 A = 39.948 g/mole - ---> Isotope: Ar36 Z = 18 N = 36 A = 35.97 g/mole abundance: 0.337 % - ---> Isotope: Ar38 Z = 18 N = 38 A = 37.96 g/mole abundance: 0.063 % - ---> Isotope: Ar40 Z = 18 N = 40 A = 39.96 g/mole abundance: 99.600 % - ElmMassFraction: 100.00 % ElmAbundance 100.00 % - - - Material: Galactic density: 0.000 mg/cm3 RadL: 204310098.490 pc Nucl.Int.Length: 113427284.261 pc - Imean: 19.200 eV temperature: 2.73 K pressure: 0.00 atm - - ---> Element: H (H) Z = 1.0 N = 1 A = 1.008 g/mole - ---> Isotope: H1 Z = 1 N = 1 A = 1.01 g/mole abundance: 99.989 % - ---> Isotope: H2 Z = 1 N = 2 A = 2.01 g/mole abundance: 0.011 % - ElmMassFraction: 100.00 % ElmAbundance 100.00 % - - - -Checking overlaps for volume Calorimeter:0 (G4Box) ... OK! -Checking overlaps for volume Abso:0 (G4Box) ... OK! -Checking overlaps for volume Gap:0 (G4Box) ... OK! - ------------------------------------------------------------- ----> The calorimeter is 10 layers of: [ 10mm of G4_Pb + 5mm of liquidArgon ] ------------------------------------------------------------- - - hInelastic FTFP_BERT : threshold between BERT and FTFP is over the interval - for pions : 3 to 6 GeV - for kaons : 3 to 6 GeV - for proton : 3 to 6 GeV - for neutron : 3 to 6 GeV - -### Adding tracking cuts for neutron TimeCut(ns)= 10000 KinEnergyCut(MeV)= 0 -======================================================================= -====== Electromagnetic Physics Parameters ======== -======================================================================= -LPM effect enabled 1 -Enable creation and use of sampling tables 0 -Apply cuts on all EM processes 0 -Use combined TransportationWithMsc Disabled -Use general process 1 -Enable linear polarisation for gamma 0 -Enable photoeffect sampling below K-shell 1 -Enable sampling of quantum entanglement 0 -X-section factor for integral approach 0.8 -Min kinetic energy for tables 100 eV -Max kinetic energy for tables 100 TeV -Number of bins per decade of a table 7 -Verbose level 1 -Verbose level for worker thread 0 -Bremsstrahlung energy threshold above which - primary e+- is added to the list of secondary 100 TeV -Bremsstrahlung energy threshold above which primary - muon/hadron is added to the list of secondary 100 TeV -Lowest triplet kinetic energy 1 MeV -Enable sampling of gamma linear polarisation 0 -5D gamma conversion model type 0 -5D gamma conversion model on isolated ion 0 -Livermore data directory epics_2017 -======================================================================= -====== Ionisation Parameters ======== -======================================================================= -Step function for e+- (0.2, 1 mm) -Step function for muons/hadrons (0.2, 0.1 mm) -Step function for light ions (0.2, 0.1 mm) -Step function for general ions (0.2, 0.1 mm) -Lowest e+e- kinetic energy 1 keV -Lowest muon/hadron kinetic energy 1 keV -Use ICRU90 data 0 -Fluctuations of dE/dx are enabled 1 -Type of fluctuation model for leptons and hadrons Urban -Use built-in Birks satuaration 0 -Build CSDA range enabled 0 -Use cut as a final range enabled 0 -Enable angular generator interface 0 -Max kinetic energy for CSDA tables 1 GeV -Max kinetic energy for NIEL computation 0 eV -Linear loss limit 0.01 -Read data from file for e+e- pair production by mu 0 -======================================================================= -====== Multiple Scattering Parameters ======== -======================================================================= -Type of msc step limit algorithm for e+- 1 -Type of msc step limit algorithm for muons/hadrons 0 -Msc lateral displacement for e+- enabled 1 -Msc lateral displacement for muons and hadrons 0 -Urban msc model lateral displacement alg96 1 -Range factor for msc step limit for e+- 0.04 -Range factor for msc step limit for muons/hadrons 0.2 -Geometry factor for msc step limitation of e+- 2.5 -Safety factor for msc step limit for e+- 0.6 -Skin parameter for msc step limitation of e+- 1 -Lambda limit for msc step limit for e+- 1 mm -Use Mott correction for e- scattering 0 -Factor used for dynamic computation of angular - limit between single and multiple scattering 1 -Fixed angular limit between single - and multiple scattering 3.1416 rad -Upper energy limit for e+- multiple scattering 100 MeV -Type of electron single scattering model 0 -Type of nuclear form-factor 1 -Screening factor 1 -======================================================================= - -phot: for gamma SubType=12 BuildTable=0 - LambdaPrime table from 200 keV to 100 TeV in 61 bins - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - LivermorePhElectric : Emin= 0 eV Emax= 100 TeV SauterGavrila Fluo - -compt: for gamma SubType=13 BuildTable=1 - Lambda table from 100 eV to 1 MeV, 7 bins/decade, spline: 1 - LambdaPrime table from 1 MeV to 100 TeV in 56 bins - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - Klein-Nishina : Emin= 0 eV Emax= 100 TeV - -conv: for gamma SubType=14 BuildTable=1 - Lambda table from 1.022 MeV to 100 TeV, 18 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - BetheHeitlerLPM : Emin= 0 eV Emax= 100 TeV ModifiedTsai - -Rayl: for gamma SubType=11 BuildTable=1 - Lambda table from 100 eV to 150 keV, 7 bins/decade, spline: 0 - LambdaPrime table from 150 keV to 100 TeV in 62 bins - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - LivermoreRayleigh : Emin= 0 eV Emax= 100 TeV CullenGenerator - -msc: for e- SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - UrbanMsc : Emin= 0 eV Emax= 100 MeV Nbins=42 100 eV - 100 MeV - StepLim=UseSafety Rfact=0.04 Gfact=2.5 Sfact=0.6 DispFlag:1 Skin=1 Llim=1 mm - WentzelVIUni : Emin= 100 MeV Emax= 100 TeV Nbins=42 100 MeV - 100 TeV - StepLim=UseSafety Rfact=0.04 Gfact=2.5 Sfact=0.6 DispFlag:1 Skin=1 Llim=1 mm - -eIoni: for e- XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - MollerBhabha : Emin= 0 eV Emax= 100 TeV - -eBrem: for e- XStype:4 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - LPM flag: 1 for E > 1 GeV, VertexHighEnergyTh(GeV)= 100000 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eBremSB : Emin= 0 eV Emax= 1 GeV ModifiedTsai - eBremLPM : Emin= 1 GeV Emax= 100 TeV ModifiedTsai - -CoulombScat: for e- XStype:1 SubType=1 BuildTable=1 - Lambda table from 100 MeV to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 100 MeV Emax= 100 TeV - -msc: for e+ SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - UrbanMsc : Emin= 0 eV Emax= 100 MeV Nbins=42 100 eV - 100 MeV - StepLim=UseSafety Rfact=0.04 Gfact=2.5 Sfact=0.6 DispFlag:1 Skin=1 Llim=1 mm - WentzelVIUni : Emin= 100 MeV Emax= 100 TeV Nbins=42 100 MeV - 100 TeV - StepLim=UseSafety Rfact=0.04 Gfact=2.5 Sfact=0.6 DispFlag:1 Skin=1 Llim=1 mm - -eIoni: for e+ XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - MollerBhabha : Emin= 0 eV Emax= 100 TeV - -eBrem: for e+ XStype:4 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - LPM flag: 1 for E > 1 GeV, VertexHighEnergyTh(GeV)= 100000 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eBremSB : Emin= 0 eV Emax= 1 GeV ModifiedTsai - eBremLPM : Emin= 1 GeV Emax= 100 TeV ModifiedTsai - -annihil: for e+ XStype:2 SubType=5 BuildTable=0 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eplus2gg : Emin= 0 eV Emax= 100 TeV - -CoulombScat: for e+ XStype:1 SubType=1 BuildTable=1 - Lambda table from 100 MeV to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 100 MeV Emax= 100 TeV - -msc: for proton SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for proton XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - Bragg : Emin= 0 eV Emax= 2 MeV - BetheBloch : Emin= 2 MeV Emax= 100 TeV - -hBrems: for proton XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for proton XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 17x1001 from 7.50618 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for proton XStype:1 SubType=1 BuildTable=1 - Lambda table from threshold to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for GenericIon SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - UrbanMsc : Emin= 0 eV Emax= 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -ionIoni: for GenericIon XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.02 - Stopping Power data for 17 ion/material pairs - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - BraggIon : Emin= 0 eV Emax= 2 MeV - BetheBloch : Emin= 2 MeV Emax= 100 TeV - -msc: for alpha SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - UrbanMsc : Emin= 0 eV Emax= 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -ionIoni: for alpha XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.02 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - BraggIon : Emin= 0 eV Emax=7.9452 MeV - BetheBloch : Emin=7.9452 MeV Emax= 100 TeV - -msc: for anti_proton SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for anti_proton XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - ICRU73QO : Emin= 0 eV Emax= 2 MeV - BetheBloch : Emin= 2 MeV Emax= 100 TeV - -hBrems: for anti_proton XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for anti_proton XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 17x1001 from 7.50618 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for anti_proton XStype:1 SubType=1 BuildTable=1 - Lambda table from threshold to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for kaon+ SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for kaon+ XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - Bragg : Emin= 0 eV Emax=1.05231 MeV - BetheBloch : Emin=1.05231 MeV Emax= 100 TeV - -hBrems: for kaon+ XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for kaon+ XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 18x1001 from 3.94942 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for kaon+ XStype:1 SubType=1 BuildTable=1 - Lambda table from threshold to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for kaon- SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for kaon- XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - ICRU73QO : Emin= 0 eV Emax=1.05231 MeV - BetheBloch : Emin=1.05231 MeV Emax= 100 TeV - -hBrems: for kaon- XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for kaon- XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 18x1001 from 3.94942 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for kaon- XStype:1 SubType=1 BuildTable=1 - Used Lambda table of kaon+ - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for mu+ SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -muIoni: for mu+ XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - Bragg : Emin= 0 eV Emax= 200 keV - MuBetheBloch : Emin= 200 keV Emax= 100 TeV - -muBrems: for mu+ XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - MuBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -muPairProd: for mu+ XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 21x1001 from 0.85 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - muPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for mu+ XStype:1 SubType=1 BuildTable=1 - Lambda table from threshold to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for mu- SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -muIoni: for mu- XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - ICRU73QO : Emin= 0 eV Emax= 200 keV - MuBetheBloch : Emin= 200 keV Emax= 100 TeV - -muBrems: for mu- XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - MuBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -muPairProd: for mu- XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 21x1001 from 0.85 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - muPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for mu- XStype:1 SubType=1 BuildTable=1 - Used Lambda table of mu+ - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for pi+ SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for pi+ XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - Bragg : Emin= 0 eV Emax=297.505 keV - BetheBloch : Emin=297.505 keV Emax= 100 TeV - -hBrems: for pi+ XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for pi+ XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 20x1001 from 1.11656 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for pi+ XStype:1 SubType=1 BuildTable=1 - Lambda table from threshold to 100 TeV, 7 bins/decade, spline: 0 - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -msc: for pi- SubType= 10 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - WentzelVIUni : Emin= 0 eV Emax= 100 TeV Nbins=84 100 eV - 100 TeV - StepLim=Minimal Rfact=0.2 Gfact=2.5 Sfact=0.6 DispFlag:0 Skin=1 Llim=1 mm - -hIoni: for pi- XStype:3 SubType=2 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - StepFunction=(0.2, 0.1 mm), integ: 3, fluct: 1, linLossLim= 0.01 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - ICRU73QO : Emin= 0 eV Emax=297.505 keV - BetheBloch : Emin=297.505 keV Emax= 100 TeV - -hBrems: for pi- XStype:1 SubType=3 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hBrem : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -hPairProd: for pi- XStype:1 SubType=4 - dE/dx and range tables from 100 eV to 100 TeV in 84 bins - Lambda tables from threshold to 100 TeV, 7 bins/decade, spline: 1 - Sampling table 20x1001 from 1.11656 GeV to 100 TeV - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - hPairProd : Emin= 0 eV Emax= 100 TeV ModifiedMephi - -CoulombScat: for pi- XStype:1 SubType=1 BuildTable=1 - Used Lambda table of pi+ - ThetaMin(p) < Theta(degree) < 180 pLimit(GeV^1)= 0.139531 - ===== EM models for the G4Region DefaultRegionForTheWorld ====== - eCoulombScattering : Emin= 0 eV Emax= 100 TeV - -==================================================================== - HADRONIC PROCESSES SUMMARY (verbose level 1) - ---------------------------------------------------- - Hadronic Processes for neutron - - Process: hadElastic - Model: hElasticCHIPS: 0 eV ---> 100 TeV - Cr_sctns: G4NeutronElasticXS: 0 eV ---> 100 TeV - - - Process: neutronInelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: G4NeutronInelasticXS: 0 eV ---> 100 TeV - - - Process: nCapture - Model: nRadCapture: 0 eV ---> 100 TeV - Cr_sctns: G4NeutronCaptureXS: 0 eV ---> 100 TeV - - - Process: nKiller - ---------------------------------------------------- - Hadronic Processes for B- - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: B-Inelastic - Model: FTFP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for D- - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: D-Inelastic - Model: FTFP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for GenericIon - - Process: ionInelastic - Model: Binary Light Ion Cascade: 0 eV /n ---> 6 GeV/n - Model: FTFP: 3 GeV/n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - ---------------------------------------------------- - Hadronic Processes for He3 - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - - Process: He3Inelastic - Model: Binary Light Ion Cascade: 0 eV /n ---> 6 GeV/n - Model: FTFP: 3 GeV/n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - ---------------------------------------------------- - Hadronic Processes for alpha - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - - Process: alphaInelastic - Model: Binary Light Ion Cascade: 0 eV /n ---> 6 GeV/n - Model: FTFP: 3 GeV/n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - ---------------------------------------------------- - Hadronic Processes for anti_He3 - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100.1 MeV/n - Model: AntiAElastic: 100 MeV/n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_He3Inelastic - Model: FTFP: 0 eV /n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_alpha - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100.1 MeV/n - Model: AntiAElastic: 100 MeV/n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_alphaInelastic - Model: FTFP: 0 eV /n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_deuteron - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100.1 MeV/n - Model: AntiAElastic: 100 MeV/n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_deuteronInelastic - Model: FTFP: 0 eV /n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_hypertriton - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_lambda - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: anti_lambdaInelastic - Model: FTFP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_neutron - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100.1 MeV - Model: AntiAElastic: 100 MeV ---> 100 TeV - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_neutronInelastic - Model: FTFP: 0 eV ---> 100 TeV - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_proton - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100.1 MeV - Model: AntiAElastic: 100 MeV ---> 100 TeV - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_protonInelastic - Model: FTFP: 0 eV ---> 100 TeV - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for anti_triton - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100.1 MeV/n - Model: AntiAElastic: 100 MeV/n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: anti_tritonInelastic - Model: FTFP: 0 eV /n ---> 100 TeV/n - Cr_sctns: AntiAGlauber: 0 eV ---> 25.6 PeV - - - Process: hFritiofCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for deuteron - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - - Process: dInelastic - Model: Binary Light Ion Cascade: 0 eV /n ---> 6 GeV/n - Model: FTFP: 3 GeV/n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - ---------------------------------------------------- - Hadronic Processes for e+ - - Process: positronNuclear - Model: G4ElectroVDNuclearModel: 0 eV ---> 1 PeV - Cr_sctns: ElectroNuclearXS: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for e- - - Process: electronNuclear - Model: G4ElectroVDNuclearModel: 0 eV ---> 1 PeV - Cr_sctns: ElectroNuclearXS: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for gamma - - Process: photonNuclear - Model: GammaNPreco: 0 eV ---> 200 MeV - Model: BertiniCascade: 199 MeV ---> 6 GeV - Model: TheoFSGenerator: 3 GeV ---> 100 TeV - Cr_sctns: GammaNuclearXS: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for kaon+ - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: kaon+Inelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for kaon- - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: kaon-Inelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: hBertiniCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for lambda - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: lambdaInelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for mu+ - - Process: muonNuclear - Model: G4MuonVDNuclearModel: 0 eV ---> 1 PeV - Cr_sctns: KokoulinMuonNuclearXS: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for mu- - - Process: muonNuclear - Model: G4MuonVDNuclearModel: 0 eV ---> 1 PeV - Cr_sctns: KokoulinMuonNuclearXS: 0 eV ---> 100 TeV - - - Process: muMinusCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for pi+ - - Process: hadElastic - Model: hElasticGlauber: 0 eV ---> 100 TeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - - Process: pi+Inelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for pi- - - Process: hadElastic - Model: hElasticGlauber: 0 eV ---> 100 TeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - - Process: pi-Inelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - - Process: hBertiniCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for proton - - Process: hadElastic - Model: hElasticCHIPS: 0 eV ---> 100 TeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - - Process: protonInelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: BarashenkovGlauberGribov: 0 eV ---> 100 TeV - - ---------------------------------------------------- - Hadronic Processes for sigma- - - Process: hadElastic - Model: hElasticLHEP: 0 eV ---> 100 TeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: sigma-Inelastic - Model: FTFP: 3 GeV ---> 100 TeV - Model: BertiniCascade: 0 eV ---> 6 GeV - Cr_sctns: Glauber-Gribov: 0 eV ---> 100 TeV - - - Process: hBertiniCaptureAtRest - ---------------------------------------------------- - Hadronic Processes for triton - - Process: hadElastic - Model: hElasticLHEP: 0 eV /n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - - Process: tInelastic - Model: Binary Light Ion Cascade: 0 eV /n ---> 6 GeV/n - Model: FTFP: 3 GeV/n ---> 100 TeV/n - Cr_sctns: Glauber-Gribov Nucl-nucl: 0 eV ---> 25.6 PeV - - -================================================================ -======================================================================= -====== Pre-compound/De-excitation Physics Parameters ======== -======================================================================= -Type of pre-compound inverse x-section 3 -Pre-compound model active 1 -Pre-compound excitation low energy 100 keV -Pre-compound excitation high energy 30 MeV -Type of de-excitation inverse x-section 3 -Type of de-excitation factory Evaporation+GEM -Number of de-excitation channels 68 -Min excitation energy 10 eV -Min energy per nucleon for multifragmentation 200 GeV -Limit excitation energy for Fermi BreakUp 20 MeV -Level density (1/MeV) 0.075 -Use simple level density model 1 -Use discrete excitation energy of the residual 1 -Time limit for long lived isomeres 1 ns -Isomer production flag 1 -Internal e- conversion flag 1 -Store e- internal conversion data 0 -Correlated gamma emission flag 0 -Max 2J for sampling of angular correlations 10 -======================================================================= -G4VisManager: Using G4TrajectoryDrawByCharge as fallback trajectory model. -See commands in /vis/modeling/trajectories/ for other options. -### Run 0 starts. - --------- WWWW ------- G4Exception-START -------- WWWW ------- -*** G4Exception : Analysis_W001 - issued by : G4RootNtupleFileManager::SetNtupleMergingMode -Merging ntuples is not applicable in sequential application. -Setting was ignored. -*** This is just a warning message. *** --------- WWWW -------- G4Exception-END --------- WWWW ------- - -... set ntuple merging row mode : row-wise - done -... create file : B4.root - done -... open analysis file : B4.root - done -... open analysis file : B4.root - done -Using ---> Event 0 starts. ----> End of event: 0 - Absorber: total energy: 259.894 MeV total track length: 18.9569 cm - Gap: total energy: 17.4244 MeV total track length: 8.74398 cm - - ----> print histograms statistic for the entire run - - EAbs : mean = 259.894 MeV rms = 0 eV - EGap : mean = 17.4244 MeV rms = 0 eV - LAbs : mean = 18.9569 cm rms = 0 fm - LGap : mean = 8.74398 cm rms = 0 fm -... write file : B4.root - done -... close file : B4.root - done -There are 4 h1 histograms - 0 with 0 entries: Edep in absorber - 1 with 0 entries: Edep in gap - 2 with 0 entries: trackL in absorber - 3 with 0 entries: trackL in gap -List them with "/analysis/list". -View them with "/vis/plot" or "/vis/reviewPlots". - Transportation, GammaGeneralProc, msc, eIoni - eBrem, CoulombScat, msc, eIoni - eBrem, annihil, CoulombScat, msc - ionIoni, msc, muIoni, muBrems - muPairProd, CoulombScat, muIoni, msc - hIoni, hBrems, hPairProd, CoulombScat - hIoni, msc, hIoni, hBrems - hPairProd, CoulombScat, hIoni, msc - hIoni, hBrems, hPairProd, CoulombScat - msc, hIoni, CoulombScat, hIoni - hIoni, msc, ionIoni, msc - ionIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, hIoni - hIoni, hIoni, hIoni, electronNuclear - positronNuclear, muonNuclear, Decay, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, hadElastic - hadElastic, hadElastic, hadElastic, neutronInelastic - nCapture, protonInelastic, pi+Inelastic, pi-Inelastic - kaon+Inelastic, kaon-Inelastic, kaon0LInelastic, kaon0SInelastic -anti_protonInelastic,anti_neutronInelastic,anti_deuteronInelastic,anti_tritonInelastic - anti_He3Inelastic,anti_alphaInelastic, lambdaInelastic, sigma+Inelastic - sigma-Inelastic, xi0Inelastic, xi-Inelastic, omega-Inelastic -anti_lambdaInelastic,anti_sigma+Inelastic,anti_sigma-Inelastic, anti_xi0Inelastic - anti_xi-Inelastic,anti_omega-Inelastic, D+Inelastic, D0Inelastic - D-Inelastic, anti_D0Inelastic, Ds+Inelastic, Ds-Inelastic - B+Inelastic, B0Inelastic, B-Inelastic, anti_B0Inelastic - Bs0Inelastic, anti_Bs0Inelastic, Bc+Inelastic, Bc-Inelastic - lambda_c+Inelastic, xi_c+Inelastic, xi_c0Inelastic, omega_c0Inelastic - lambda_bInelastic, xi_b0Inelastic, xi_b-Inelastic, omega_b-Inelastic -anti_lambda_c+Inelastic,anti_xi_c+Inelastic,anti_xi_c0Inelastic,anti_omega_c0Inelastic -anti_lambda_bInelastic,anti_xi_b0Inelastic,anti_xi_b-Inelastic,anti_omega_b-Inelastic -hFritiofCaptureAtRest,hBertiniCaptureAtRest,muMinusCaptureAtRest, dInelastic - tInelastic, He3Inelastic, alphaInelastic, ionInelastic - nKiller -### Run 1 starts. -... create file : B4.root - done -... open analysis file : B4.root - done -... open analysis file : B4.root - done -Using ---> Event 0 starts. ----> End of event: 0 - Absorber: total energy: 278.136 MeV total track length: 20.1737 cm - Gap: total energy: 19.9095 MeV total track length: 10.0917 cm - - ----> print histograms statistic for the entire run - - EAbs : mean = 278.136 MeV rms = 0 eV - EGap : mean = 19.9095 MeV rms = 0 eV - LAbs : mean = 20.1737 cm rms = 0 fm - LGap : mean = 10.0917 cm rms = 0 fm -... write file : B4.root - done -... close file : B4.root - done -There are 4 h1 histograms - 0 with 0 entries: Edep in absorber - 1 with 0 entries: Edep in gap - 2 with 0 entries: trackL in absorber - 3 with 0 entries: trackL in gap -List them with "/analysis/list". -View them with "/vis/plot" or "/vis/reviewPlots". -### Run 2 starts. -... create file : B4.root - done -... open analysis file : B4.root - done -... open analysis file : B4.root - done -Using ---> Event 0 starts. ----> End of event: 0 - Absorber: total energy: 435.043 MeV total track length: 31.1955 cm - Gap: total energy: 37.3961 MeV total track length: 18.5223 cm - - ----> print histograms statistic for the entire run - - EAbs : mean = 435.043 MeV rms = 0 eV - EGap : mean = 37.3961 MeV rms = 0 eV - LAbs : mean = 31.1955 cm rms = 0 fm - LGap : mean = 18.5223 cm rms = 0 fm -... write file : B4.root - done -... close file : B4.root - done -There are 4 h1 histograms - 0 with 0 entries: Edep in absorber - 1 with 0 entries: Edep in gap - 2 with 0 entries: trackL in absorber - 3 with 0 entries: trackL in gap -List them with "/analysis/list". -View them with "/vis/plot" or "/vis/reviewPlots". -Graphics systems deleted. -Visualization Manager deleting... diff --git a/gui.mac b/gui.mac deleted file mode 100644 index d7bb656..0000000 --- a/gui.mac +++ /dev/null @@ -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 -# diff --git a/init_vis.mac b/init_vis.mac deleted file mode 100644 index bcf4ae0..0000000 --- a/init_vis.mac +++ /dev/null @@ -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 diff --git a/plotHisto.C b/plotHisto.C deleted file mode 100644 index 868a742..0000000 --- a/plotHisto.C +++ /dev/null @@ -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"); -} diff --git a/plotNtuple.C b/plotNtuple.C deleted file mode 100644 index 086bbd4..0000000 --- a/plotNtuple.C +++ /dev/null @@ -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"); -} diff --git a/run1.mac b/run1.mac deleted file mode 100644 index 1898371..0000000 --- a/run1.mac +++ /dev/null @@ -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 -# diff --git a/run2.mac b/run2.mac deleted file mode 100644 index 35e16a6..0000000 --- a/run2.mac +++ /dev/null @@ -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 - diff --git a/vis.mac b/vis.mac deleted file mode 100644 index a4df708..0000000 --- a/vis.mac +++ /dev/null @@ -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 From c11ecf51de45cc2bc24a12f9cb238234cd07f100 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 11:07:58 +0200 Subject: [PATCH 22/33] update geant4 submodule: fix circular dep in track ID pre-assignment Co-Authored-By: Claude Sonnet 4.6 --- lib/geant4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/geant4 b/lib/geant4 index e7827c9..71c7582 160000 --- a/lib/geant4 +++ b/lib/geant4 @@ -1 +1 @@ -Subproject commit e7827c9b07aa6788f37e70658302945d6f129c70 +Subproject commit 71c7582c45c8b7624ef7be16288349c056b1a6dd From 38a0273087b38fdda2e7dbe8b61007dcc6688628 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 11:10:44 +0200 Subject: [PATCH 23/33] link run_pbwo4 to build root in both build modes Superbuild: install step creates build/run_pbwo4 symlink pointing at the executable in build/minicalosim-build/. Regular build: CMAKE_RUNTIME_OUTPUT_DIRECTORY ensures the executable lands in build/ regardless of generator. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 2 ++ superbuild/CMakeLists.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0133e5..381d34d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,8 @@ target_link_libraries(minicalo PUBLIC ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) #---------------------------------------------------------------------------- # Add the executable, and link it to the Geant4 and Python libraries # +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers}) target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt index 4a64abe..20b8514 100644 --- a/superbuild/CMakeLists.txt +++ b/superbuild/CMakeLists.txt @@ -43,6 +43,8 @@ ExternalProject_Add(run_pbwo4 -DCMAKE_PREFIX_PATH=${G4_INSTALL} -DWITH_GEANT4_UIVIS=OFF BUILD_COMMAND cmake --build --target run_pbwo4 --parallel ${NPROC} - INSTALL_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_BINARY_DIR}/minicalosim-build/run_pbwo4 + ${CMAKE_BINARY_DIR}/run_pbwo4 DEPENDS geant4 ) From 2f74ee0637bf57c72e1ddb7e26fb9d0b8dae8f43 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 11 Jun 2026 11:15:11 +0200 Subject: [PATCH 24/33] rewrite README with full project and Geant4 modification docs Co-Authored-By: Claude Sonnet 4.6 --- README.md | 292 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 273 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index a3e64bb..a887aad 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,285 @@ # 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. -## 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 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 +``` + +--- + +## 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_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. From 26821af188e3102ff9bbb26c6cdcb0a8993a1a50 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Fri, 12 Jun 2026 13:06:51 +0200 Subject: [PATCH 25/33] replace Dockerfile-mini with new superbuild-based Dockerfile Switches from the old per-component Dockerfile-mini to a unified Dockerfile that uses the superbuild CMake setup, Ubuntu 24.04, uv for Python dependency management, and pyproject.toml for the Python environment. Co-Authored-By: Claude Sonnet 4.6 --- docker/Dockerfile | 68 ++++++++++++++++++++++++++++++++++++++++++ docker/Dockerfile-mini | 68 ------------------------------------------ docker/pyproject.toml | 21 +++++++++++++ 3 files changed, 89 insertions(+), 68 deletions(-) create mode 100644 docker/Dockerfile delete mode 100644 docker/Dockerfile-mini create mode 100644 docker/pyproject.toml diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..756d9b4 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,68 @@ +FROM ubuntu:24.04 + +ARG DEBIAN_FRONTEND=noninteractive + +# --------------------------------------------------------------------------- +# 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}" + +WORKDIR /src + +# --------------------------------------------------------------------------- +# 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 . + +# 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 + +# --------------------------------------------------------------------------- +# Build Geant4 + miniCaloSim +# --------------------------------------------------------------------------- +RUN cmake -S superbuild -B build -G Ninja \ + -DGEANT4_INSTALL_DATA=ON + +RUN cmake --build build --parallel + +# --------------------------------------------------------------------------- +# Python environment (uv) +# --------------------------------------------------------------------------- +RUN uv venv /opt/venv + +ENV PATH="/opt/venv/bin:${PATH}" + +ADD docker/pyproject.toml pyproject.toml + +RUN uv sync + +WORKDIR /workspace + +ENTRYPOINT ["/bin/bash", "-l"] \ No newline at end of file diff --git a/docker/Dockerfile-mini b/docker/Dockerfile-mini deleted file mode 100644 index 7a45b7c..0000000 --- a/docker/Dockerfile-mini +++ /dev/null @@ -1,68 +0,0 @@ -FROM ubuntu:22.04 - -SHELL ["/bin/bash", "-c"] - -USER root -ENV DEBIAN_FRONTEND=noninteractive - -# Build tools + minimal Python -RUN apt-get update -y && apt-get install -y \ - python3 python3-dev python3-pip \ - cmake g++ gcc binutils \ - libssl-dev wget git \ - && rm -rf /var/lib/apt/lists/* - -RUN python3 -m pip install --upgrade pip && \ - python3 -m pip install numpy pandas uproot awkward plotly ipython && \ - ln -sf /usr/bin/python3 /usr/bin/python - -# Geant4 — build from submodule source (batch mode only, no visualization, no multithreading) -ADD minicalosim/lib/geant4 /tmp/geant4-src - -RUN rm -f /tmp/geant4-src/.git && \ - cmake \ - -S /tmp/geant4-src \ - -B /tmp/geant4-build \ - -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 \ - -DCMAKE_RULE_MESSAGES=OFF \ - -DGEANT4_INSTALL_DATA=ON \ - -DGEANT4_BUILD_MULTITHREADED=OFF \ - -DGEANT4_BUILD_TLS_MODEL=global-dynamic \ - > /dev/null && \ - cmake --build /tmp/geant4-build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) > /dev/null && \ - cmake --install /tmp/geant4-build > /dev/null && \ - rm -rf /tmp/geant4-src /tmp/geant4-build - -ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib" - -# Build minicalo -ARG BUILD_DATE -ARG COMMIT -ARG USER -LABEL org.label-schema.build-date=$BUILD_DATE - -ADD minicalosim /root/minicalosim - -RUN cd /root/minicalosim && git checkout $COMMIT && \ - git submodule update --init lib/pybind11 && \ - cmake \ - -S . -B build \ - -DWITH_GEANT4_UIVIS=OFF \ - > /dev/null && \ - cmake --build build --parallel $(( $(nproc) < 16 ? $(nproc) : 16 )) && \ - cp build/minicalo*.so /usr/local/lib/python3.10/dist-packages/ - -RUN echo "commit = '${COMMIT}'" > /root/minicalosim/bind/minicalo_version.py && \ - cp /root/minicalosim/bind/G4Calo.py \ - /root/minicalosim/bind/minicalo_tools.py \ - /root/minicalosim/bind/minipandas.py \ - /root/minicalosim/bind/minicalo_version.py \ - /usr/local/lib/python3.10/dist-packages/ && \ - install -m 755 /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/G4Calo_exec.py - -# Copy examples -RUN cp -r /root/minicalosim/bind /examples && \ - rm -rf /root/minicalosim - -WORKDIR /examples -CMD ["bash"] diff --git a/docker/pyproject.toml b/docker/pyproject.toml new file mode 100644 index 0000000..89a6ab0 --- /dev/null +++ b/docker/pyproject.toml @@ -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", +] From 0adc896bce498b64e83c2f0b543a742b740ae7de Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 17 Jun 2026 14:40:05 +0200 Subject: [PATCH 26/33] add post-step momentum direction to Steps ntuple Pre-step direction was already recorded; post_dx/dy/dz lets students compare momentum direction before and after each step. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 1 + src/RunAction.cc | 3 +++ src/SteppingAction.cc | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/README.md b/README.md index a887aad..8d2f147 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ One row per Geant4 step. Every step of every track of every event is recorded. | `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) | diff --git a/src/RunAction.cc b/src/RunAction.cc index 2d7507f..4e00ef5 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -111,6 +111,9 @@ RunAction::RunAction() 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->FinishNtuple(); // Spawning ntuple (one row per secondary born, ntuple id=2) diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index cb4146f..a40b6c4 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -143,6 +143,12 @@ void SteppingAction::UserSteppingAction(const G4Step* step) 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()); + // 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(); From 942130c667c3c0e736d782cff4cae373db095831 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 17 Jun 2026 14:50:34 +0200 Subject: [PATCH 27/33] add run-at-etp.sh for batch pbwo4 production runs Builds and runs run_pbwo4 ten times with 10k events each, then moves the renamed outputs to /ceph/lbogner/geant_steps/. Co-Authored-By: Claude Sonnet 4.6 --- run-at-etp.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 run-at-etp.sh diff --git a/run-at-etp.sh b/run-at-etp.sh new file mode 100755 index 0000000..ff264e4 --- /dev/null +++ b/run-at-etp.sh @@ -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/ From 11d01d701501aac0a6fc59670e6f7bbe86499b08 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 24 Jun 2026 09:45:31 +0200 Subject: [PATCH 28/33] Add run_sampling executable for sampling calorimeter geometries Mirrors run_pbwo4 but alternates thin absorber and active layers, with four selectable configs (by name or 1-based index): pb_scint, fe_scint, w_scint_ecal (homogeneous ECAL-like preshower + W/scint sampling section), and pb_lar. Each is sized to reach ~20-25 X0 of absorber despite the dilution from inactive gaps. Wired into the top-level CMakeLists, superbuild, and Dockerfile alongside run_pbwo4. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 3 + README.md | 45 +++++++++++--- docker/Dockerfile | 1 + run_sampling.cc | 124 ++++++++++++++++++++++++++++++++++++++ superbuild/CMakeLists.txt | 16 +++++ 5 files changed, 180 insertions(+), 9 deletions(-) create mode 100644 run_sampling.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 381d34d..b6ce8ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers}) target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) +add_executable(run_sampling run_sampling.cc ${sources} ${headers}) +target_link_libraries(run_sampling ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) + #---------------------------------------------------------------------------- # Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX # diff --git a/README.md b/README.md index 8d2f147..0634da7 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ A Geant4-based calorimeter simulator for teaching and research. Supports arbitra 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. -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. +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. --- ## Repository layout ``` -run_pbwo4.cc main executable entry point +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 @@ -69,22 +70,23 @@ Requires a Geant4 ≥ 11 installation visible to CMake (e.g. via `Geant4_DIR` or ```bash cmake -B build -S . cmake --build build --parallel $(nproc) -# executable: build/run_pbwo4 +# 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. Three targets are available: +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 -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 +cmake --build build --target geant4 # compile Geant4 (~30–60 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 executable is at `build/run_pbwo4` (symlink in the superbuild case). +After the build, the executables are at `build/run_pbwo4` and `build/run_sampling` (symlinks in the superbuild case). **Options:** @@ -110,6 +112,30 @@ Output file name: `pbwo4_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__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 ~20–25 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 @@ -195,6 +221,7 @@ A few material choices available via NIST names: | `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) diff --git a/docker/Dockerfile b/docker/Dockerfile index 756d9b4..f34f7c3 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -38,6 +38,7 @@ COPY include/ include/ COPY bind/ bind/ COPY lib/ lib/ COPY run_pbwo4.cc . +COPY run_sampling.cc . # 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 diff --git a/run_sampling.cc b/run_sampling.cc new file mode 100644 index 0000000..1f329c6 --- /dev/null +++ b/run_sampling.cc @@ -0,0 +1,124 @@ +#include "GeometryDescriptor.hh" +#include "G4System.hh" + +#include +#include +#include +#include +#include + +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> kConfigs = { + {"pb_scint", buildPbScint}, + {"fe_scint", buildFeScint}, + {"w_scint_ecal", buildWScintEcal}, + {"pb_lar", buildPbLAr}, +}; + +void printUsage() { + std::cerr << "Usage: run_sampling [configName|configIndex] [nEvents]" << 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; + + if (argc > 3) { + 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; + } + } + + GeometryDescriptor (*builder)() = nullptr; + try { + std::size_t pos = 0; + int index = std::stoi(configName, &pos); + if (pos == configName.size() && index >= 1 && index <= static_cast(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; + } + + GeometryDescriptor gd = builder(); + + std::string outfile = "sampling_" + configName + "_" + std::to_string(nEvents) + "events_hits.root"; + + G4System g4; + g4.init(gd, -1); + g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); + + std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; + return 0; +} diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt index 20b8514..4d58e14 100644 --- a/superbuild/CMakeLists.txt +++ b/superbuild/CMakeLists.txt @@ -48,3 +48,19 @@ ExternalProject_Add(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 --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 +) From 2715bc8d0dd098732e39c39307eb6318829d6539 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 24 Jun 2026 19:24:35 +0200 Subject: [PATCH 29/33] Add next_volume/next_material columns and sampling batch script Records the volume and material a track is about to enter when a step ends at a geometric boundary, since post-step's physical volume still refers to the volume the step occurred in. Also adds run-sampling-batch.sh to run all run_sampling configs in parallel from isolated directories, avoiding output filename collisions. Co-Authored-By: Claude Sonnet 4.6 --- run-sampling-batch.sh | 40 ++++++++++++++++++++++++++++++++++++++++ src/RunAction.cc | 2 ++ src/SteppingAction.cc | 16 ++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100755 run-sampling-batch.sh diff --git a/run-sampling-batch.sh b/run-sampling-batch.sh new file mode 100755 index 0000000..12def56 --- /dev/null +++ b/run-sampling-batch.sh @@ -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" diff --git a/src/RunAction.cc b/src/RunAction.cc index 4e00ef5..ac7eb5f 100644 --- a/src/RunAction.cc +++ b/src/RunAction.cc @@ -114,6 +114,8 @@ RunAction::RunAction() 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) diff --git a/src/SteppingAction.cc b/src/SteppingAction.cc index a40b6c4..c395a7f 100644 --- a/src/SteppingAction.cc +++ b/src/SteppingAction.cc @@ -149,6 +149,22 @@ void SteppingAction::UserSteppingAction(const G4Step* step) 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(); From 62945c95c453a2e24525d5370bdd0a2a62531a34 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 25 Jun 2026 16:53:23 +0200 Subject: [PATCH 30/33] Add export_xsec executable for PbWO4 gamma attenuation coefficients Builds a minimal PbWO4 geometry, forces EM physics tables via a zero-event run, then sweeps photon energy 1 eV-10 MeV with G4EmCalculator to dump per-process mass attenuation coefficients to CSV. --- CMakeLists.txt | 3 ++ export_xsec.cc | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 export_xsec.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b6ce8ad..bbe7a56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,9 @@ target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES}) 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 # diff --git a/export_xsec.cc b/export_xsec.cc new file mode 100644 index 0000000..3b45fbf --- /dev/null +++ b/export_xsec.cc @@ -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 +#include +#include +#include + +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 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 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; +} From 6a3aabf772cd6f8b0c9d6bbdf2d0e071e7a94f6e Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Wed, 1 Jul 2026 12:48:54 +0200 Subject: [PATCH 31/33] Allow disabling of GDML so there is no dependency on xerces-c --- README.md | 1 + superbuild/CMakeLists.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0634da7..dfdafa0 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ After the build, the executables are at `build/run_pbwo4` and `build/run_samplin | 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. diff --git a/superbuild/CMakeLists.txt b/superbuild/CMakeLists.txt index 4d58e14..5454a5d 100644 --- a/superbuild/CMakeLists.txt +++ b/superbuild/CMakeLists.txt @@ -18,6 +18,7 @@ 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 @@ -28,7 +29,7 @@ ExternalProject_Add(geant4 -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=Release -DGEANT4_INSTALL_DATA=${GEANT4_INSTALL_DATA} - -DGEANT4_USE_GDML=ON + -DGEANT4_USE_GDML=${GEANT4_USE_GDML} -DGEANT4_BUILD_MULTITHREADED=OFF -DGEANT4_BUILD_TLS_MODEL=global-dynamic BUILD_COMMAND cmake --build --parallel ${NPROC} From 068cf93604d5866000e16b44e5ab50ea13df64a0 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Thu, 9 Jul 2026 10:04:47 +0200 Subject: [PATCH 32/33] Allow MINICALOSIM_SEED env var to override RNG seed in run_pbwo4/run_sampling Concurrent job launches can land in the same wall-clock second, causing G4System::init's time-based seed (seed=-1) to collide and produce byte-identical physics across separately-named shards. Falls back to the existing time-based seed if the variable is unset, empty, or invalid. Co-Authored-By: Claude Sonnet 5 --- run_pbwo4.cc | 15 ++++++++++++++- run_sampling.cc | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/run_pbwo4.cc b/run_pbwo4.cc index 1754eb1..22e3895 100644 --- a/run_pbwo4.cc +++ b/run_pbwo4.cc @@ -1,6 +1,7 @@ #include "GeometryDescriptor.hh" #include "G4System.hh" +#include #include #include #include @@ -8,6 +9,18 @@ int main(int argc, char** argv) { int nEvents = 10; + 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 > 2) { std::cerr << "Usage: run_pbwo4 [nEvents]" << std::endl; return 1; @@ -28,7 +41,7 @@ int main(int argc, char** argv) { gd.addLayer(20.0, "G4_PbWO4", true, 10, 10); G4System g4; - g4.init(gd, -1); + g4.init(gd, seed); g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; diff --git a/run_sampling.cc b/run_sampling.cc index 1f329c6..b2da2b1 100644 --- a/run_sampling.cc +++ b/run_sampling.cc @@ -1,6 +1,7 @@ #include "GeometryDescriptor.hh" #include "G4System.hh" +#include #include #include #include @@ -111,12 +112,24 @@ int main(int argc, char** argv) { 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, -1); + g4.init(gd, seed); g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; From 14cfc18369c7606f79309eb4997f2866c3059fd7 Mon Sep 17 00:00:00 2001 From: Lars Bogner Date: Mon, 13 Jul 2026 13:58:54 +0200 Subject: [PATCH 33/33] Add optional primary particle energy argument to dataset scripts run_pbwo4 and run_sampling previously hardcoded 1 GeV mono-energetic e- primaries. Both now accept a trailing energy_GeV CLI argument (default 1.0, unchanged) so datasets at other energies can be produced without recompiling. --- run_pbwo4.cc | 18 ++++++++++++++---- run_sampling.cc | 18 ++++++++++++++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/run_pbwo4.cc b/run_pbwo4.cc index 22e3895..c6b11e6 100644 --- a/run_pbwo4.cc +++ b/run_pbwo4.cc @@ -8,6 +8,7 @@ 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")) { @@ -21,11 +22,11 @@ int main(int argc, char** argv) { } } - if (argc > 2) { - std::cerr << "Usage: run_pbwo4 [nEvents]" << std::endl; + if (argc > 3) { + std::cerr << "Usage: run_pbwo4 [nEvents] [energy_GeV]" << std::endl; return 1; } - if (argc == 2) { + if (argc >= 2) { try { nEvents = std::stoi(argv[1]); if (nEvents <= 0) throw std::invalid_argument("must be positive"); @@ -34,6 +35,15 @@ int main(int argc, char** argv) { 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"; @@ -42,7 +52,7 @@ int main(int argc, char** argv) { G4System g4; g4.init(gd, seed); - g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); + g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile); std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; return 0; diff --git a/run_sampling.cc b/run_sampling.cc index b2da2b1..53c1c7b 100644 --- a/run_sampling.cc +++ b/run_sampling.cc @@ -57,7 +57,7 @@ const std::vector> kConfigs = { }; void printUsage() { - std::cerr << "Usage: run_sampling [configName|configIndex] [nEvents]" << std::endl; + 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; @@ -69,15 +69,16 @@ void printUsage() { int main(int argc, char** argv) { std::string configName = kConfigs.front().first; int nEvents = 10; + double energy_GeV = 1.0; - if (argc > 3) { + if (argc > 4) { printUsage(); return 1; } if (argc >= 2) { configName = argv[1]; } - if (argc == 3) { + if (argc >= 3) { try { nEvents = std::stoi(argv[2]); if (nEvents <= 0) throw std::invalid_argument("must be positive"); @@ -86,6 +87,15 @@ int main(int argc, char** argv) { 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 { @@ -130,7 +140,7 @@ int main(int argc, char** argv) { G4System g4; g4.init(gd, seed); - g4.run_batch(nEvents, {"e-"}, 1.0, 1.0, outfile); + g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile); std::cout << "Saved " << nEvents << " events to " << outfile << std::endl; return 0;