From 47919f46aa498a383e05ec9fe155b07c82dea222 Mon Sep 17 00:00:00 2001 From: Jan Kieseler Date: Fri, 10 Oct 2025 13:18:59 +0200 Subject: [PATCH] docu --- bind/G4Calo.py | 250 ++++++++++++++++++++++++++++++++++++++++++--- bind/minipandas.py | 71 ++++++++++++- 2 files changed, 302 insertions(+), 19 deletions(-) diff --git a/bind/G4Calo.py b/bind/G4Calo.py index 6a3f5ff..66d28a8 100644 --- a/bind/G4Calo.py +++ b/bind/G4Calo.py @@ -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) diff --git a/bind/minipandas.py b/bind/minipandas.py index 22dc5cb..f1a5f5f 100644 --- a/bind/minipandas.py +++ b/bind/minipandas.py @@ -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 (_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,9 +254,33 @@ 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: raise ValueError("minipandas.concat: empty iterable.")