This commit is contained in:
Jan Kieseler
2025-10-10 13:18:59 +02:00
parent e53e2b0af4
commit 47919f46aa
2 changed files with 302 additions and 19 deletions
+234 -16
View File
@@ -1,3 +1,153 @@
"""
============================================================
G4Calo Public Interface Functions
============================================================
This module provides two main functions intended for student use:
- run_batch(...)
- display_event(...)
They allow running Geant4 calorimeter simulations and visualising
individual events using a pandas-like, lightweight data structure
(MiniFrame) built for teaching and analysis.
------------------------------------------------------------
Function: run_batch(...)
------------------------------------------------------------
Purpose
-------
Run a full Geant4 simulation batch with automatic parallelisation
and return the results as a lightweight, pandas-like table (MiniFrame).
Overview
--------
`run_batch` distributes a requested number of Geant4 events across
available CPU cores, runs all mini-batches in parallel, collects their
temporary ROOT output files, converts them to numpy arrays, and
finally assembles everything into a single MiniFrame for analysis
or visualisation.
Parameters
----------
gd : GeometryDescriptor
The calorimeter geometry to simulate.
nEvents : int
Total number of events to simulate. Must be > 0.
particleSpec : str or list[str]
Particle type(s) to generate, e.g. "gamma" or ["e-", "pi+"].
minEnergy_GeV : float
Minimum kinetic energy in GeV for the primary particles.
maxEnergy_GeV : float, optional
Maximum kinetic energy in GeV. If negative (default), identical
to minEnergy_GeV.
filename : str, optional
Optional output file name for storing the resulting MiniFrame
using pickle format. If empty, no file is written.
no_mp : bool, optional
If True, disables multiprocessing and runs all events sequentially.
Useful for debugging.
manual_seed : int, optional
Optional random-seed override. If < 0 (default), an internal per-core
seed sequence is generated and persisted between runs.
Returns
-------
MiniFrame
A table-like object containing one row per simulated event.
Each row stores scalar quantities (e.g. true_energy, total_dep_energy)
and fixed-size arrays for each sensor-level variable
(sensor_energy, sensor_x, sensor_y, ...).
Notes
-----
- Automatically chooses the number of cores so that each core processes
at least ~80 events.
- Temporary ROOT files are written to /dev/shm or /tmp and removed after merging.
- Prints timing information for simulation and concatenation steps.
- For deterministic results, specify manual_seed.
Example
-------
>>> df = run_batch(gd, nEvents=1000, particleSpec="gamma", minEnergy_GeV=1.0)
>>> print(df.describe())
>>> df.to_pickle("results.pkl")
------------------------------------------------------------
Function: display_event(...)
------------------------------------------------------------
Purpose
-------
Visualise the energy deposition of a single simulated event in 3D
using Plotly.
Overview
--------
`display_event` performs a one-event Geant4 simulation with the given
geometry, retrieves deposited sensor energies, and renders a 3D view
of the calorimeter including materials, layers, sensors, and the
incoming particle direction.
Parameters
----------
gd : GeometryDescriptor
The calorimeter geometry descriptor (modified in-place).
particleSpec : str or list[str]
Particle type(s) to simulate, e.g. "e-", "pi+", "gamma".
energy : float
Particle kinetic energy in GeV.
logE : bool, optional
If True, use logarithmic scaling for sensor opacity
(currently not implemented).
renderer : str, optional
Optional Plotly renderer ("browser", "notebook", "png", ...).
If omitted, uses the default interactive renderer.
seed : int, optional
Optional random seed for reproducibility.
outfile : str, optional
If provided, saves the figure as an interactive HTML file
instead of displaying it.
Returns
-------
None — the function displays or saves the visualisation.
What the Plot Shows
-------------------
- Coloured cubes for each material layer (colour legend included)
- Black semi-transparent cubes for sensors, opacity ∝ deposited energy
- Red arrow showing incoming particle direction
- Axes labelled in mm (x, y, z following HEP conventions)
- Interactive rotation and zoom supported
Example
-------
>>> display_event(gd, particleSpec="gamma", energy=1.0, renderer="browser")
Typical Workflow
----------------
>>> df = run_batch(gd, 200, "e-", 1.0)
>>> print(df.describe())
>>> display_event(gd, "e-", 1.0, renderer="browser")
These two functions form the primary interface for student exercises:
`run_batch` to simulate, and `display_event` to visualise results.
All heavy lifting (parallelisation, I/O, and conversion) is handled
internally so that focus remains on physics interpretation.
"""
import threading
import time
import warnings
@@ -6,16 +156,6 @@ import sys
from minicalo_tools import TempFileManager
from concurrent.futures import ThreadPoolExecutor
'''
give G4Calo package an executable that takes input and output json files names
This calls the actual Geant stuff.
The input are all the arguments and a geometry descriptor
The output is the data frame as well as the changed geometry descriptor
The user only interacts with a wrapper that calls this executable through a system call
'''
from minicalo import GeometryDescriptor
@@ -199,12 +339,52 @@ def run_batch(
filename: str = "",
no_mp: bool = False,
manual_seed : int = -1):
'''
splits the batch in jobs depending on how many cores are available and runs mini batches in parallel
this will create threads.
If no_mp is set to True, it will run the batch in a single thread
The seeding is not thread safe (unless provided by hand)
'''
"""
Run a full Geant4 simulation batch with automatic parallelisation.
Distributes the requested number of events across CPU cores, runs each
mini-batch in parallel, collects temporary ROOT files, converts them to
numpy arrays, and merges everything into a lightweight, pandas-like
MiniFrame.
Parameters
----------
gd : GeometryDescriptor
Calorimeter geometry to simulate.
nEvents : int
Total number of events to simulate.
particleSpec : str or list[str]
Particle type(s), e.g. "gamma" or ["e-", "pi+"].
minEnergy_GeV : float
Minimum kinetic energy in GeV.
maxEnergy_GeV : float, optional
Maximum kinetic energy in GeV. If negative, equals minEnergy_GeV.
filename : str, optional
Optional pickle filename for saving results.
no_mp : bool, optional
Disable multiprocessing (run single-threaded) for debugging.
manual_seed : int, optional
Optional random seed. If < 0, seeds are generated automatically.
Returns
-------
MiniFrame
Table-like structure with one row per event, containing both scalar
values and fixed-size sensor arrays.
Notes
-----
- Automatically chooses the number of cores to balance workload.
- Temporary files are written to /dev/shm or /tmp and removed after merging.
- Prints timing information for both simulation and data assembly.
- For reproducibility, set manual_seed.
Example
-------
>>> df = run_batch(gd, 1000, "gamma", 1.0)
>>> print(df.describe())
>>> df.to_pickle("results.pkl")
"""
assert nEvents > 0
assert minEnergy_GeV > 0
@@ -346,6 +526,44 @@ def display_event(gd : GeometryDescriptor,
particleSpec,
energy: float,
logE = False, renderer=None, seed = -1, outfile : str = ""):
"""
Visualise a single simulated event in 3D using Plotly.
Runs a one-event Geant4 simulation with the provided geometry, extracts
the deposited sensor energies, and renders an interactive 3D view of
the calorimeter with materials, layers, and energy deposition.
Parameters
----------
gd : GeometryDescriptor
The calorimeter geometry (modified in place by the simulation).
particleSpec : str or list[str]
Particle type(s), e.g. "e-", "pi+", "gamma".
energy : float
Particle kinetic energy in GeV.
logE : bool, optional
Use logarithmic opacity scaling (currently not implemented).
renderer : str, optional
Plotly renderer ("browser", "notebook", etc.). Default: interactive.
seed : int, optional
Random seed for reproducibility.
outfile : str, optional
If set, saves the figure as an interactive HTML file.
Returns
-------
None — displays or saves a 3D Plotly figure.
Example
-------
>>> display_event(gd, "gamma", 1.0, renderer="browser")
The plot shows:
- Coloured cubes for material layers
- Semi-transparent black cubes for sensors (opacity ∝ deposited energy)
- Red arrow for incoming particle direction
- Interactive rotation and zoom
"""
gd = _fill_event(gd, particleSpec, energy)
+67 -2
View File
@@ -1,4 +1,45 @@
# minipandas.py
"""
MiniFrame: a minimal, NumPy-backed, pandas-like table.
Each column is a NumPy array sharing the same row length (axis 0).
Columns may be:
• scalar columns: shape (N,)
• vector columns: shape (N, M) with fixed M across the frame
Supports familiar pandas patterns:
• len(mf), mf.shape, mf.keys(), "col" in mf
• column access: mf["col"] -> ndarray
• row selection: mf[idx], mf[:N], mf[mask] -> MiniFrame (views where possible)
• head(), copy(), describe(), to_pandas(), to_pickle()/read_pickle()
Parameters
----------
data : Mapping[str, np.ndarray or scalar]
Column data. Scalars are broadcast to length N.
length : int, optional (keyword-only)
Row count N to use when all provided values are scalars. If omitted,
N is inferred from the first non-scalar column.
Notes
-----
- Only 1D (N,) and 2D (N, M) columns are supported.
- Vector columns are expanded to wide columns (<name>_0..M-1) by to_pandas().
- Properties:
scalar_cols -> list[str] # columns with ndim == 1
vector_cols -> list[str] # columns with ndim == 2
vector_length(col) -> int or None
Examples
--------
>>> mf = MiniFrame({"run": 7, "event": np.arange(5), "energy": np.random.rand(5, 4)})
>>> mf[:3] # first 3 rows
>>> mf["energy"] # raw ndarray (5, 4)
>>> mf.describe() # quick stats
>>> df = mf.to_pandas() # convert to pandas (energy -> energy_0.._3)
>>> mf.to_pickle("out.pkl")
>>> mf2 = MiniFrame.read_pickle("out.pkl")
"""
from __future__ import annotations
import numpy as np
@@ -213,8 +254,32 @@ def concat(
copy: bool = False,
) -> MiniFrame:
"""
Concatenate MiniFrames along rows (axis=0). Column sets and per-column shapes must match.
Args match pandas.concat subset for familiarity; only axis=0 is supported.
Concatenate MiniFrames along rows (axis=0), pandas-style.
This is a lightweight analogue of pandas.concat for MiniFrame objects.
All frames must have identical column sets and per-column shapes
(except for the row dimension).
Parameters
----------
objs : Iterable[MiniFrame]
Frames to concatenate.
axis : int, default 0
Only axis=0 (row-wise) is supported.
ignore_index : bool, default False
Accepted for API similarity; MiniFrame has no explicit index (no-op).
copy : bool, default False
If True, return a deep copy of the result.
Returns
-------
MiniFrame
The row-wise concatenation of the input frames.
Examples
--------
>>> mf = mpd.concat([mf1, mf2])
>>> mf.shape
"""
objs = list(objs)
if not objs: