Add per-step Spawning ntuple, Geant4 submodule superbuild, and standalone dataset executables #1
@@ -2,3 +2,6 @@
|
||||
path = lib/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "lib/geant4"]
|
||||
path = lib/geant4
|
||||
url = git@gitlab.etp.kit.edu:lbogner/geant4.git
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
miniCaloSim is a Geant4-based calorimeter simulator designed for teaching. It exposes a Python API via pybind11 so students can define calorimeter geometries, run batch simulations, and visualise results — all from a Jupyter notebook.
|
||||
|
||||
## Build
|
||||
|
||||
Requires Geant4 (v11.1.2), pybind11 (submodule at `lib/pybind11`), and Python 3 development headers.
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
This produces two artifacts:
|
||||
- `build/minicalo.so` — the pybind11 Python module
|
||||
- `build/exampleB4a` — a standalone Geant4 executable (for reference only)
|
||||
|
||||
After building, copy the Python files to the Python path for use outside the `bind/` directory (the Dockerfile shows the canonical install paths):
|
||||
```bash
|
||||
cp build/minicalo*.so bind/G4Calo.py bind/minicalo_tools.py bind/minipandas.py /target/python/path/
|
||||
cp bind/G4Calo_exec.py /usr/local/bin/
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the minipandas unit tests (no Geant4 required):
|
||||
```bash
|
||||
cd bind && python3 test_minipandas.py
|
||||
```
|
||||
|
||||
Run the TempFileManager unit tests:
|
||||
```bash
|
||||
cd bind && python3 -m unittest minicalo_tools.TestTempFileManager
|
||||
```
|
||||
|
||||
There are no automated tests for the full simulation — student usage is the primary validation path.
|
||||
|
||||
## Architecture
|
||||
|
||||
### C++ / Geant4 layer (`src/`, `include/`)
|
||||
|
||||
The simulation is a standard Geant4 application derived from example B4a. The key extension is that geometry is driven externally via `GeometryDescriptor` rather than hardcoded:
|
||||
|
||||
- **`GeometryDescriptor`** (`include/GeometryDescriptor.hh`) — the central geometry object. Holds a list of `Layer` objects, each of which contains `Sensor` objects after construction. Sensors accumulate deposited energy during a run. Fully pickle-able via `__getstate__`/`__setstate__`.
|
||||
- **`Layer`** — describes one slab: material (NIST name), thickness (cm), active/passive, and an `nx × ny` sensor grid.
|
||||
- **`Sensor`** — stores position, size, and `mutable energy` (reset between events via `GeometryDescriptor::resetSensorEnergies()`).
|
||||
- **`DetectorConstruction`** (`src/DetectorConstruction.cc`) — reads `GeometryDescriptor` and builds Geant4 volumes. Uses `G4PVParameterised` + `LayerParametrisation` for the sensor grid. Active sensors have their `G4VPhysicalVolume*` assigned back into the `Layer`, enabling lookup in `SteppingAction`.
|
||||
- **`SteppingAction`** — on each step, maps the physical volume back to a `Sensor` via `GeometryDescriptor::getSensorByVolume()` and accumulates energy.
|
||||
- **`RunAction`** — writes a ROOT ntuple named `"Hits"` with per-event scalars (`true_energy`, `total_dep_energy`, `N_layers`, ...) and per-sensor arrays (`sensor_energy`, `sensor_x/y/z`, `sensor_dx/dy/dz`, `sensor_layer`, `sensor_copy_number`).
|
||||
- **`G4System`** (`include/G4System.hh`) — owns the Geant4 run manager lifetime. `init(GeometryDescriptor&, seed)` sets everything up; `run_batch(...)` fires events to a ROOT file. One `G4System` per process — instantiated inside the subprocess (`G4Calo_exec.py`) to avoid Geant4 singleton issues.
|
||||
|
||||
### pybind11 bindings (`bind/bindings.cpp`)
|
||||
|
||||
Exposes `Sensor`, `Layer`, `GeometryDescriptor`, and `G4System` to Python as the `minicalo` module.
|
||||
|
||||
### Python layer (`bind/`)
|
||||
|
||||
- **`G4Calo.py`** — the public student API. `run_batch()` parallelises events across CPU cores using `ThreadPoolExecutor`; each worker subprocess calls `G4Calo_exec.py`, which instantiates a fresh `G4System` to avoid Geant4 singleton issues. Results are written as temporary ROOT files in `/dev/shm` (fallback: `/tmp`), read back with `uproot`/`awkward`, and merged into a `MiniFrame`. `display_event()` runs a single event and renders a 3D Plotly figure.
|
||||
- **`G4Calo_exec.py`** — the worker subprocess entry point. Reads a pickled `GeometryDescriptor` + parameters, runs the simulation, writes a ROOT file, and returns the path. Must be on `$PATH` as an executable.
|
||||
- **`minicalo_tools.py`** — `TempFileManager`: context manager for `/dev/shm`-backed pickle IPC files between the main process and worker subprocesses.
|
||||
- **`minipandas.py`** — `MiniFrame`: a minimal NumPy-backed dataframe. Supports scalar columns `(N,)` and fixed-size vector columns `(N, M)`. Converts to `pandas.DataFrame` via `to_pandas()` and serialises with `to_pickle()`/`read_pickle()`.
|
||||
|
||||
### Subprocess isolation pattern
|
||||
|
||||
Geant4 uses global singletons that cannot be re-initialised within a process. Therefore, each simulation batch (even in "single-core" mode) runs in a fresh subprocess spawned by `G4Calo_exec.py`. Communication uses pickle files via `TempFileManager`; ROOT output files are used for event data.
|
||||
|
||||
### Coordinate conventions
|
||||
|
||||
Geant4 internal units are mm; sensor positions stored in `Sensor` are in Geant4 internal units. The Plotly visualisation multiplies layer dimensions by 10 to convert from cm to mm and labels axes with `[mm]`.
|
||||
|
||||
## Docker
|
||||
|
||||
`docker/Dockerfile` builds a JupyterHub-based image with Geant4 v11.1.2 (multithreading disabled, `GEANT4_BUILD_MULTITHREADED=OFF`), the minicalo package, and ML dependencies (PyTorch, PyG). `docker/Dockerfile-cpu` is the CPU-only variant.
|
||||
+9
-26
@@ -55,35 +55,18 @@ target_link_libraries(minicalo PUBLIC ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||
#----------------------------------------------------------------------------
|
||||
# Add the executable, and link it to the Geant4 and Python libraries
|
||||
#
|
||||
add_executable(exampleB4a exampleB4a.cc ${sources} ${headers})
|
||||
target_link_libraries(exampleB4a ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# Copy all scripts to the build directory, i.e. the directory in which we
|
||||
# build B4a. This is so that we can run the executable directly because it
|
||||
# relies on these scripts being in the current working directory.
|
||||
#
|
||||
set(EXAMPLEB4A_SCRIPTS
|
||||
exampleB4a.out
|
||||
exampleB4.in
|
||||
gui.mac
|
||||
init_vis.mac
|
||||
plotHisto.C
|
||||
plotNtuple.C
|
||||
run1.mac
|
||||
run2.mac
|
||||
vis.mac
|
||||
)
|
||||
add_executable(run_pbwo4 run_pbwo4.cc ${sources} ${headers})
|
||||
target_link_libraries(run_pbwo4 ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||
|
||||
foreach(_script ${EXAMPLEB4A_SCRIPTS})
|
||||
configure_file(
|
||||
${PROJECT_SOURCE_DIR}/${_script}
|
||||
${PROJECT_BINARY_DIR}/${_script}
|
||||
COPYONLY
|
||||
)
|
||||
endforeach()
|
||||
add_executable(run_sampling run_sampling.cc ${sources} ${headers})
|
||||
target_link_libraries(run_sampling ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||
|
||||
add_executable(export_xsec export_xsec.cc ${sources} ${headers})
|
||||
target_link_libraries(export_xsec ${Geant4_LIBRARIES} ${Python3_LIBRARIES})
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX
|
||||
#
|
||||
#install(TARGETS exampleB4a DESTINATION bin)
|
||||
#install(TARGETS run_pbwo4 DESTINATION bin)
|
||||
|
||||
-21
@@ -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_*
|
||||
|
||||
@@ -1,31 +1,314 @@
|
||||
# miniCaloSim
|
||||
|
||||
A Geant4-based calorimeter simulator for teaching and research. Supports arbitrary sampling or homogeneous calorimeter geometries, runs batches of particle showers, and writes ROOT output for analysis.
|
||||
|
||||
---
|
||||
|
||||
## Getting started
|
||||
## Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Repository layout](#repository-layout)
|
||||
- [Build](#build)
|
||||
- [Option A — system Geant4](#option-a--system-geant4)
|
||||
- [Option B — build Geant4 from submodule](#option-b--build-geant4-from-submodule)
|
||||
- [Running](#running)
|
||||
- [ROOT output format](#root-output-format)
|
||||
- [Hits tree](#hits-tree)
|
||||
- [Steps tree](#steps-tree)
|
||||
- [Spawning tree](#spawning-tree)
|
||||
- [Calorimeter materials](#calorimeter-materials)
|
||||
- [Docker](#docker)
|
||||
- [Geant4 modifications](#geant4-modifications)
|
||||
|
||||
### Materials
|
||||
---
|
||||
|
||||
Below are some useful material choices. Not all information is provided, but can be found on the internet ;)
|
||||
## Overview
|
||||
|
||||
## Scintillator materials:
|
||||
miniCaloSim wraps the standard Geant4 B4a example into a configurable geometry driven by a `GeometryDescriptor` object. A geometry is a stack of layers; each layer is a slab of named NIST material with an optional sensor grid that accumulates deposited energy. The primary particle type and energy are configurable at run time.
|
||||
|
||||
* G4_POLYSTYRENE: a polymer of styrene with a density of 1.06 g/cm3 and a refractive index of 1.57. Cost: basically nothing.
|
||||
* G4_PLASTIC_SC_VINYLTOLUENE: a plastic scintillator based on vinyltoluene with a density of 1.032 g/cm3 and a refractive index of 1.58. Cost: basically nothing.
|
||||
* G4_BGO: bismuth germanate, an inorganic crystal scintillator with a density of 7.13 g/cm3 and a refractive index of 2.15. Cost 0.8 CHF/cm3
|
||||
* G4_LSO: lutetium oxyorthosilicate, an inorganic crystal scintillator with a density of 7.4 g/cm3 and a refractive index of 1.82. Cost: 6 CHF/cm3
|
||||
* G4_LYSO: lutetium yttrium oxyorthosilicate, an inorganic crystal scintillator with a density of 7.1 g/cm3 and a refractive index of 1.81. 30CHF / cm3
|
||||
The default executable `run_pbwo4` simulates electron showers in a 20 cm PbWO₄ crystal with a 10×10 sensor grid and writes a ROOT file. A second executable, `run_sampling`, simulates the same kind of showers in a sampling calorimeter (alternating thin absorber and active layers); the layer configuration is selected by name at run time.
|
||||
|
||||
## Absorber materials
|
||||
---
|
||||
|
||||
* G4_Pb: lead, a high-density and high-Z material that is commonly used as an absorber for electromagnetic calorimeters. It has a density of 11.35 g/cm3 and a radiation length of 0.56 cm. Cost: 0.03 CHF/cm3.
|
||||
* G4_Fe: iron, a medium-density and medium-Z material that is often used as an absorber for hadronic calorimeters. It has a density of 7.87 g/cm3 and an interaction length of 16.77 cm. Cost: 0.005 CHF/cm3.
|
||||
* G4_W: tungsten, a very high-density and very high-Z material that is suitable for compact and high-resolution electromagnetic calorimeters. It has a density of 19.3 g/cm3 and a radiation length of 0.35 cm. 0.6 CHF/cm3.
|
||||
* G4_Cu: copper, a high-density and high-Z material that is also used as an absorber for electromagnetic calorimeters. It has a density of 8.96 g/cm3 and a radiation length of 1.43 cm. 0.08 CHF/cm3
|
||||
* G4_BRASS: It has a density of 8.53 g/cm3 and a composition of 70% copper and 30% zinc by mass. It has a radiation length of 1.46 cm and an interaction length of 16.95 cm. Cost: 0.02 CHF/cm3
|
||||
## Repository layout
|
||||
|
||||
## For homogenous calorimeters
|
||||
```
|
||||
run_pbwo4.cc homogeneous PbWO4 crystal executable entry point
|
||||
run_sampling.cc sampling calorimeter executable entry point (selectable layer configs)
|
||||
src/ Geant4 application sources
|
||||
ActionInitialization.cc
|
||||
DetectorConstruction.cc reads GeometryDescriptor, builds Geant4 volumes
|
||||
EventAction.cc fills the Hits ntuple at end of each event
|
||||
SteppingAction.cc fills Steps and Spawning ntuples per step
|
||||
RunAction.cc creates all three ROOT ntuples
|
||||
G4System.cc owns the Geant4 run manager lifetime
|
||||
GeometryDescriptor.cc geometry data model (layers + sensors)
|
||||
PrimaryGeneratorAction.cc
|
||||
include/ corresponding headers
|
||||
bind/ Python bindings (pybind11 + pure-Python helpers)
|
||||
bindings.cpp exposes Sensor, Layer, GeometryDescriptor, G4System
|
||||
G4Calo.py high-level Python API (run_batch, display_event)
|
||||
G4Calo_exec.py worker subprocess entry point
|
||||
minicalo_tools.py TempFileManager for subprocess IPC via /dev/shm
|
||||
minipandas.py MiniFrame: minimal NumPy-backed dataframe
|
||||
lib/
|
||||
geant4/ Geant4 source submodule (fork with modifications)
|
||||
pybind11/ pybind11 submodule
|
||||
superbuild/ CMake superbuild to compile Geant4 from source
|
||||
docker/ Dockerfile-mini
|
||||
docs/ design documents
|
||||
```
|
||||
|
||||
* G4_CESIUM_IODIDE: cesium iodide, an inorganic crystal scintillator with a density of 4.51 g/cm3 and a refractive index of 1.79. radiation length about 1.9 cm. Cost: about 2 CHF/cm3
|
||||
* G4_PbWO4: lead tungstate crystal (CMS ECAL). Lead tungstate has a very high density of 19.3 g/cm3 and a very short radiation length of 0.89 cm, which makes it suitable for compact and high-resolution electromagnetic calorimeters. Cost: about 3 CHF/cm3
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
### Option A — system Geant4
|
||||
|
||||
Requires a Geant4 ≥ 11 installation visible to CMake (e.g. via `Geant4_DIR` or `CMAKE_PREFIX_PATH`), plus pybind11 (submodule) and Python 3 development headers.
|
||||
|
||||
```bash
|
||||
cmake -B build -S .
|
||||
cmake --build build --parallel $(nproc)
|
||||
# executables: build/run_pbwo4, build/run_sampling
|
||||
```
|
||||
|
||||
### Option B — build Geant4 from submodule
|
||||
|
||||
The superbuild compiles Geant4 from `lib/geant4` into `build/geant4-install`, then builds minicalosim against it. Four targets are available:
|
||||
|
||||
```bash
|
||||
cmake -B build -S superbuild/ # configure once
|
||||
|
||||
cmake --build build --target geant4 # compile Geant4 (~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 executables are at `build/run_pbwo4` and `build/run_sampling` (symlinks in the superbuild case).
|
||||
|
||||
**Options:**
|
||||
|
||||
| CMake option | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `GEANT4_INSTALL_DATA` | `ON` | Download Geant4 physics data tables (~2 GB) |
|
||||
| `GEANT4_USE_GDML` | `ON` | Build Geant4 with GDML support (requires Xerces-C); set `OFF` to build without Xerces-C |
|
||||
|
||||
The superbuild caps parallelism at `min(nproc, 16)` to avoid memory exhaustion.
|
||||
|
||||
---
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
# default: 10 events, writes pbwo4_10events_hits.root
|
||||
build/run_pbwo4
|
||||
|
||||
# custom number of events
|
||||
build/run_pbwo4 500
|
||||
```
|
||||
|
||||
Output file name: `pbwo4_<nEvents>events_hits.root`.
|
||||
|
||||
The geometry is hardcoded in `run_pbwo4.cc`: a single 20 cm PbWO₄ layer with a 10×10 sensor grid, bombarded with 1 GeV electrons.
|
||||
|
||||
### Sampling calorimeter (`run_sampling`)
|
||||
|
||||
```bash
|
||||
# default config (pb_scint), 10 events, writes sampling_pb_scint_10events_hits.root
|
||||
build/run_sampling
|
||||
|
||||
# pick a config by name, default event count
|
||||
build/run_sampling fe_scint
|
||||
|
||||
# pick a config by 1-based index, and a number of events
|
||||
build/run_sampling 3 500
|
||||
```
|
||||
|
||||
The config can be given either by name or by its 1-based index (`run_sampling --help`-style usage, including the index list, is printed on an invalid selection). Output file name: `sampling_<configName>_<nEvents>events_hits.root` (always uses the resolved name, even when selected by index).
|
||||
|
||||
All configs use a 10×10 sensor grid per active layer and are sized to reach ~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
|
||||
|
||||
Each run produces a ROOT file with three TTrees.
|
||||
|
||||
### Hits tree
|
||||
|
||||
One row per simulated event. Contains integrated quantities over the whole calorimeter.
|
||||
|
||||
| Column | Type | Description |
|
||||
|---|---|---|
|
||||
| `true_energy` | `double` | Primary particle energy [MeV] |
|
||||
| `total_dep_energy` | `double` | Total energy deposited in all sensors [MeV] |
|
||||
| `N_layers` | `int` | Number of layers |
|
||||
| `N_active_layers` | `int` | Number of active (sensitive) layers |
|
||||
| `N_sensors` | `int` | Total number of sensors |
|
||||
| `sensor_energy[N]` | `double[]` | Deposited energy per sensor [MeV] |
|
||||
| `sensor_x/y/z[N]` | `double[]` | Sensor centre position [mm] |
|
||||
| `sensor_dx/dy/dz[N]` | `double[]` | Sensor half-size [mm] |
|
||||
| `sensor_layer[N]` | `int[]` | Layer index of each sensor |
|
||||
| `sensor_copy_number[N]` | `int[]` | Copy number within its layer |
|
||||
|
||||
### Steps tree
|
||||
|
||||
One row per Geant4 step. Every step of every track of every event is recorded.
|
||||
|
||||
| Column | Type | Description |
|
||||
|---|---|---|
|
||||
| `event_id` | `int` | Event index within the run |
|
||||
| `track_id` | `int` | Track ID (1 = primary, ≥2 = secondaries) |
|
||||
| `step_no` | `int` | Step number within this track (starts at 1) |
|
||||
| `pdg` | `int` | PDG encoding of the particle |
|
||||
| `pre_x/y/z` | `double` | Pre-step position [mm] |
|
||||
| `pre_E` | `double` | Kinetic energy at step start [MeV] |
|
||||
| `post_x/y/z` | `double` | Post-step position [mm] |
|
||||
| `post_E` | `double` | Kinetic energy at step end [MeV] |
|
||||
| `edep` | `double` | Energy deposited in medium at this step [MeV] |
|
||||
| `step_length` | `double` | Geometric step length [mm] |
|
||||
| `process` | `string` | Physics process that ended this step |
|
||||
| `layer_id` | `int` | Layer index at the pre-step point (-1 = outside all layers) |
|
||||
| `material` | `string` | Material name at the pre-step point |
|
||||
| `pre_dx/dy/dz` | `double` | Pre-step momentum unit direction |
|
||||
| `post_dx/dy/dz` | `double` | Post-step momentum unit direction |
|
||||
| `Bx/By/Bz` | `double` | Magnetic field at pre-step position [T] |
|
||||
| `Ex/Ey/Ez` | `double` | Electric field at pre-step position [V/m] |
|
||||
| `child_track_ids` | `int[]` | Track IDs of all secondaries spawned during this step (empty if none) |
|
||||
|
||||
**Track IDs** are assigned sequentially per event starting from 1. The primary particle is always track 1. Within an event, `track_id` uniquely identifies a particle. See also the Spawning tree.
|
||||
|
||||
### Spawning tree
|
||||
|
||||
One row per secondary particle birth. Enables direct shower-tree reconstruction without position joins.
|
||||
|
||||
| Column | Type | Description |
|
||||
|---|---|---|
|
||||
| `event_id` | `int` | Event index |
|
||||
| `parent_track_id` | `int` | Track ID of the parent particle |
|
||||
| `parent_step_no` | `int` | Step number of the parent at which this secondary was created |
|
||||
| `child_track_id` | `int` | Track ID of the newly born secondary |
|
||||
|
||||
**Example join:** to find all secondaries born at step 5 of track 3 in event 0:
|
||||
```python
|
||||
spawning[(spawning.event_id == 0) &
|
||||
(spawning.parent_track_id == 3) &
|
||||
(spawning.parent_step_no == 5)]
|
||||
```
|
||||
|
||||
The same information is available in the Steps tree via the `child_track_ids` array column on the parent's row.
|
||||
|
||||
---
|
||||
|
||||
## Calorimeter materials
|
||||
|
||||
A few material choices available via NIST names:
|
||||
|
||||
### Scintillators (active layers)
|
||||
|
||||
| NIST name | Density | Notes |
|
||||
|---|---|---|
|
||||
| `G4_POLYSTYRENE` | 1.06 g/cm³ | cheap plastic scintillator |
|
||||
| `G4_PLASTIC_SC_VINYLTOLUENE` | 1.032 g/cm³ | plastic scintillator |
|
||||
| `G4_BGO` | 7.13 g/cm³ | inorganic crystal, ~0.8 CHF/cm³ |
|
||||
| `G4_LSO` | 7.4 g/cm³ | inorganic crystal, ~6 CHF/cm³ |
|
||||
| `G4_LYSO` | 7.1 g/cm³ | inorganic crystal, ~30 CHF/cm³ |
|
||||
| `G4_lAr` | 1.40 g/cm³ | liquid argon, cryogenic (ATLAS EM calo-style) |
|
||||
|
||||
### Absorbers (passive layers)
|
||||
|
||||
| NIST name | Density | X₀ | Notes |
|
||||
|---|---|---|---|
|
||||
| `G4_Pb` | 11.35 g/cm³ | 0.56 cm | standard EM calorimeter absorber |
|
||||
| `G4_Fe` | 7.87 g/cm³ | — | hadronic calorimeter absorber |
|
||||
| `G4_W` | 19.3 g/cm³ | 0.35 cm | compact high-resolution EM |
|
||||
| `G4_Cu` | 8.96 g/cm³ | 1.43 cm | EM calorimeter absorber |
|
||||
| `G4_BRASS` | 8.53 g/cm³ | 1.46 cm | 70% Cu / 30% Zn |
|
||||
|
||||
### Homogeneous calorimeters
|
||||
|
||||
| NIST name | Density | X₀ | Notes |
|
||||
|---|---|---|---|
|
||||
| `G4_CESIUM_IODIDE` | 4.51 g/cm³ | ~1.9 cm | inorganic crystal |
|
||||
| `G4_PbWO4` | 8.28 g/cm³ | 0.89 cm | lead tungstate (CMS ECAL) |
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
`docker/Dockerfile-mini` builds a minimal Ubuntu 22.04 image with:
|
||||
- Geant4 compiled from the `lib/geant4` submodule (batch mode, no vis, no multithreading)
|
||||
- The `minicalo` Python package installed system-wide
|
||||
- Python dependencies: numpy, pandas, uproot, awkward, plotly
|
||||
|
||||
Build (from the repo root, passing `COMMIT` to pin the source):
|
||||
```bash
|
||||
docker build \
|
||||
--build-arg COMMIT=$(git rev-parse HEAD) \
|
||||
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
||||
-f docker/Dockerfile-mini \
|
||||
-t minicalosim:latest \
|
||||
--build-context minicalosim=. \
|
||||
.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Geant4 modifications
|
||||
|
||||
The `lib/geant4` submodule is a fork of Geant4 with three small changes to enable pre-assigning track IDs to secondary particles at the moment they are created, so that `UserSteppingAction` can record which child track IDs a step spawned (the `child_track_ids` column and the Spawning tree).
|
||||
|
||||
### Background
|
||||
|
||||
In unmodified Geant4, track IDs are assigned in `G4EventManager::StackTracks()`, which is called only after the parent track has finished all its steps. At the time `UserSteppingAction` fires, the secondaries visible via `G4Step::GetSecondaryInCurrentStep()` have not yet received their IDs.
|
||||
|
||||
### Changes
|
||||
|
||||
All changes are on the `minicalosim-spawning` branch of `git@gitlab.etp.kit.edu:lbogner/geant4.git`.
|
||||
|
||||
**`source/tracking/include/G4SteppingManager.hh`**
|
||||
|
||||
Adds a `G4int* fTrackIDCounter` member and a `SetTrackIDCounter(G4int*)` setter. The pointer is set by `G4EventManager` (via `G4TrackingManager`) at the start of each event and points directly into `G4EventManager::trackIDCounter`.
|
||||
|
||||
**`source/tracking/include/G4TrackingManager.hh`**
|
||||
|
||||
Adds a `SetTrackIDCounter(G4int*)` pass-through that forwards the pointer into `G4SteppingManager`.
|
||||
|
||||
**`source/tracking/src/G4SteppingManager.cc`**
|
||||
|
||||
In `ProcessSecondariesFromParticleChange()`, after `SetParentID` and `SetCreatorProcess`, immediately assigns a track ID to each secondary by incrementing through the pointer:
|
||||
|
||||
```cpp
|
||||
if (fTrackIDCounter) {
|
||||
tempSecondaryTrack->SetTrackID(++(*fTrackIDCounter));
|
||||
}
|
||||
```
|
||||
|
||||
**`source/event/src/G4EventManager.cc`**
|
||||
|
||||
- Calls `trackManager->SetTrackIDCounter(&trackIDCounter)` once before the event's tracking loop.
|
||||
- `StackTracks()` is updated to skip counter increment and ID assignment for tracks that already have a non-zero ID (i.e. were pre-assigned above):
|
||||
|
||||
```cpp
|
||||
if (IDhasAlreadySet) {
|
||||
++trackIDCounter; // advance for primaries (ID set externally)
|
||||
} else if (newTrack->GetTrackID() == 0) {
|
||||
++trackIDCounter; // normal secondary: assign as before
|
||||
newTrack->SetTrackID(trackIDCounter);
|
||||
...
|
||||
}
|
||||
// else: pre-assigned secondary — counter already incremented, skip
|
||||
```
|
||||
|
||||
### Why not include `G4EventManager.hh` in `G4SteppingManager.cc`?
|
||||
|
||||
`G4event` already depends on `G4tracking` (`G4EventManager` uses `G4TrackingManager`), so including `G4EventManager.hh` in the tracking module would create a circular dependency. The pointer approach avoids any new include: `G4SteppingManager` only needs a `G4int*`, which requires no additional header.
|
||||
|
||||
+48
-8
@@ -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,14 +281,17 @@ 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)
|
||||
|
||||
@@ -334,7 +361,8 @@ def run_batch(
|
||||
maxEnergy_GeV: float = -1.0,
|
||||
filename: str = "",
|
||||
no_mp: bool = False,
|
||||
manual_seed : int = -1):
|
||||
manual_seed : int = -1,
|
||||
return_steps: bool = False):
|
||||
"""
|
||||
Run a full Geant4 simulation batch with automatic parallelisation.
|
||||
|
||||
@@ -361,12 +389,17 @@ no_mp : bool, optional
|
||||
Disable multiprocessing (run single-threaded) for debugging.
|
||||
manual_seed : int, optional
|
||||
Optional random seed. If < 0, seeds are generated automatically.
|
||||
return_steps : bool, optional
|
||||
If True, also return a MiniFrame of step-level data (one row per
|
||||
Geant4 step across all events). Returns a tuple (hits, steps).
|
||||
|
||||
Returns
|
||||
-------
|
||||
MiniFrame
|
||||
Table-like structure with one row per event, containing both scalar
|
||||
values and fixed-size sensor arrays.
|
||||
MiniFrame or tuple[MiniFrame, MiniFrame]
|
||||
If return_steps is False (default): event-level MiniFrame with one
|
||||
row per event. If return_steps is True: (hits, steps) where steps
|
||||
has one row per Geant4 step with columns event_id, track_id,
|
||||
step_no, pdg, pre_x/y/z, pre_E, post_x/y/z, post_E.
|
||||
|
||||
Notes
|
||||
-----
|
||||
@@ -406,6 +439,7 @@ Example
|
||||
#print(f"Running {nEventsLastCore} events on last core")
|
||||
|
||||
nevents = [nEventsPerCore if i < nCores - 1 else nEventsLastCore for i in range(nCores)]
|
||||
event_id_offsets = [sum(nevents[:i]) for i in range(nCores)]
|
||||
|
||||
if manual_seed >= 0:
|
||||
seed = manual_seed
|
||||
@@ -456,13 +490,19 @@ Example
|
||||
sw.reset()
|
||||
sw.start()
|
||||
df = None
|
||||
steps_df = None
|
||||
try:
|
||||
df = _assemble_results_to_mini_df(rp)
|
||||
finally: #make sure to delete temp files
|
||||
if return_steps:
|
||||
steps_df = _assemble_results_to_mini_df(rp, tree_name="Steps",
|
||||
event_id_offsets=event_id_offsets)
|
||||
finally:
|
||||
for f in tmpfile:
|
||||
if os.path.exists(f):
|
||||
os.remove(f)
|
||||
print('G4Calo: concatenation finished after {:.2f} seconds'.format(sw.elapsed()))
|
||||
if return_steps:
|
||||
return df, steps_df
|
||||
return df
|
||||
|
||||
def _fill_event(gd : GeometryDescriptor,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import os
|
||||
from G4Calo import run_batch
|
||||
from minicalo import GeometryDescriptor
|
||||
|
||||
output_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
gd = GeometryDescriptor()
|
||||
gd.addLayer(20.0, "G4_PbWO4", True, 10, 10)
|
||||
|
||||
hits, steps = run_batch(gd, 10, "e-", 1.0, return_steps=True)
|
||||
|
||||
hits_out = os.path.join(output_dir, "pbwo4_10events_hits.pkl")
|
||||
steps_out = os.path.join(output_dir, "pbwo4_10events_steps.pkl")
|
||||
hits.to_pickle(hits_out)
|
||||
steps.to_pickle(steps_out)
|
||||
print(f"Saved {len(hits)} events to {hits_out}")
|
||||
print(f"Saved {len(steps)} steps to {steps_out}")
|
||||
+56
-118
@@ -1,131 +1,69 @@
|
||||
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
|
||||
FROM ubuntu:24.04
|
||||
|
||||
SHELL ["/bin/sh", "-c"]
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
USER root
|
||||
# ---------------------------------------------------------------------------
|
||||
# System dependencies (dnf instead of apt)
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
make \
|
||||
cmake \
|
||||
ninja-build \
|
||||
libxerces-c-dev \
|
||||
git \
|
||||
curl \
|
||||
ca-certificates \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# ---------------------------------------------------------------------------
|
||||
# uv
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
ENV PATH="/root/.local/bin:${PATH}"
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
WORKDIR /src
|
||||
|
||||
RUN /usr/bin/ls
|
||||
RUN sed -i "s,# deb http://archive.canonical.com/ubuntu,deb http://archive.canonical.com/ubuntu,g" /etc/apt/sources.list
|
||||
# ---------------------------------------------------------------------------
|
||||
# Copy only files needed for the C++ build
|
||||
# ---------------------------------------------------------------------------
|
||||
COPY CMakeLists.txt .
|
||||
COPY superbuild/ superbuild/
|
||||
COPY src/ src/
|
||||
COPY include/ include/
|
||||
COPY bind/ bind/
|
||||
COPY lib/ lib/
|
||||
COPY run_pbwo4.cc .
|
||||
COPY run_sampling.cc .
|
||||
|
||||
## Install some deps
|
||||
#RUN ls /etc/apt/
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y software-properties-common
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa
|
||||
RUN apt-get update -y && apt-get upgrade -y
|
||||
RUN apt-get install -y python3 python3-dev python3-venv python3-pip
|
||||
#RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 100
|
||||
# lib/geant4/.git is a submodule pointer to the host's .git/modules dir, which
|
||||
# is not copied into the image. Replace it with a fresh repo so Geant4's cmake
|
||||
# git status calls succeed.
|
||||
RUN rm lib/geant4/.git && git init lib/geant4
|
||||
|
||||
RUN apt-get install -y dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev libxft-dev libxext-dev libssl-dev
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build Geant4 + miniCaloSim
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN cmake -S superbuild -B build -G Ninja \
|
||||
-DGEANT4_INSTALL_DATA=ON
|
||||
|
||||
#RUN python3 --version && python3 -m ensurepip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN cmake --build build --parallel
|
||||
|
||||
RUN python3 -m pip install pandas numpy matplotlib MarkupSafe wandb uproot setuptools awkward-pandas plotly
|
||||
RUN python3 -m pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
|
||||
RUN python3 -m pip install torch_geometric
|
||||
RUN python3 -m pip install torch-cluster -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
|
||||
# ---------------------------------------------------------------------------
|
||||
# Python environment (uv)
|
||||
# ---------------------------------------------------------------------------
|
||||
RUN uv venv /opt/venv
|
||||
|
||||
# # # # GEANT
|
||||
RUN apt-get -y install libxmu-dev \
|
||||
libgl1-mesa-dev \
|
||||
libxerces-c-dev \
|
||||
libexpat1 \
|
||||
libx11-dev \
|
||||
libglu1-mesa-dev \
|
||||
freeglut3-dev \
|
||||
mesa-common-dev \
|
||||
imagemagick \
|
||||
gv wget autotools-dev libicu-dev libbz2-dev
|
||||
ENV PATH="/opt/venv/bin:${PATH}"
|
||||
|
||||
ADD docker/pyproject.toml pyproject.toml
|
||||
|
||||
RUN uv sync
|
||||
|
||||
## DAWN FOR VIS
|
||||
|
||||
RUN mkdir dawn-source \
|
||||
&& wget -q -O - http://geant4.kek.jp/~tanaka/src/dawn_3_91a.tgz | tar xzf - -C "dawn-source" \
|
||||
&& export DAWN_PS_PREVIEWER=NONE && cd dawn-source/dawn_3_91a \
|
||||
&& make clean && make guiclean && make && make install \
|
||||
&& cd - && rm -rf "dawn-source"
|
||||
|
||||
## main package
|
||||
|
||||
RUN cd && wget -q https://gitlab.cern.ch/geant4/geant4/-/archive/v11.1.2/geant4-v11.1.2.tar.gz && \
|
||||
tar -xzf geant4-v11.1.2.tar.gz -C /tmp && \
|
||||
mkdir /tmp/geant4-v11.1.2-build && \
|
||||
cd /tmp/geant4-v11.1.2-build && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 -DCMAKE_RULE_MESSAGES=OFF -DGEANT4_INSTALL_DATA=ON \
|
||||
-DGEANT4_USE_GDML=ON -DGEANT4_USE_OPENGL_X11=ON \
|
||||
-DGEANT4_BUILD_MULTITHREADED=OFF \
|
||||
-DGEANT4_BUILD_TLS_MODEL=global-dynamic \
|
||||
-DGEANT4_USE_RAYTRACER_X11=ON /tmp/geant4-v11.1.2 >/dev/null && \
|
||||
make -j24 >/dev/null && \
|
||||
make install >/dev/null
|
||||
|
||||
RUN rm -rf /tmp/geant4-v11.1.2 && \
|
||||
rm -rf /tmp/geant4-v11.1.2-build
|
||||
|
||||
|
||||
RUN python3 -m pip install geant4-pybind
|
||||
|
||||
ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}"
|
||||
|
||||
RUN apt-get install -y git
|
||||
|
||||
RUN python3 -m pip install ipython
|
||||
RUN apt-get install -y tk # for dawn
|
||||
|
||||
# add jupyter packages for UC2 on top so they don't need to be installed every time
|
||||
RUN python3 -m pip install jupyterlab jupyterhub==4.1.5 batchspawner
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
|
||||
RUN python3 -m pip install torch-scatter -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
|
||||
|
||||
#finally the package; use the commit hash to check out the correct version and only get them here, such that the rest can be cached
|
||||
ARG BUILD_DATE
|
||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||
ARG COMMIT
|
||||
ARG USER
|
||||
|
||||
# copy minicalosim and checkout the correct commit
|
||||
ADD minicalosim /root/minicalosim
|
||||
|
||||
RUN cd /root/minicalosim && git checkout $COMMIT && \
|
||||
mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4
|
||||
|
||||
|
||||
RUN cp /root/minicalosim/build/minicalo* /root/minicalosim/bind/G4Calo.py /root/minicalosim/bind/minicalo_tools.py /root/minicalosim/bind/minipandas.py /usr/local/lib/python3.10/dist-packages/
|
||||
|
||||
RUN cp /root/minicalosim/bind/G4Calo_exec.py /usr/local/bin/
|
||||
|
||||
RUN cp /root/minicalosim/docker/start-notebook.py /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-notebook.sh /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-singleuser.py /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-singleuser.sh /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/fix-permissions /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/check_cuda_torch.py /usr/local/bin/
|
||||
|
||||
ARG NB_USER="jovyan"
|
||||
ARG NB_UID="1000"
|
||||
ARG NB_GID="100"
|
||||
|
||||
USER root
|
||||
|
||||
#RUN groupadd -g ${NB_GID} ${NB_USER} && \
|
||||
RUN useradd -m -s /bin/bash -N -u ${NB_UID} -g ${NB_GID} ${NB_USER}
|
||||
|
||||
RUN mkdir /etc/jupyter
|
||||
RUN fix-permissions /etc/jupyter/
|
||||
|
||||
|
||||
# clean up
|
||||
RUN rm -rf /root/minicalosim
|
||||
|
||||
RUN pip3 install pyarrow fastparquet
|
||||
|
||||
USER ${NB_UID}
|
||||
CMD ["start-notebook.sh"]
|
||||
WORKDIR /workspace
|
||||
|
||||
ENTRYPOINT ["/bin/bash", "-l"]
|
||||
@@ -1,127 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
SHELL ["/bin/sh", "-c"]
|
||||
|
||||
USER root
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN /usr/bin/ls
|
||||
RUN sed -i "s,# deb http://archive.canonical.com/ubuntu,deb http://archive.canonical.com/ubuntu,g" /etc/apt/sources.list
|
||||
|
||||
## Install some deps
|
||||
#RUN ls /etc/apt/
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y software-properties-common
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa
|
||||
RUN apt-get update -y && apt-get upgrade -y
|
||||
RUN apt-get install -y python3 python3-dev python3-venv python3-pip
|
||||
#RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 100
|
||||
|
||||
RUN apt-get install -y dpkg-dev cmake g++ gcc binutils libx11-dev libxpm-dev libxft-dev libxext-dev libssl-dev
|
||||
|
||||
#RUN python3 --version && python3 -m ensurepip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
|
||||
RUN python3 -m pip install pandas numpy matplotlib MarkupSafe wandb uproot setuptools awkward-pandas plotly
|
||||
RUN python3 -m pip install --no-cache-dir torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
|
||||
RUN python3 -m pip install torch_geometric
|
||||
RUN python3 -m pip install torch-cluster -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
|
||||
|
||||
# # # # GEANT
|
||||
RUN apt-get -y install libxmu-dev \
|
||||
libgl1-mesa-dev \
|
||||
libxerces-c-dev \
|
||||
libexpat1 \
|
||||
libx11-dev \
|
||||
libglu1-mesa-dev \
|
||||
freeglut3-dev \
|
||||
mesa-common-dev \
|
||||
imagemagick \
|
||||
gv wget autotools-dev libicu-dev libbz2-dev
|
||||
|
||||
|
||||
|
||||
## DAWN FOR VIS
|
||||
|
||||
RUN mkdir dawn-source \
|
||||
&& wget -q -O - http://geant4.kek.jp/~tanaka/src/dawn_3_91a.tgz | tar xzf - -C "dawn-source" \
|
||||
&& export DAWN_PS_PREVIEWER=NONE && cd dawn-source/dawn_3_91a \
|
||||
&& make clean && make guiclean && make && make install \
|
||||
&& cd - && rm -rf "dawn-source"
|
||||
|
||||
## main package
|
||||
|
||||
RUN cd && wget -q https://gitlab.cern.ch/geant4/geant4/-/archive/v11.1.2/geant4-v11.1.2.tar.gz && \
|
||||
tar -xzf geant4-v11.1.2.tar.gz -C /tmp && \
|
||||
mkdir /tmp/geant4-v11.1.2-build && \
|
||||
cd /tmp/geant4-v11.1.2-build && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/opt/geant4-v11.1.2 -DCMAKE_RULE_MESSAGES=OFF -DGEANT4_INSTALL_DATA=ON \
|
||||
-DGEANT4_USE_GDML=ON -DGEANT4_USE_OPENGL_X11=ON \
|
||||
-DGEANT4_BUILD_MULTITHREADED=ON \
|
||||
-DGEANT4_BUILD_TLS_MODEL=global-dynamic \
|
||||
-DGEANT4_USE_RAYTRACER_X11=ON /tmp/geant4-v11.1.2 >/dev/null && \
|
||||
make -j4 >/dev/null && \
|
||||
make install >/dev/null
|
||||
|
||||
RUN rm -rf /tmp/geant4-v11.1.2 && \
|
||||
rm -rf /tmp/geant4-v11.1.2-build
|
||||
|
||||
|
||||
RUN python3 -m pip install geant4-pybind
|
||||
|
||||
ENV LD_LIBRARY_PATH="/opt/geant4-v11.1.2/lib:${LD_LIBRARY_PATH}"
|
||||
|
||||
RUN apt-get install -y git
|
||||
|
||||
RUN python3 -m pip install ipython
|
||||
RUN apt-get install -y tk # for dawn
|
||||
|
||||
# add jupyter packages for UC2 on top so they don't need to be installed every time
|
||||
RUN python3 -m pip install jupyterlab jupyterhub==4.1.5 batchspawner
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
|
||||
RUN python3 -m pip install torch-scatter -f https://data.pyg.org/whl/torch-2.1.0+cu118.html
|
||||
|
||||
#finally the package; use the commit hash to check out the correct version and only get them here, such that the rest can be cached
|
||||
ARG BUILD_DATE
|
||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||
ARG COMMIT
|
||||
ARG USER
|
||||
|
||||
# copy minicalosim and checkout the correct commit
|
||||
ADD minicalosim /root/minicalosim
|
||||
|
||||
RUN cd /root/minicalosim && git checkout $COMMIT && \
|
||||
mkdir -p build && cd build && rm -rf * && cmake ../ && make -j4 &&\
|
||||
cp minicalo* ../bind/G4Calo.py /usr/local/lib/python3.8/dist-packages/
|
||||
|
||||
|
||||
|
||||
RUN cp /root/minicalosim/docker/start-notebook.py /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-notebook.sh /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-singleuser.py /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/start-singleuser.sh /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/fix-permissions /usr/local/bin/
|
||||
RUN cp /root/minicalosim/docker/check_cuda_torch.py /usr/local/bin/
|
||||
|
||||
ARG NB_USER="jovyan"
|
||||
ARG NB_UID="1000"
|
||||
ARG NB_GID="100"
|
||||
|
||||
USER root
|
||||
|
||||
#RUN groupadd -g ${NB_GID} ${NB_USER} && \
|
||||
RUN useradd -m -s /bin/bash -N -u ${NB_UID} -g ${NB_GID} ${NB_USER}
|
||||
|
||||
RUN mkdir /etc/jupyter
|
||||
RUN fix-permissions /etc/jupyter/
|
||||
|
||||
|
||||
# clean up
|
||||
RUN rm -rf /root/minicalosim
|
||||
|
||||
USER ${NB_UID}
|
||||
CMD ["start-notebook.sh"]
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# a simple parser for the args <commit> and --no-cache
|
||||
# if no commit is given, the latest commit is used
|
||||
# if --no-cache is given, the --no-cache flag is passed to the docker build command
|
||||
# if only --no-cache is given, the latest commit is used
|
||||
|
||||
# check if commit is given as argument, if not use the latest commit
|
||||
# check if an argument is given at all
|
||||
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
#check if the first arg is --no-cache
|
||||
if [ "$1" == "--no-cache" ]; then
|
||||
FORCE_NO_CACHE="--no-cache"
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
else
|
||||
COMMIT=$1
|
||||
#check if this is a valid commit
|
||||
if ! git cat-file -e $COMMIT^{commit} 2>/dev/null; then
|
||||
echo "ERROR: Commit $COMMIT not found in local repository."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else #default to last commit
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
fi
|
||||
|
||||
|
||||
# COMMIT is a valid commit at this point. If COMMIT is the last commit and changes are not commited, the user is warned.
|
||||
|
||||
#check if the commit is the last commit
|
||||
if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then
|
||||
# check if we have uncommitted changes and if so warn the user.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "WARNING: You are building the latest commit ${COMMIT}, but you have uncommitted changes."
|
||||
echo " This means that the image will not be reproducible."
|
||||
echo " If you want to build the latest commit, please commit your changes first."
|
||||
read -p "Do you want to continue anyway? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
check_remote_commit_exists() {
|
||||
# Usage: check_remote_commit_exists <repo-url> <commit-sha>
|
||||
local url="$1"
|
||||
local commit="$2"
|
||||
|
||||
# Create a temporary directory for a shallow fetch
|
||||
local tmpdir
|
||||
tmpdir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmpdir"' RETURN # auto-cleanup on function exit
|
||||
|
||||
git -C "$tmpdir" init -q
|
||||
git -C "$tmpdir" remote add origin "$url"
|
||||
|
||||
if git -C "$tmpdir" fetch --quiet --depth=1 origin "$commit" 2>/dev/null; then
|
||||
echo "✅ Commit $commit exists on remote."
|
||||
return 0
|
||||
else
|
||||
echo "❌ ERROR: Commit $commit not found on remote."
|
||||
echo "Refs currently visible on remote:"
|
||||
git ls-remote --heads --tags "$url"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check if the commit has been pushed
|
||||
if ! check_remote_commit_exists https://github.com/jkiesele/minicalosim $COMMIT; then
|
||||
echo "ERROR: Commit $COMMIT not found in remote repository."
|
||||
fi
|
||||
|
||||
# check if no-cache is given as second argument
|
||||
if [ "$2" == "--no-cache" ]; then
|
||||
FORCE_NO_CACHE="--no-cache"
|
||||
fi
|
||||
|
||||
#switch to directory this script is located in
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../ #switch to the root directory of the project
|
||||
echo "Building minicalosim:$COMMIT"
|
||||
|
||||
#print the current working directory
|
||||
echo "Current working directory: $(pwd)"
|
||||
|
||||
docker build $FORCE_NO_CACHE -t jkiesele/minicalosim:$COMMIT --build-arg USER=$USER --build-arg BUILD_DATE="$(date)" --build-arg COMMIT=$COMMIT -f minicalosim/docker/Dockerfile .
|
||||
|
||||
echo "sucessfully built container jkiesele/minicalosim:${COMMIT}"
|
||||
|
||||
docker tag jkiesele/minicalosim:$COMMIT jkiesele/minicalosim:latest
|
||||
|
||||
echo "also tagged as jkiesele/minicalosim:latest"
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# a simple parser for the args <commit> and --no-cache
|
||||
# if no commit is given, the latest commit is used
|
||||
# if --no-cache is given, the --no-cache flag is passed to the docker build command
|
||||
# if only --no-cache is given, the latest commit is used
|
||||
|
||||
# check if commit is given as argument, if not use the latest commit
|
||||
# check if an argument is given at all
|
||||
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
#check if the first arg is --no-cache
|
||||
if [ "$1" == "--no-cache" ]; then
|
||||
FORCE_NO_CACHE="--no-cache"
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
else
|
||||
COMMIT=$1
|
||||
#check if this is a valid commit
|
||||
if ! git cat-file -e $COMMIT^{commit} 2>/dev/null; then
|
||||
echo "ERROR: Commit $COMMIT not found in local repository."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else #default to last commit
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
fi
|
||||
|
||||
|
||||
# COMMIT is a valid commit at this point. If COMMIT is the last commit and changes are not commited, the user is warned.
|
||||
|
||||
#check if the commit is the last commit
|
||||
if [ "$COMMIT" == "$(git rev-parse --short HEAD)" ]; then
|
||||
# check if we have uncommitted changes and if so warn the user.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "WARNING: You are building the latest commit ${COMMIT}, but you have uncommitted changes."
|
||||
echo " This means that the image will not be reproducible."
|
||||
echo " If you want to build the latest commit, please commit your changes first."
|
||||
read -p "Do you want to continue anyway? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# check if the commit has been pushed
|
||||
REMOTE_COMMIT=$(git ls-remote https://gitlab.etp.kit.edu/jkiesele/minicalosim.git | grep $COMMIT)
|
||||
if [ -z "$REMOTE_COMMIT" ]; then
|
||||
echo "ERROR: Commit $COMMIT not found in remote repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# check if no-cache is given as second argument
|
||||
if [ "$2" == "--no-cache" ]; then
|
||||
FORCE_NO_CACHE="--no-cache"
|
||||
fi
|
||||
|
||||
#switch to directory this script is located in
|
||||
cd "$(dirname "$0")"
|
||||
cd ../../ #switch to the root directory of the project
|
||||
echo "Building minicalosim_cpu:$COMMIT"
|
||||
|
||||
#print the current working directory
|
||||
echo "Current working directory: $(pwd)"
|
||||
|
||||
docker build $FORCE_NO_CACHE -t jkiesele/minicalosim_cpu:$COMMIT --build-arg USER=$USER --build-arg BUILD_DATE="$(date)" --build-arg COMMIT=$COMMIT -f minicalosim/docker/Dockerfile-cpu .
|
||||
|
||||
echo "sucessfully built container jkiesele/minicalosim_cpu:${COMMIT}"
|
||||
|
||||
docker tag jkiesele/minicalosim_cpu:$COMMIT jkiesele/minicalosim_cpu:latest
|
||||
|
||||
echo "also tagged as jkiesele/minicalosim_cpu:latest"
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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 "$@"
|
||||
@@ -0,0 +1,31 @@
|
||||
# Design: `parent_step_no` Column on Steps Ntuple
|
||||
|
||||
## Problem
|
||||
|
||||
The Steps ntuple records `parent_id` (which track created a secondary) but not *at which step* of the parent the secondary was born. Finding this after the fact requires a position join: match the secondary's first pre-step point against the parent's post-step points across ntuple rows.
|
||||
|
||||
## Proposed solution
|
||||
|
||||
Add a `parent_step_no` integer column to the Steps ntuple. Value is `-1` for the primary particle and the parent's step number for every step of any secondary.
|
||||
|
||||
## Mechanism
|
||||
|
||||
Use `G4VUserTrackInformation` to carry the parent step number forward from creation time to the secondary's tracking phase:
|
||||
|
||||
1. **Tag at creation** — in `UserSteppingAction`, iterate `step->GetSecondaryInCurrentStep()` and attach a `TrackUserInfo(track->GetCurrentStepNumber())` object to each secondary via `const_cast<G4Track*>(sec)->SetUserInformation(...)`. Geant4 takes ownership and deletes it with the track.
|
||||
|
||||
2. **Read at tracking** — when filling a Steps ntuple row, retrieve `dynamic_cast<TrackUserInfo*>(track->GetUserInformation())` and write `info->parentStepNo`, or `-1` if null (primary).
|
||||
|
||||
## Files
|
||||
|
||||
- `include/TrackUserInfo.hh` — new: minimal `G4VUserTrackInformation` subclass storing `int parentStepNo`
|
||||
- `src/RunAction.cc` — add column 27 `parent_step_no` (Integer) to ntuple 1 (`Steps`)
|
||||
- `src/SteppingAction.cc` — tag new secondaries; fill column 27
|
||||
|
||||
## Note on `const_cast`
|
||||
|
||||
`GetSecondaryInCurrentStep()` returns `const G4Track*`. The `const` is a conservative API choice, not a physics invariant. `G4VUserTrackInformation` is an explicit user-side side-channel; using `const_cast` here is standard Geant4 practice.
|
||||
|
||||
## Superseded by
|
||||
|
||||
This approach was superseded by the `Spawning` ntuple (branch `spawning-ntuple`), which avoids the per-row overhead and `const_cast` by pre-assigning track IDs in `G4SteppingManager` and writing a dedicated secondary-birth table.
|
||||
@@ -1,23 +0,0 @@
|
||||
# Macro file for example B4 test
|
||||
|
||||
/run/initialize
|
||||
|
||||
# e+ 300MeV
|
||||
/gun/particle e+
|
||||
/gun/energy 300 MeV
|
||||
/run/beamOn 1
|
||||
#
|
||||
# list the existing physics processes
|
||||
/process/list
|
||||
#
|
||||
# switch off MultipleScattering
|
||||
/process/inactivate msc
|
||||
/run/beamOn 1
|
||||
#
|
||||
# switch on MultipleScattering
|
||||
/process/activate msc
|
||||
#
|
||||
# change detector parameter
|
||||
/gun/particle gamma
|
||||
/gun/energy 500 MeV
|
||||
/run/beamOn 1
|
||||
-285
@@ -1,285 +0,0 @@
|
||||
//
|
||||
// ********************************************************************
|
||||
// * License and Disclaimer *
|
||||
// * *
|
||||
// * The Geant4 software is copyright of the Copyright Holders of *
|
||||
// * the Geant4 Collaboration. It is provided under the terms and *
|
||||
// * conditions of the Geant4 Software License, included in the file *
|
||||
// * LICENSE and available at http://cern.ch/geant4/license . These *
|
||||
// * include a list of copyright holders. *
|
||||
// * *
|
||||
// * Neither the authors of this software system, nor their employing *
|
||||
// * institutes,nor the agencies providing financial support for this *
|
||||
// * work make any representation or warranty, express or implied, *
|
||||
// * regarding this software system or assume any liability for its *
|
||||
// * use. Please see the license in the file LICENSE and URL above *
|
||||
// * for the full disclaimer and the limitation of liability. *
|
||||
// * *
|
||||
// * This code implementation is the result of the scientific and *
|
||||
// * technical work of the GEANT4 collaboration. *
|
||||
// * By using, copying, modifying or distributing the software (or *
|
||||
// * any work based on the software) you agree to acknowledge its *
|
||||
// * use in resulting scientific publications, and indicate your *
|
||||
// * acceptance of all terms of the Geant4 Software license. *
|
||||
// ********************************************************************
|
||||
//
|
||||
//
|
||||
/// \file exampleB4a.cc
|
||||
/// \brief Main program of the B4a example
|
||||
|
||||
#include "DetectorConstruction.hh"
|
||||
#include "ActionInitialization.hh"
|
||||
|
||||
#include "G4RunManagerFactory.hh"
|
||||
#include "G4SteppingVerbose.hh"
|
||||
#include "G4UIcommand.hh"
|
||||
#include "G4UImanager.hh"
|
||||
#include "G4UIExecutive.hh"
|
||||
#include "G4VisExecutive.hh"
|
||||
#include "FTFP_BERT.hh"
|
||||
#include "Randomize.hh"
|
||||
|
||||
#include "GeometryDescriptor.hh"
|
||||
|
||||
#include "G4System.hh"
|
||||
|
||||
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
void PrintUsage() {
|
||||
G4cerr << " Usage: " << G4endl;
|
||||
G4cerr << " exampleB4a [-m macro ] [-u UIsession] [-t nThreads] [-vDefault]"
|
||||
<< G4endl;
|
||||
G4cerr << " note: -t option is available only for multi-threaded mode."
|
||||
<< G4endl;
|
||||
}
|
||||
}
|
||||
|
||||
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
|
||||
|
||||
int main2(int argc,char** argv)
|
||||
{
|
||||
// Evaluate arguments
|
||||
//
|
||||
if ( argc > 7 ) {
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
G4String macro;
|
||||
G4String session;
|
||||
G4bool verboseBestUnits = true;
|
||||
#ifdef G4MULTITHREADED
|
||||
G4int nThreads = 0;
|
||||
#endif
|
||||
for ( G4int i=1; i<argc; i=i+2 ) {
|
||||
if ( G4String(argv[i]) == "-m" ) macro = argv[i+1];
|
||||
else if ( G4String(argv[i]) == "-u" ) session = argv[i+1];
|
||||
#ifdef G4MULTITHREADED
|
||||
else if ( G4String(argv[i]) == "-t" ) {
|
||||
nThreads = G4UIcommand::ConvertToInt(argv[i+1]);
|
||||
}
|
||||
#endif
|
||||
else if ( G4String(argv[i]) == "-vDefault" ) {
|
||||
verboseBestUnits = false;
|
||||
--i; // this option is not followed with a parameter
|
||||
}
|
||||
else {
|
||||
PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect interactive mode (if no macro provided) and define UI session
|
||||
//
|
||||
G4UIExecutive* ui = nullptr;
|
||||
if ( ! macro.size() ) {
|
||||
ui = new G4UIExecutive(argc, argv, session);
|
||||
}
|
||||
|
||||
// Optionally: choose a different Random engine...
|
||||
// G4Random::setTheEngine(new CLHEP::MTwistEngine);
|
||||
|
||||
// Use G4SteppingVerboseWithUnits
|
||||
if ( verboseBestUnits ) {
|
||||
G4int precision = 4;
|
||||
G4SteppingVerbose::UseBestUnit(precision);
|
||||
}
|
||||
|
||||
// Construct the default run manager
|
||||
//
|
||||
auto* runManager =
|
||||
G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default);
|
||||
#ifdef G4MULTITHREADED
|
||||
if ( nThreads > 0 ) {
|
||||
runManager->SetNumberOfThreads(nThreads);
|
||||
}
|
||||
#endif
|
||||
|
||||
GeometryDescriptor * cw = new GeometryDescriptor();
|
||||
cw->addLayer(1, "G4_Pb", false);
|
||||
cw->addLayer(10, "G4_Si", true, 9);
|
||||
cw->addLayer(1, "G4_Pb", false);
|
||||
cw->addLayer(20, "G4_Si", true, 15);
|
||||
cw->addLayer(4, "G4_Pb", false);
|
||||
cw->addLayer(3, "G4_Si", true, 23);
|
||||
|
||||
|
||||
// Set mandatory initialization classes
|
||||
//
|
||||
auto detConstruction = new B4::DetectorConstruction(cw);
|
||||
|
||||
runManager->SetUserInitialization(detConstruction);
|
||||
|
||||
auto physicsList = new FTFP_BERT;
|
||||
runManager->SetUserInitialization(physicsList);
|
||||
|
||||
auto actionInitialization = new B4a::ActionInitialization(detConstruction);
|
||||
runManager->SetUserInitialization(actionInitialization);
|
||||
|
||||
actionInitialization->setGeneratorProperties(100, 1000, "e-");
|
||||
|
||||
// Initialize visualization
|
||||
//
|
||||
auto visManager = new G4VisExecutive;
|
||||
// G4VisExecutive can take a verbosity argument - see /vis/verbose guidance.
|
||||
// G4VisManager* visManager = new G4VisExecutive("Quiet");
|
||||
visManager->Initialize();
|
||||
|
||||
// Get the pointer to the User Interface manager
|
||||
auto UImanager = G4UImanager::GetUIpointer();
|
||||
|
||||
// Process macro or start UI session
|
||||
//
|
||||
if ( macro.size() ) {
|
||||
// batch mode
|
||||
G4String command = "/control/execute ";
|
||||
UImanager->ApplyCommand(command+macro);
|
||||
}
|
||||
else {
|
||||
// interactive mode : define UI session
|
||||
UImanager->ApplyCommand("/control/execute init_vis.mac");
|
||||
if (ui->IsGUI()) {
|
||||
UImanager->ApplyCommand("/control/execute gui.mac");
|
||||
}
|
||||
ui->SessionStart();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
// Job termination
|
||||
// Free the store: user actions, physics_list and detector_description are
|
||||
// owned and deleted by the run manager, so they should not be deleted
|
||||
// in the main() program !
|
||||
|
||||
delete visManager;
|
||||
delete runManager;
|
||||
delete cw;
|
||||
return 0;
|
||||
}
|
||||
|
||||
//later
|
||||
//class Runner {
|
||||
//public:
|
||||
// void initialize(GeometryDescriptor CW, bool gui=false);
|
||||
// void run(int nEvents, std::string partSpecies, double minEnergy, double maxEnergy);
|
||||
//}
|
||||
|
||||
void run(GeometryDescriptor CW, int nEvents, std::string partSpecies, double minEnergy, double maxEnergy, bool gui=false){
|
||||
|
||||
char* argv[]={(char*)"dummy"};
|
||||
|
||||
// Construct the default run manager
|
||||
//
|
||||
auto* runManager =
|
||||
G4RunManagerFactory::CreateRunManager(G4RunManagerType::Default);
|
||||
runManager->SetNumberOfThreads(1);
|
||||
|
||||
GeometryDescriptor * cw = &CW;
|
||||
G4String session;
|
||||
G4UIExecutive* ui = nullptr;
|
||||
if ( gui ) {
|
||||
ui = new G4UIExecutive((int)1, argv, session);
|
||||
}
|
||||
|
||||
// Set mandatory initialization classes
|
||||
//
|
||||
auto detConstruction = new B4::DetectorConstruction(cw);
|
||||
|
||||
runManager->SetUserInitialization(detConstruction);
|
||||
|
||||
auto physicsList = new FTFP_BERT;
|
||||
runManager->SetUserInitialization(physicsList);
|
||||
|
||||
auto actionInitialization = new B4a::ActionInitialization(detConstruction);
|
||||
runManager->SetUserInitialization(actionInitialization);
|
||||
|
||||
actionInitialization->setGeneratorProperties(minEnergy, maxEnergy, partSpecies);
|
||||
|
||||
// Initialize visualization
|
||||
//
|
||||
auto visManager = new G4VisExecutive;
|
||||
// G4VisExecutive can take a verbosity argument - see /vis/verbose guidance.
|
||||
// G4VisManager* visManager = new G4VisExecutive("Quiet");
|
||||
visManager->Initialize();
|
||||
|
||||
// Get the pointer to the User Interface manager
|
||||
auto UImanager = G4UImanager::GetUIpointer();
|
||||
|
||||
// Process macro or start UI session
|
||||
//
|
||||
|
||||
|
||||
if ( gui ) {
|
||||
// interactive mode : define UI session
|
||||
UImanager->ApplyCommand("/control/execute init_vis.mac");
|
||||
if (ui->IsGUI()) {
|
||||
UImanager->ApplyCommand("/control/execute gui.mac");
|
||||
}
|
||||
ui->SessionStart();
|
||||
delete ui;
|
||||
}
|
||||
else {
|
||||
G4String command = "/control/execute run1.mac";
|
||||
UImanager->ApplyCommand("/run/initialize");
|
||||
UImanager->ApplyCommand("/run/beamOn "+ std::to_string(nEvents));
|
||||
}
|
||||
|
||||
|
||||
// Job termination
|
||||
// Free the store: user actions, physics_list and detector_description are
|
||||
// owned and deleted by the run manager, so they should not be deleted
|
||||
// in the main() program !
|
||||
delete visManager;
|
||||
delete runManager;
|
||||
|
||||
}
|
||||
|
||||
int main(){
|
||||
|
||||
GeometryDescriptor cw;
|
||||
//to be moved
|
||||
cw.addLayer(1, "G4_Pb", false);
|
||||
cw.addLayer(10, "G4_Si", true, 9);
|
||||
cw.addLayer(1, "G4_Pb", false);
|
||||
cw.addLayer(20, "G4_Si", true, 15);
|
||||
cw.addLayer(4, "G4_Pb", false);
|
||||
cw.addLayer(3, "G4_Si", true, 23);
|
||||
|
||||
G4System builder(true);
|
||||
builder.init(cw);
|
||||
//builder.run_gui();
|
||||
std::vector<std::string> parts = {"e-"};
|
||||
builder.run_batch(10000,parts , 1, 100);
|
||||
|
||||
//run(cw, 1000, "e-", 1, 100, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo.....
|
||||
|
||||
-1111
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
// Export material-specific gamma cross sections for PbWO4 using G4EmCalculator.
|
||||
//
|
||||
// Builds a minimal geometry, initialises FTFP_BERT, forces the EM physics
|
||||
// tables to be built (via a fakeRun "/run/beamOn 0"), then sweeps photon
|
||||
// energy from 1 eV to 1e5 eV and writes a CSV of the mass attenuation
|
||||
// coefficients (cm^2/g) for each gamma process plus the total.
|
||||
|
||||
#include "GeometryDescriptor.hh"
|
||||
#include "G4System.hh"
|
||||
|
||||
#include "G4EmCalculator.hh"
|
||||
#include "G4Material.hh"
|
||||
#include "G4NistManager.hh"
|
||||
#include "G4Gamma.hh"
|
||||
#include "G4SystemOfUnits.hh"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const std::string materialName = "G4_PbWO4";
|
||||
const std::string outfile = (argc > 1) ? argv[1] : "pbwo4_xsec.csv";
|
||||
|
||||
// Log-spaced energy grid: 1 eV -> 1e5 eV, points per decade (small steps).
|
||||
const double eMin = 1.0 * eV;
|
||||
const double eMax = 1.0e7 * eV; // 10 MeV
|
||||
const int pointsPerDecade = 200;
|
||||
const int nDecades = 7; // 1e0 .. 1e7 eV
|
||||
const int nPoints = pointsPerDecade * nDecades + 1;
|
||||
|
||||
// Gamma processes registered by the EM standard physics in FTFP_BERT.
|
||||
const std::vector<std::string> processes = {"phot", "compt", "conv", "Rayl"};
|
||||
|
||||
// Minimal valid geometry: one active PbWO4 layer so init() has something to build.
|
||||
GeometryDescriptor gd;
|
||||
gd.addLayer(2.0, materialName, true, 1, 1);
|
||||
|
||||
G4System g4;
|
||||
g4.init(gd, -1);
|
||||
// fakeRun: builds EM physics tables without invoking RunAction (no ROOT file).
|
||||
g4.applyUICommand("/run/beamOn 0");
|
||||
|
||||
const G4Material* mat = G4NistManager::Instance()->FindOrBuildMaterial(materialName);
|
||||
if (mat == nullptr) {
|
||||
std::cerr << "Could not build material " << materialName << std::endl;
|
||||
return 1;
|
||||
}
|
||||
const double density_g_cm3 = mat->GetDensity() / (g / cm3);
|
||||
|
||||
G4EmCalculator calc;
|
||||
const G4ParticleDefinition* gamma = G4Gamma::Gamma();
|
||||
|
||||
std::ofstream out(outfile);
|
||||
out << "# material=" << materialName
|
||||
<< " density_g_per_cm3=" << density_g_cm3 << "\n";
|
||||
out << "# mass attenuation coefficients mu/rho in cm^2/g\n";
|
||||
out << "energy_eV,phot,compt,conv,rayl,total\n";
|
||||
out.setf(std::ios::scientific);
|
||||
out.precision(6);
|
||||
|
||||
const double logStep = (std::log10(eMax) - std::log10(eMin)) / (nPoints - 1);
|
||||
for (int i = 0; i < nPoints; ++i) {
|
||||
const double energy = std::pow(10.0, std::log10(eMin) + i * logStep);
|
||||
|
||||
double total_lin = 0.0; // linear attenuation, G4 units (1/mm)
|
||||
std::vector<double> mu; // per-process mass attenuation (cm^2/g)
|
||||
mu.reserve(processes.size());
|
||||
for (const auto& proc : processes) {
|
||||
// Macroscopic cross section Sigma [1/mm] = material-specific cross section.
|
||||
const double sigmaVol = calc.ComputeCrossSectionPerVolume(energy, gamma, proc, mat);
|
||||
total_lin += sigmaVol;
|
||||
// Convert to mass attenuation coefficient: (Sigma in 1/cm) / density.
|
||||
mu.push_back((sigmaVol * cm) / density_g_cm3);
|
||||
}
|
||||
const double total_mu = (total_lin * cm) / density_g_cm3;
|
||||
|
||||
out << energy / eV << "," << mu[0] << "," << mu[1] << ","
|
||||
<< mu[2] << "," << mu[3] << "," << total_mu << "\n";
|
||||
}
|
||||
|
||||
std::cout << "Wrote " << nPoints << " energy points for " << materialName
|
||||
<< " to " << outfile << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -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
|
||||
#
|
||||
@@ -32,6 +32,8 @@
|
||||
|
||||
#include "G4VUserPrimaryGeneratorAction.hh"
|
||||
#include "globals.hh"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class G4ParticleGun;
|
||||
class G4Event;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "G4UserRunAction.hh"
|
||||
#include "globals.hh"
|
||||
#include <vector>
|
||||
|
||||
class G4Run;
|
||||
|
||||
@@ -82,6 +83,8 @@ class RunAction : public G4UserRunAction
|
||||
mutable std::vector<int> hitLayer;
|
||||
mutable std::vector<int> hitCopyNumber;
|
||||
|
||||
mutable std::vector<int> stepChildIds;
|
||||
|
||||
mutable G4String filename;
|
||||
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# Macro file for the initialization of example B4
|
||||
# in interactive session
|
||||
#
|
||||
# Set some default verbose
|
||||
#
|
||||
/control/verbose 2
|
||||
/control/saveHistory
|
||||
/run/verbose 2
|
||||
#
|
||||
# Change the default number of threads (in multi-threaded mode)
|
||||
#/run/numberOfThreads 4
|
||||
#
|
||||
# Initialize kernel
|
||||
/run/initialize
|
||||
#
|
||||
# Visualization setting
|
||||
/control/execute vis.mac
|
||||
Submodule
+1
Submodule lib/geant4 added at 71c7582c45
-43
@@ -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");
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// ROOT macro file for plotting example B4 ntuple
|
||||
//
|
||||
// Can be run from ROOT session:
|
||||
// root[0] .x plotNtuple.C
|
||||
|
||||
{
|
||||
gROOT->Reset();
|
||||
gROOT->SetStyle("Plain");
|
||||
|
||||
// Draw histos filled by Geant4 simulation
|
||||
//
|
||||
|
||||
// Open file filled by Geant4 simulation
|
||||
TFile f("B4.root");
|
||||
|
||||
// Create a canvas and divide it into 2x2 pads
|
||||
TCanvas* c1 = new TCanvas("c1", "", 20, 20, 1000, 1000);
|
||||
c1->Divide(2,2);
|
||||
|
||||
// Get ntuple
|
||||
TNtuple* ntuple = (TNtuple*)f.Get("B4");
|
||||
|
||||
// Draw Eabs histogram in the pad 1
|
||||
c1->cd(1);
|
||||
ntuple->Draw("Eabs");
|
||||
|
||||
// Draw Labs histogram in the pad 2
|
||||
c1->cd(2);
|
||||
ntuple->Draw("Labs");
|
||||
|
||||
// Draw Egap histogram in the pad 3
|
||||
// with logaritmic scale for y ?? how to do this?
|
||||
c1->cd(3);
|
||||
gPad->SetLogy(1);
|
||||
ntuple->Draw("Egap");
|
||||
|
||||
// Draw Lgap histogram in the pad 4
|
||||
// with logaritmic scale for y ?? how to do this?
|
||||
c1->cd(4);
|
||||
gPad->SetLogy(1);
|
||||
ntuple->Draw("Egap");
|
||||
}
|
||||
Executable
+14
@@ -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/
|
||||
Executable
+40
@@ -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"
|
||||
@@ -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
|
||||
#
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "GeometryDescriptor.hh"
|
||||
#include "G4System.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int nEvents = 10;
|
||||
double energy_GeV = 1.0;
|
||||
|
||||
int seed = -1;
|
||||
if (const char* seedEnv = std::getenv("MINICALOSIM_SEED")) {
|
||||
try {
|
||||
std::size_t pos = 0;
|
||||
std::string seedStr(seedEnv);
|
||||
int parsed = std::stoi(seedStr, &pos);
|
||||
if (pos == seedStr.size()) seed = parsed;
|
||||
} catch (const std::exception&) {
|
||||
// Not a valid integer; fall back to time-based seed.
|
||||
}
|
||||
}
|
||||
|
||||
if (argc > 3) {
|
||||
std::cerr << "Usage: run_pbwo4 [nEvents] [energy_GeV]" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (argc >= 2) {
|
||||
try {
|
||||
nEvents = std::stoi(argv[1]);
|
||||
if (nEvents <= 0) throw std::invalid_argument("must be positive");
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Invalid nEvents '" << argv[1] << "': " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (argc == 3) {
|
||||
try {
|
||||
energy_GeV = std::stod(argv[2]);
|
||||
if (energy_GeV <= 0) throw std::invalid_argument("must be positive");
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Invalid energy_GeV '" << argv[2] << "': " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string outfile = "pbwo4_" + std::to_string(nEvents) + "events_hits.root";
|
||||
|
||||
GeometryDescriptor gd;
|
||||
gd.addLayer(20.0, "G4_PbWO4", true, 10, 10);
|
||||
|
||||
G4System g4;
|
||||
g4.init(gd, seed);
|
||||
g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile);
|
||||
|
||||
std::cout << "Saved " << nEvents << " events to " << outfile << std::endl;
|
||||
return 0;
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
#include "GeometryDescriptor.hh"
|
||||
#include "G4System.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
GeometryDescriptor buildPbScint() {
|
||||
GeometryDescriptor gd;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
gd.addLayer(0.2, "G4_Pb", false);
|
||||
gd.addLayer(0.3, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||
}
|
||||
return gd;
|
||||
}
|
||||
|
||||
GeometryDescriptor buildFeScint() {
|
||||
GeometryDescriptor gd;
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
gd.addLayer(1.0, "G4_Fe", false);
|
||||
gd.addLayer(0.5, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||
}
|
||||
return gd;
|
||||
}
|
||||
|
||||
GeometryDescriptor buildWScintEcal() {
|
||||
GeometryDescriptor gd;
|
||||
// Thin homogeneous preshower: ~0.05 X0, sees MIPs/shower-start, not containment.
|
||||
gd.addLayer(2.0, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||
for (int i = 0; i < 75; ++i) {
|
||||
gd.addLayer(0.1, "G4_W", false);
|
||||
gd.addLayer(0.2, "G4_PLASTIC_SC_VINYLTOLUENE", true, 10, 10);
|
||||
}
|
||||
return gd;
|
||||
}
|
||||
|
||||
GeometryDescriptor buildPbLAr() {
|
||||
GeometryDescriptor gd;
|
||||
for (int i = 0; i < 70; ++i) {
|
||||
gd.addLayer(0.2, "G4_Pb", false);
|
||||
gd.addLayer(0.4, "G4_lAr", true, 10, 10);
|
||||
}
|
||||
return gd;
|
||||
}
|
||||
|
||||
// Ordered (not alphabetical) so configs can also be selected by 1-based index.
|
||||
const std::vector<std::pair<std::string, GeometryDescriptor (*)()>> kConfigs = {
|
||||
{"pb_scint", buildPbScint},
|
||||
{"fe_scint", buildFeScint},
|
||||
{"w_scint_ecal", buildWScintEcal},
|
||||
{"pb_lar", buildPbLAr},
|
||||
};
|
||||
|
||||
void printUsage() {
|
||||
std::cerr << "Usage: run_sampling [configName|configIndex] [nEvents] [energy_GeV]" << std::endl;
|
||||
std::cerr << "Available configs:" << std::endl;
|
||||
for (std::size_t i = 0; i < kConfigs.size(); ++i) {
|
||||
std::cerr << " " << (i + 1) << ": " << kConfigs[i].first << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::string configName = kConfigs.front().first;
|
||||
int nEvents = 10;
|
||||
double energy_GeV = 1.0;
|
||||
|
||||
if (argc > 4) {
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
if (argc >= 2) {
|
||||
configName = argv[1];
|
||||
}
|
||||
if (argc >= 3) {
|
||||
try {
|
||||
nEvents = std::stoi(argv[2]);
|
||||
if (nEvents <= 0) throw std::invalid_argument("must be positive");
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Invalid nEvents '" << argv[2] << "': " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (argc == 4) {
|
||||
try {
|
||||
energy_GeV = std::stod(argv[3]);
|
||||
if (energy_GeV <= 0) throw std::invalid_argument("must be positive");
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Invalid energy_GeV '" << argv[3] << "': " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
GeometryDescriptor (*builder)() = nullptr;
|
||||
try {
|
||||
std::size_t pos = 0;
|
||||
int index = std::stoi(configName, &pos);
|
||||
if (pos == configName.size() && index >= 1 && index <= static_cast<int>(kConfigs.size())) {
|
||||
configName = kConfigs[index - 1].first;
|
||||
builder = kConfigs[index - 1].second;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// Not a valid index; fall through to name lookup below.
|
||||
}
|
||||
if (builder == nullptr) {
|
||||
for (const auto& kv : kConfigs) {
|
||||
if (kv.first == configName) {
|
||||
builder = kv.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (builder == nullptr) {
|
||||
std::cerr << "Unknown config '" << configName << "'." << std::endl;
|
||||
printUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int seed = -1;
|
||||
if (const char* seedEnv = std::getenv("MINICALOSIM_SEED")) {
|
||||
try {
|
||||
std::size_t pos = 0;
|
||||
std::string seedStr(seedEnv);
|
||||
int parsed = std::stoi(seedStr, &pos);
|
||||
if (pos == seedStr.size()) seed = parsed;
|
||||
} catch (const std::exception&) {
|
||||
// Not a valid integer; fall back to time-based seed.
|
||||
}
|
||||
}
|
||||
|
||||
GeometryDescriptor gd = builder();
|
||||
|
||||
std::string outfile = "sampling_" + configName + "_" + std::to_string(nEvents) + "events_hits.root";
|
||||
|
||||
G4System g4;
|
||||
g4.init(gd, seed);
|
||||
g4.run_batch(nEvents, {"e-"}, energy_GeV, energy_GeV, outfile);
|
||||
|
||||
std::cout << "Saved " << nEvents << " events to " << outfile << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -70,6 +70,7 @@ void ActionInitialization::Build() const
|
||||
eventAction->setPrimaryGeneratorAction(gen);
|
||||
SetUserAction(eventAction);
|
||||
auto steppingAction = new SteppingAction(eventAction);
|
||||
steppingAction->setRunAction(runact);
|
||||
SetUserAction(steppingAction);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,50 @@ RunAction::RunAction()
|
||||
analysisManager->CreateNtupleIColumn("sensor_layer",hitLayer);
|
||||
analysisManager->CreateNtupleIColumn("sensor_copy_number",hitCopyNumber);
|
||||
|
||||
analysisManager->FinishNtuple();
|
||||
|
||||
// Steps ntuple (one row per step, ntuple id=1)
|
||||
analysisManager->CreateNtuple("Steps", "Steps");
|
||||
analysisManager->CreateNtupleIColumn("event_id"); // col 0
|
||||
analysisManager->CreateNtupleIColumn("track_id"); // col 1
|
||||
analysisManager->CreateNtupleIColumn("step_no"); // col 2
|
||||
analysisManager->CreateNtupleIColumn("pdg"); // col 3
|
||||
analysisManager->CreateNtupleDColumn("pre_x"); // col 4
|
||||
analysisManager->CreateNtupleDColumn("pre_y"); // col 5
|
||||
analysisManager->CreateNtupleDColumn("pre_z"); // col 6
|
||||
analysisManager->CreateNtupleDColumn("pre_E"); // col 7
|
||||
analysisManager->CreateNtupleDColumn("post_x"); // col 8
|
||||
analysisManager->CreateNtupleDColumn("post_y"); // col 9
|
||||
analysisManager->CreateNtupleDColumn("post_z"); // col 10
|
||||
analysisManager->CreateNtupleDColumn("post_E"); // col 11
|
||||
analysisManager->CreateNtupleDColumn("edep"); // col 12 [MeV]
|
||||
analysisManager->CreateNtupleDColumn("step_length"); // col 13 [mm]
|
||||
analysisManager->CreateNtupleSColumn("process"); // col 14
|
||||
analysisManager->CreateNtupleIColumn("layer_id"); // col 15 (-1 = outside all layers)
|
||||
analysisManager->CreateNtupleSColumn("material"); // col 16
|
||||
analysisManager->CreateNtupleDColumn("pre_dx"); // col 17
|
||||
analysisManager->CreateNtupleDColumn("pre_dy"); // col 18
|
||||
analysisManager->CreateNtupleDColumn("pre_dz"); // col 19
|
||||
analysisManager->CreateNtupleDColumn("Bx"); // col 20 [T]
|
||||
analysisManager->CreateNtupleDColumn("By"); // col 21 [T]
|
||||
analysisManager->CreateNtupleDColumn("Bz"); // col 22 [T]
|
||||
analysisManager->CreateNtupleDColumn("Ex"); // col 23 [V/m]
|
||||
analysisManager->CreateNtupleDColumn("Ey"); // col 24 [V/m]
|
||||
analysisManager->CreateNtupleDColumn("Ez"); // col 25 [V/m]
|
||||
analysisManager->CreateNtupleIColumn("child_track_ids", stepChildIds); // col 26 (variable-length array)
|
||||
analysisManager->CreateNtupleDColumn("post_dx"); // col 27
|
||||
analysisManager->CreateNtupleDColumn("post_dy"); // col 28
|
||||
analysisManager->CreateNtupleDColumn("post_dz"); // col 29
|
||||
analysisManager->CreateNtupleSColumn("next_volume"); // col 30 (volume entered if step ended at a boundary, else "")
|
||||
analysisManager->CreateNtupleSColumn("next_material"); // col 31 (material entered if step ended at a boundary, else "")
|
||||
analysisManager->FinishNtuple();
|
||||
|
||||
// Spawning ntuple (one row per secondary born, ntuple id=2)
|
||||
analysisManager->CreateNtuple("Spawning", "Spawning");
|
||||
analysisManager->CreateNtupleIColumn("event_id"); // col 0
|
||||
analysisManager->CreateNtupleIColumn("parent_track_id"); // col 1
|
||||
analysisManager->CreateNtupleIColumn("parent_step_no"); // col 2
|
||||
analysisManager->CreateNtupleIColumn("child_track_id"); // col 3
|
||||
analysisManager->FinishNtuple();
|
||||
}
|
||||
|
||||
|
||||
+121
-7
@@ -33,6 +33,13 @@
|
||||
|
||||
#include "G4Step.hh"
|
||||
#include "G4RunManager.hh"
|
||||
#include "G4AnalysisManager.hh"
|
||||
#include "G4Track.hh"
|
||||
#include "G4ParticleDefinition.hh"
|
||||
#include "G4TransportationManager.hh"
|
||||
#include "G4FieldManager.hh"
|
||||
#include "G4Field.hh"
|
||||
#include "G4SystemOfUnits.hh"
|
||||
|
||||
using namespace B4;
|
||||
|
||||
@@ -68,16 +75,123 @@ void SteppingAction::UserSteppingAction(const G4Step* step)
|
||||
|
||||
|
||||
auto sensor = DetectorConstruction::getDetectorConstruction()->getGeometryDescriptor()->getSensorByVolume(volume);
|
||||
if(sensor == nullptr){
|
||||
//can simply be not an active volume, so no need to throw an error
|
||||
//G4cout << "Sensor not found in" << volume->GetName() << G4endl; //DEBUG
|
||||
return;
|
||||
if(sensor != nullptr){
|
||||
//G4cout << "Sensor found in " << volume->GetName() << " with copy number " << volume->GetCopyNo()<< G4endl; //DEBUG
|
||||
sensor->energy += edep;
|
||||
}
|
||||
//G4cout << "Sensor found in " << volume->GetName() << " with copy number " << volume->GetCopyNo()<< G4endl; //DEBUG
|
||||
sensor->energy += edep;
|
||||
//find sensor by volume copy number
|
||||
|
||||
// Record step kinematics for every step
|
||||
auto analysisManager = G4AnalysisManager::Instance();
|
||||
auto pre = step->GetPreStepPoint();
|
||||
auto post = step->GetPostStepPoint();
|
||||
auto track = step->GetTrack();
|
||||
int evtId = G4RunManager::GetRunManager()->GetCurrentEvent()->GetEventID();
|
||||
|
||||
// Write one Spawning row per secondary born in this step (track IDs pre-assigned);
|
||||
// also populate stepChildIds for the child_track_ids vector column in Steps.
|
||||
fRunAction->stepChildIds.clear();
|
||||
const auto* secondaries = step->GetSecondaryInCurrentStep();
|
||||
if (secondaries) {
|
||||
for (const G4Track* sec : *secondaries) {
|
||||
fRunAction->stepChildIds.push_back(sec->GetTrackID());
|
||||
analysisManager->FillNtupleIColumn(2, 0, evtId);
|
||||
analysisManager->FillNtupleIColumn(2, 1, track->GetTrackID());
|
||||
analysisManager->FillNtupleIColumn(2, 2, track->GetCurrentStepNumber());
|
||||
analysisManager->FillNtupleIColumn(2, 3, sec->GetTrackID());
|
||||
analysisManager->AddNtupleRow(2);
|
||||
}
|
||||
}
|
||||
|
||||
analysisManager->FillNtupleIColumn(1, 0, evtId);
|
||||
analysisManager->FillNtupleIColumn(1, 1, track->GetTrackID());
|
||||
analysisManager->FillNtupleIColumn(1, 2, track->GetCurrentStepNumber());
|
||||
analysisManager->FillNtupleIColumn(1, 3, track->GetDefinition()->GetPDGEncoding());
|
||||
analysisManager->FillNtupleDColumn(1, 4, pre->GetPosition().x());
|
||||
analysisManager->FillNtupleDColumn(1, 5, pre->GetPosition().y());
|
||||
analysisManager->FillNtupleDColumn(1, 6, pre->GetPosition().z());
|
||||
analysisManager->FillNtupleDColumn(1, 7, pre->GetKineticEnergy());
|
||||
analysisManager->FillNtupleDColumn(1, 8, post->GetPosition().x());
|
||||
analysisManager->FillNtupleDColumn(1, 9, post->GetPosition().y());
|
||||
analysisManager->FillNtupleDColumn(1, 10, post->GetPosition().z());
|
||||
analysisManager->FillNtupleDColumn(1, 11, post->GetKineticEnergy());
|
||||
|
||||
// A: energy deposited in medium at this step (≠ pre_E - post_E when secondaries are created)
|
||||
analysisManager->FillNtupleDColumn(1, 12, edep);
|
||||
|
||||
// B: geometric step length
|
||||
analysisManager->FillNtupleDColumn(1, 13, step->GetStepLength());
|
||||
|
||||
// D: process that ended this step
|
||||
G4String processName = "";
|
||||
const auto* postProc = post->GetProcessDefinedStep();
|
||||
if (postProc) processName = postProc->GetProcessName();
|
||||
analysisManager->FillNtupleSColumn(1, 14, processName);
|
||||
|
||||
// E: layer index (-1 if outside all layers) and material name at pre-step point
|
||||
int layerId = -1;
|
||||
const auto& layers = cw->getLayers();
|
||||
for (int i = 0; i < (int)layers.size(); i++) {
|
||||
if (layers[i].physicalVolume == volume) { layerId = i; break; }
|
||||
}
|
||||
G4String materialName = pre->GetMaterial() ? pre->GetMaterial()->GetName() : "";
|
||||
analysisManager->FillNtupleIColumn(1, 15, layerId);
|
||||
analysisManager->FillNtupleSColumn(1, 16, materialName);
|
||||
|
||||
// F: pre-step momentum direction (unit vector)
|
||||
const auto dir = pre->GetMomentumDirection();
|
||||
analysisManager->FillNtupleDColumn(1, 17, dir.x());
|
||||
analysisManager->FillNtupleDColumn(1, 18, dir.y());
|
||||
analysisManager->FillNtupleDColumn(1, 19, dir.z());
|
||||
|
||||
// G: post-step momentum direction (unit vector)
|
||||
const auto postDir = post->GetMomentumDirection();
|
||||
analysisManager->FillNtupleDColumn(1, 27, postDir.x());
|
||||
analysisManager->FillNtupleDColumn(1, 28, postDir.y());
|
||||
analysisManager->FillNtupleDColumn(1, 29, postDir.z());
|
||||
|
||||
// H: volume/material the track is about to enter, if this step ended at a
|
||||
// geometric boundary. post->GetPhysicalVolume() still refers to the volume
|
||||
// the step occurred in, not the one being entered, so use track->GetNextVolume().
|
||||
G4String nextVolumeName = "";
|
||||
G4String nextMaterialName = "";
|
||||
if (post->GetStepStatus() == fGeomBoundary) {
|
||||
auto nextVolume = track->GetNextVolume();
|
||||
if (nextVolume) {
|
||||
nextVolumeName = nextVolume->GetName();
|
||||
auto nextMaterial = nextVolume->GetLogicalVolume()->GetMaterial();
|
||||
if (nextMaterial) nextMaterialName = nextMaterial->GetName();
|
||||
}
|
||||
}
|
||||
analysisManager->FillNtupleSColumn(1, 30, nextVolumeName);
|
||||
analysisManager->FillNtupleSColumn(1, 31, nextMaterialName);
|
||||
|
||||
// Field: B [T] and E [V/m] at pre-step position; zero if no field is registered
|
||||
G4double Bx=0, By=0, Bz=0, Ex=0, Ey=0, Ez=0;
|
||||
const auto* fm = G4TransportationManager::GetTransportationManager()->GetFieldManager();
|
||||
if (fm && fm->DoesFieldExist()) {
|
||||
const G4Field* field = fm->GetDetectorField();
|
||||
if (field) {
|
||||
const G4double point[4] = {pre->GetPosition().x(), pre->GetPosition().y(),
|
||||
pre->GetPosition().z(), pre->GetGlobalTime()};
|
||||
G4double fieldVal[6] = {0,0,0,0,0,0};
|
||||
field->GetFieldValue(point, fieldVal);
|
||||
Bx = fieldVal[0] / tesla;
|
||||
By = fieldVal[1] / tesla;
|
||||
Bz = fieldVal[2] / tesla;
|
||||
Ex = fieldVal[3] / (volt/m);
|
||||
Ey = fieldVal[4] / (volt/m);
|
||||
Ez = fieldVal[5] / (volt/m);
|
||||
}
|
||||
}
|
||||
analysisManager->FillNtupleDColumn(1, 20, Bx);
|
||||
analysisManager->FillNtupleDColumn(1, 21, By);
|
||||
analysisManager->FillNtupleDColumn(1, 22, Bz);
|
||||
analysisManager->FillNtupleDColumn(1, 23, Ex);
|
||||
analysisManager->FillNtupleDColumn(1, 24, Ey);
|
||||
analysisManager->FillNtupleDColumn(1, 25, Ez);
|
||||
// col 26 child_track_ids: vector column, auto-read from fRunAction->stepChildIds at AddNtupleRow
|
||||
|
||||
analysisManager->AddNtupleRow(1);
|
||||
}
|
||||
|
||||
//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo......
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(minicalosim-superbuild NONE)
|
||||
|
||||
include(ExternalProject)
|
||||
include(ProcessorCount)
|
||||
|
||||
ProcessorCount(NPROC)
|
||||
if(NOT NPROC OR NPROC EQUAL 0)
|
||||
set(NPROC 4)
|
||||
endif()
|
||||
# Cap at 16 to avoid memory exhaustion on large machines (mirrors Dockerfile)
|
||||
if(NPROC GREATER 16)
|
||||
set(NPROC 16)
|
||||
endif()
|
||||
|
||||
set(G4_INSTALL ${CMAKE_BINARY_DIR}/geant4-install)
|
||||
set(G4_SRC ${CMAKE_SOURCE_DIR}/../lib/geant4)
|
||||
set(MINI_SRC ${CMAKE_SOURCE_DIR}/..)
|
||||
|
||||
option(GEANT4_INSTALL_DATA "Download Geant4 physics data files (~2 GB)" ON)
|
||||
option(GEANT4_USE_GDML "Build Geant4 with GDML support (requires Xerces-C)" ON)
|
||||
|
||||
# ── Geant4 ────────────────────────────────────────────────────────────────────
|
||||
ExternalProject_Add(geant4
|
||||
SOURCE_DIR ${G4_SRC}
|
||||
BINARY_DIR ${CMAKE_BINARY_DIR}/geant4-build
|
||||
INSTALL_DIR ${G4_INSTALL}
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DGEANT4_INSTALL_DATA=${GEANT4_INSTALL_DATA}
|
||||
-DGEANT4_USE_GDML=${GEANT4_USE_GDML}
|
||||
-DGEANT4_BUILD_MULTITHREADED=OFF
|
||||
-DGEANT4_BUILD_TLS_MODEL=global-dynamic
|
||||
BUILD_COMMAND cmake --build <BINARY_DIR> --parallel ${NPROC}
|
||||
INSTALL_COMMAND cmake --install <BINARY_DIR>
|
||||
)
|
||||
|
||||
# ── minicalosim (run_pbwo4) ───────────────────────────────────────────────────
|
||||
ExternalProject_Add(run_pbwo4
|
||||
SOURCE_DIR ${MINI_SRC}
|
||||
BINARY_DIR ${CMAKE_BINARY_DIR}/minicalosim-build
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_PREFIX_PATH=${G4_INSTALL}
|
||||
-DWITH_GEANT4_UIVIS=OFF
|
||||
BUILD_COMMAND cmake --build <BINARY_DIR> --target run_pbwo4 --parallel ${NPROC}
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
${CMAKE_BINARY_DIR}/minicalosim-build/run_pbwo4
|
||||
${CMAKE_BINARY_DIR}/run_pbwo4
|
||||
DEPENDS geant4
|
||||
)
|
||||
|
||||
# ── minicalosim (run_sampling) ─────────────────────────────────────────────
|
||||
# Shares run_pbwo4's BINARY_DIR (already configured against G4_INSTALL) and
|
||||
# depends on it so the two ExternalProjects don't reconfigure that dir concurrently.
|
||||
ExternalProject_Add(run_sampling
|
||||
SOURCE_DIR ${MINI_SRC}
|
||||
BINARY_DIR ${CMAKE_BINARY_DIR}/minicalosim-build
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_PREFIX_PATH=${G4_INSTALL}
|
||||
-DWITH_GEANT4_UIVIS=OFF
|
||||
BUILD_COMMAND cmake --build <BINARY_DIR> --target run_sampling --parallel ${NPROC}
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
${CMAKE_BINARY_DIR}/minicalosim-build/run_sampling
|
||||
${CMAKE_BINARY_DIR}/run_sampling
|
||||
DEPENDS run_pbwo4
|
||||
)
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user