From 2d0db0b3e7ba04634c971eb03e183a146e068d6b Mon Sep 17 00:00:00 2001 From: Luigi Pertoldi Date: Thu, 11 Sep 2025 18:13:25 +0200 Subject: [PATCH 1/4] add Calorimeter detector and output scheme --- include/RMGCalorimeterDetector.hh | 58 +++++++++ include/RMGCalorimeterOutputScheme.hh | 65 ++++++++++ include/RMGDetectorMetadata.hh | 1 + src/CMakeLists.txt | 4 + src/RMGCalorimeterDetector.cc | 104 +++++++++++++++ src/RMGCalorimeterOutputScheme.cc | 174 ++++++++++++++++++++++++++ src/RMGHardware.cc | 6 + 7 files changed, 412 insertions(+) create mode 100644 include/RMGCalorimeterDetector.hh create mode 100644 include/RMGCalorimeterOutputScheme.hh create mode 100644 src/RMGCalorimeterDetector.cc create mode 100644 src/RMGCalorimeterOutputScheme.cc diff --git a/include/RMGCalorimeterDetector.hh b/include/RMGCalorimeterDetector.hh new file mode 100644 index 000000000..a7819091b --- /dev/null +++ b/include/RMGCalorimeterDetector.hh @@ -0,0 +1,58 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#ifndef _RMG_CALORIMETER_DETECTOR_HH_ +#define _RMG_CALORIMETER_DETECTOR_HH_ + +#include +#include + +#include "G4VSensitiveDetector.hh" + +#include "RMGDetectorHit.hh" + +class G4Step; +class G4HCofThisEvent; +class G4TouchableHistory; +/** @brief Class to describe the germanium detector, mainly handles processing of the detected hits. + * Extends @c G4VSensitiveDetector */ +class RMGCalorimeterDetector : public G4VSensitiveDetector { + + public: + + RMGCalorimeterDetector(); + ~RMGCalorimeterDetector() = default; + + RMGCalorimeterDetector(RMGCalorimeterDetector const&) = delete; + RMGCalorimeterDetector& operator=(RMGCalorimeterDetector const&) = delete; + RMGCalorimeterDetector(RMGCalorimeterDetector&&) = delete; + RMGCalorimeterDetector& operator=(RMGCalorimeterDetector&&) = delete; + + void Initialize(G4HCofThisEvent* hit_coll) override; + + /** @brief Process the detected hits computing the various parameters of a @c + * RMGDetectorHit and then adding this to the hit collection.*/ + bool ProcessHits(G4Step* step, G4TouchableHistory* history) override; + void EndOfEvent(G4HCofThisEvent* hit_coll) override; + + private: + + RMGDetectorHitsCollection* fHitsCollection = nullptr; +}; + + +#endif + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/include/RMGCalorimeterOutputScheme.hh b/include/RMGCalorimeterOutputScheme.hh new file mode 100644 index 000000000..76f8050c4 --- /dev/null +++ b/include/RMGCalorimeterOutputScheme.hh @@ -0,0 +1,65 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#ifndef _RMG_CALORIMETER_OUTPUT_SCHEME_HH_ +#define _RMG_CALORIMETER_OUTPUT_SCHEME_HH_ + +#include +#include +#include + +#include "G4AnalysisManager.hh" +#include "G4GenericMessenger.hh" + +#include "RMGCalorimeterDetector.hh" +#include "RMGDetectorHit.hh" +#include "RMGOutputTools.hh" +#include "RMGVOutputScheme.hh" + +class G4Event; +/** @brief Output scheme for Calorimeters. + * + * @details This output scheme records the hits in the Calorimeters. + * The properties of each @c RMGDetectorHit are recorded: + * - event index, + * - time, + * - energy deposition, + */ +class RMGCalorimeterOutputScheme : public RMGVOutputScheme { + + public: + + RMGCalorimeterOutputScheme() {}; + + /** @brief Sets the names of the output columns, invoked in @c RMGRunAction::SetupAnalysisManager */ + void AssignOutputNames(G4AnalysisManager* ana_man) override; + + /** @brief Store the information from the event, invoked in @c RMGEventAction::EndOfEventAction + * @details Only steps with non-zero energy are stored, unless @c fDiscardZeroEnergyHits is false. + */ + void StoreEvent(const G4Event* event) override; + + protected: + + [[nodiscard]] std::string GetNtupleNameFlat() const override { return "calorimeter"; } + + private: + + RMGDetectorHitsCollection* GetHitColl(const G4Event*); +}; + +#endif + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/include/RMGDetectorMetadata.hh b/include/RMGDetectorMetadata.hh index 7dcd2e877..3f8bc3f86 100644 --- a/include/RMGDetectorMetadata.hh +++ b/include/RMGDetectorMetadata.hh @@ -22,6 +22,7 @@ enum RMGDetectorType { kGermanium, kOptical, kScintillator, + kCalorimeter, }; struct RMGDetectorMetadata { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ebce4776f..2d0ea845c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ set(_root ${PROJECT_SOURCE_DIR}) set(PROJECT_PUBLIC_HEADERS ${_root}/include/RMGAnalysisReader.hh + ${_root}/include/RMGCalorimeterDetector.hh + ${_root}/include/RMGCalorimeterOutputScheme.hh ${_root}/include/RMGDetectorHit.hh ${_root}/include/RMGDetectorMetadata.hh ${_root}/include/RMGExceptionHandler.hh @@ -53,6 +55,8 @@ set(PROJECT_PUBLIC_HEADERS set(PROJECT_SOURCES ${_root}/src/RMGAnalysisReader.cc + ${_root}/src/RMGCalorimeterDetector.cc + ${_root}/src/RMGCalorimeterOutputScheme.cc ${_root}/src/RMGDetectorHit.cc ${_root}/src/RMGExceptionHandler.cc ${_root}/src/RMGHardware.cc diff --git a/src/RMGCalorimeterDetector.cc b/src/RMGCalorimeterDetector.cc new file mode 100644 index 000000000..2919b40e7 --- /dev/null +++ b/src/RMGCalorimeterDetector.cc @@ -0,0 +1,104 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include "RMGCalorimeterDetector.hh" + +#include +#include +#include + +#include "G4HCofThisEvent.hh" +#include "G4OpticalPhoton.hh" +#include "G4SDManager.hh" +#include "G4Step.hh" + +#include "RMGHardware.hh" +#include "RMGLog.hh" +#include "RMGManager.hh" +#include "RMGOutputTools.hh" + + +RMGCalorimeterDetector::RMGCalorimeterDetector() : G4VSensitiveDetector("Calorimeter") { + + // declare only one hit collection. + // NOTE: names in the respective output scheme class must match this + G4VSensitiveDetector::collectionName.insert("Hits"); +} + +void RMGCalorimeterDetector::Initialize(G4HCofThisEvent* hit_coll) { + + // create hits collection object + // NOTE: assumes there is only one collection name (see constructor) + fHitsCollection = new RMGDetectorHitsCollection( + G4VSensitiveDetector::SensitiveDetectorName, + G4VSensitiveDetector::collectionName[0] + ); + + // associate it with the G4HCofThisEvent object + auto hc_id = G4SDManager::GetSDMpointer()->GetCollectionID( + G4VSensitiveDetector::SensitiveDetectorName + "/" + G4VSensitiveDetector::collectionName[0] + ); + hit_coll->AddHitsCollection(hc_id, fHitsCollection); +} + +bool RMGCalorimeterDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*history*/) { + + RMGLog::OutDev(RMGLog::debug, "Processing calorimeter hits"); + + // return if no energy is deposited + // ignore optical photons + if (step->GetTrack()->GetDefinition() == G4OpticalPhoton::OpticalPhotonDefinition()) return false; + + // we're going to use info from the pre-step point + const auto prestep = step->GetPreStepPoint(); + + // check containment of prestep point + auto prestep_inside = RMGOutputTools::check_step_point_containment( + prestep, + RMGDetectorType::kCalorimeter + ); + + if (not prestep_inside) return false; + + // retrieve unique id for persistency, take from the prestep + const auto pv = prestep->GetTouchableHandle()->GetVolume(); + + auto pv_name = pv->GetName(); + const auto pv_copynr = prestep->GetTouchableHandle()->GetCopyNumber(); + + const auto det_cons = RMGManager::Instance()->GetDetectorConstruction(); + auto det_uid = det_cons->GetDetectorMetadata({pv_name, pv_copynr}).uid; + + RMGLog::OutDev(RMGLog::debug, "Hit in calorimeter nr. ", det_uid, " detected"); + + // create a new hit and fill it + if (fHitsCollection->entries() == 0) { + auto _hit = new RMGDetectorHit(); + _hit->energy_deposition = 0; + fHitsCollection->insert(_hit); + } + auto* hit = dynamic_cast(fHitsCollection->GetHit(0)); + + hit->physical_volume = pv; + hit->detector_uid = det_uid; + hit->energy_deposition += step->GetTotalEnergyDeposit(); + hit->global_time = prestep->GetGlobalTime(); + + return true; +} + +void RMGCalorimeterDetector::EndOfEvent(G4HCofThisEvent* /*hit_coll*/) {} + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/src/RMGCalorimeterOutputScheme.cc b/src/RMGCalorimeterOutputScheme.cc new file mode 100644 index 000000000..5b57a409b --- /dev/null +++ b/src/RMGCalorimeterOutputScheme.cc @@ -0,0 +1,174 @@ +// Copyright (C) 2022 Luigi Pertoldi +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +// details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +#include "RMGCalorimeterOutputScheme.hh" + +#include + +#include "G4AnalysisManager.hh" +#include "G4Event.hh" +#include "G4EventManager.hh" +#include "G4HCtable.hh" +#include "G4OpticalPhoton.hh" +#include "G4SDManager.hh" + +#include "RMGCalorimeterDetector.hh" +#include "RMGHardware.hh" +#include "RMGIpc.hh" +#include "RMGLog.hh" +#include "RMGManager.hh" +#include "RMGNavigationTools.hh" +#include "RMGOutputManager.hh" +#include "RMGOutputTools.hh" +#include "RMGTools.hh" + +namespace u = CLHEP; + + +void RMGCalorimeterOutputScheme::AssignOutputNames(G4AnalysisManager* ana_man) { + + auto rmg_man = RMGOutputManager::Instance(); + const auto det_cons = RMGManager::Instance()->GetDetectorConstruction(); + const auto detectors = det_cons->GetDetectorMetadataMap(); + + std::set registered_uids; + std::map registered_ntuples; + for (auto&& det : detectors) { + if (det.second.type != RMGDetectorType::kCalorimeter) continue; + + // do not register the ntuple twice if two detectors share their uid. + auto had_uid = registered_uids.emplace(det.second.uid); + if (!had_uid.second) continue; + + auto volumes = RMGNavigationTools::FindPhysicalVolume( + det.second.name, + std::to_string(det.second.copy_nr) + ); + if (volumes.empty()) { + RMGLog::Out( + RMGLog::fatal, + "Could not find detector physical volume in RMGCalorimeterOutputScheme::AssignOutputNames" + ); + } + if (volumes.size() > 1) { + RMGLog::Out( + RMGLog::fatal, + "Found multiple physical volumes for detector Name '{}' (copy number {}) - this is not " + "allowed", + det.second.name, + det.second.copy_nr + ); + } + + auto pv = *volumes.begin(); + auto trees = RMGNavigationTools::FindGlobalPositions(pv); + if (trees.size() > 1) { + RMGLog::Out( + RMGLog::fatal, + "more than one way to reach world volume from detector ", + det.second.name + ); + } + + auto ntuple_name = this->GetNtupleName(det.second); + auto ntuple_reg = registered_ntuples.find(ntuple_name); + if (ntuple_reg != registered_ntuples.end()) { + // ntuple already exists, but also store the ntuple id for the other uid(s). + rmg_man->RegisterNtuple(det.second.uid, ntuple_reg->second, ntuple_name); + continue; + } + + auto id = rmg_man->CreateAndRegisterNtuple( + det.second.uid, + ntuple_name, + "RMGCalorimeterOutputScheme", + ana_man + ); + registered_ntuples.emplace(ntuple_name, id); + + // store the indices + ana_man->CreateNtupleIColumn(id, "evtid"); + if (!fNtuplePerDetector) { ana_man->CreateNtupleIColumn(id, "det_uid"); } + + // store the floating points values + ana_man->CreateNtupleDColumn(id, "edep_in_keV"); + ana_man->CreateNtupleDColumn(id, "time_in_ns"); + ana_man->FinishNtuple(id); + } +} + +RMGDetectorHitsCollection* RMGCalorimeterOutputScheme::GetHitColl(const G4Event* event) { + auto sd_man = G4SDManager::GetSDMpointer(); + + auto hit_coll_id = sd_man->GetCollectionID("Calorimeter/Hits"); + if (hit_coll_id < 0) { + RMGLog::OutDev(RMGLog::error, "Could not find hit collection Calorimeter/Hits"); + return nullptr; + } + + auto hit_coll = dynamic_cast( + event->GetHCofThisEvent()->GetHC(hit_coll_id) + ); + + if (!hit_coll) { + RMGLog::Out(RMGLog::error, "Could not find hit collection associated with event"); + return nullptr; + } + + return hit_coll; +} + + +void RMGCalorimeterOutputScheme::StoreEvent(const G4Event* event) { + + auto hit_coll = GetHitColl(event); + if (!hit_coll) return; + + if (hit_coll->entries() <= 0) { + RMGLog::OutDev(RMGLog::debug, "Hit collection is empty"); + return; + } else { + RMGLog::OutDev(RMGLog::debug, "Hit collection contains ", hit_coll->entries(), " hits"); + } + + auto rmg_man = RMGOutputManager::Instance(); + if (rmg_man->IsPersistencyEnabled()) { + RMGLog::OutDev(RMGLog::debug, "Filling persistent data vectors"); + const auto ana_man = G4AnalysisManager::Instance(); + + for (auto hit : *hit_coll->GetVector()) { + + if (!hit or (hit->energy_deposition == 0)) continue; + + hit->Print(); + auto ntupleid = rmg_man->GetNtupleID(hit->detector_uid); + + int col_id = 0; + // store the indices + ana_man->FillNtupleIColumn(ntupleid, col_id++, event->GetEventID()); + if (!fNtuplePerDetector) { + ana_man->FillNtupleIColumn(ntupleid, col_id++, hit->detector_uid); + } + + FillNtupleDColumn(ana_man, ntupleid, col_id++, hit->energy_deposition / u::keV); + ana_man->FillNtupleDColumn(ntupleid, col_id++, hit->global_time / u::ns); + + // NOTE: must be called here for hit-oriented output + ana_man->AddNtupleRow(ntupleid); + } + } +} + +// vim: tabstop=2 shiftwidth=2 expandtab diff --git a/src/RMGHardware.cc b/src/RMGHardware.cc index 62a9cfc26..5aa2673c6 100644 --- a/src/RMGHardware.cc +++ b/src/RMGHardware.cc @@ -27,6 +27,8 @@ namespace fs = std::filesystem; #include "G4UserLimits.hh" #include "G4VPhysicalVolume.hh" +#include "RMGCalorimeterDetector.hh" +#include "RMGCalorimeterOutputScheme.hh" #include "RMGConfig.hh" #include "RMGGermaniumDetector.hh" #include "RMGGermaniumOutputScheme.hh" @@ -257,6 +259,10 @@ void RMGHardware::ConstructSDandField() { obj = new RMGScintillatorDetector(); output = std::make_shared(); break; + case RMGDetectorType::kCalorimeter: + obj = new RMGCalorimeterDetector(); + output = std::make_shared(); + break; default: RMGLog::OutDev( RMGLog::fatal, From d5446bc451a84e8c727cbeb5f33bb3372eee091c Mon Sep 17 00:00:00 2001 From: Luigi Pertoldi Date: Wed, 1 Oct 2025 20:10:42 +0200 Subject: [PATCH 2/4] unflatted the energy array from the calorimeters --- python/remage/post_proc.py | 77 +++++++++++++++++++------------ python/remage/utils.py | 10 ++++ src/RMGCalorimeterOutputScheme.cc | 2 +- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/python/remage/post_proc.py b/python/remage/post_proc.py index dd249ad32..ec275676c 100644 --- a/python/remage/post_proc.py +++ b/python/remage/post_proc.py @@ -1,8 +1,9 @@ from __future__ import annotations +import copy import logging import time -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from contextlib import contextmanager from pathlib import Path @@ -41,8 +42,9 @@ def post_proc( Path(p).suffix.lower() for p in [*remage_files, main_output_file] } - detector_info: list[str] = ipc_info.get("output_ntuple", 2) - detector_info_aux: list[str] = ipc_info.get("output_ntuple_aux", 2) + # these are mappings: -> [,
, ...] + detector_info = utils._icp_to_dict(ipc_info.get("output_ntuple", 2)) + detector_info_aux = utils._icp_to_dict(ipc_info.get("output_ntuple_aux", 2)) assert len(output_file_exts) == 1 @@ -84,19 +86,17 @@ def post_proc( ) log.info(msg) - # registered scintillator or germanium detectors - registered_detectors = list({det[1] for det in detector_info}) - # extract the additional tables in the output file (not detectors) - extra_tables = list({det[1] for det in detector_info_aux}) with tmp_renamed_files(remage_files) as original_files: # also get the additional tables to forward config = get_reboost_config( - registered_detectors, - extra_tables, + detector_info, + detector_info_aux, time_window=time_window_in_us, ) + msg = f"Reboost config: {config}" + log.debug(msg) # use reboost to post-process outputs reboost.build_hit( @@ -222,8 +222,8 @@ def deduplicate_table( def get_reboost_config( - reshape_table_list: Sequence[str], - other_table_list: Sequence[str], + detector_info: Mapping[str, Sequence[str]], + detector_info_aux: Mapping[str, Sequence[str]], *, time_window: float = 10, ) -> dict: @@ -231,11 +231,11 @@ def get_reboost_config( Parameters ---------- - reshape_table_list - a list of the table in the remage file that need to be reshaped - (i.e. Germanium or Scintillator output) - other_table_list - other tables in the file. + detector_info + a mapping of tables in the remage file that need to be reshaped, keyed + by output scheme name (i.e. RMGGermaniumOutputScheme). + detector_info_aux + same as `detector_info`, but holds non-standard tables. time_window time window to use for building hits (in us). @@ -245,24 +245,41 @@ def get_reboost_config( """ config = {} - # get the config for tables to be reshaped - config["processing_groups"] = [ - { - "name": "all", - "detector_mapping": [{"output": table} for table in reshape_table_list], - "hit_table_layout": f"reboost.shape.group.group_by_time(STEPS, {time_window})", - "operations": { - "t0": { - "expression": "ak.fill_none(ak.firsts(HITS.time, axis=-1), 0)", - "units": "ns", - }, - "evtid": "ak.fill_none(ak.firsts(HITS.evtid, axis=-1), 0)", + # build a default config for all tables (exclude calorimeter tables) + table_list = [ + v + for k, vals in detector_info.items() + if k != "RMGCalorimeterOutputScheme" + for v in vals + ] + + default_config = { + "name": "default", + "detector_mapping": [{"output": table} for table in table_list], + "hit_table_layout": f"reboost.shape.group.group_by_time(STEPS, {time_window})", + "operations": { + "t0": { + "expression": "ak.fill_none(ak.firsts(HITS.time, axis=-1), 0)", + "units": "ns", }, - } + "evtid": "ak.fill_none(ak.firsts(HITS.evtid, axis=-1), 0)", + }, + } + config["processing_groups"] = [default_config] + + # special treatment for the calorimeter output scheme + calo_config = copy.deepcopy(default_config) + calo_config["name"] = "RMGCalorimeterOutputScheme" + calo_config["detector_mapping"] = [ + {"output": table} for table in detector_info["RMGCalorimeterOutputScheme"] ] + # there is always one energy per event, then remove one dimension from the array + calo_config["operations"]["edep"] = "ak.ravel(HITS.edep)" + + config["processing_groups"].append(calo_config) # forward other tables as they are - config["forward"] = other_table_list + config["forward"] = [v for vals in detector_info_aux.values() for v in vals] return config diff --git a/python/remage/utils.py b/python/remage/utils.py index e3fc94d2f..7684a7cbd 100644 --- a/python/remage/utils.py +++ b/python/remage/utils.py @@ -1,7 +1,17 @@ from __future__ import annotations +from collections import defaultdict + def _to_list(thing): if not isinstance(thing, tuple | list): return [thing] return thing + + +def _icp_to_dict(thing): + d = defaultdict(list) + for k, v in thing: + d[k].append(v) + + return dict(d) diff --git a/src/RMGCalorimeterOutputScheme.cc b/src/RMGCalorimeterOutputScheme.cc index 5b57a409b..1429a129d 100644 --- a/src/RMGCalorimeterOutputScheme.cc +++ b/src/RMGCalorimeterOutputScheme.cc @@ -162,7 +162,7 @@ void RMGCalorimeterOutputScheme::StoreEvent(const G4Event* event) { ana_man->FillNtupleIColumn(ntupleid, col_id++, hit->detector_uid); } - FillNtupleDColumn(ana_man, ntupleid, col_id++, hit->energy_deposition / u::keV); + ana_man->FillNtupleDColumn(ntupleid, col_id++, hit->energy_deposition / u::keV); ana_man->FillNtupleDColumn(ntupleid, col_id++, hit->global_time / u::ns); // NOTE: must be called here for hit-oriented output From 019e4f5d5cbcfdc3241c39fa75e3e86c76f7a985 Mon Sep 17 00:00:00 2001 From: Luigi Pertoldi Date: Wed, 1 Oct 2025 20:36:22 +0200 Subject: [PATCH 3/4] update doc-dump and minor fixes --- docs/rmg-commands.md | 4 ++-- python/remage/post_proc.py | 21 +++++++++++---------- src/RMGCalorimeterDetector.cc | 3 +-- src/RMGCalorimeterOutputScheme.cc | 1 + src/RMGGermaniumDetector.cc | 1 - src/RMGGermaniumOutputScheme.cc | 1 + 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/rmg-commands.md b/docs/rmg-commands.md index 1dd42c1fe..01f8b62bd 100644 --- a/docs/rmg-commands.md +++ b/docs/rmg-commands.md @@ -372,7 +372,7 @@ Register detectors as saved in the GDML auxval structure, as written by pygeomto * **Parameter type** – `s` * **Omittable** – `True` * **Default value** – `All` - * **Candidates** – `All Germanium Optical Scintillator` + * **Candidates** – `All Germanium Optical Scintillator Calorimeter` * **Allowed states** – `PreInit` ### `/RMG/Geometry/IncludeGDMLFile` @@ -404,7 +404,7 @@ register a sensitive detector – Detector type * **Parameter type** – `s` * **Omittable** – `False` - * **Candidates** – `Germanium Optical Scintillator` + * **Candidates** – `Germanium Optical Scintillator Calorimeter` * **Parameter** – `pv_name` – Detector physical volume, accepts regex patterns * **Parameter type** – `s` diff --git a/python/remage/post_proc.py b/python/remage/post_proc.py index ec275676c..1d27e797c 100644 --- a/python/remage/post_proc.py +++ b/python/remage/post_proc.py @@ -267,16 +267,17 @@ def get_reboost_config( } config["processing_groups"] = [default_config] - # special treatment for the calorimeter output scheme - calo_config = copy.deepcopy(default_config) - calo_config["name"] = "RMGCalorimeterOutputScheme" - calo_config["detector_mapping"] = [ - {"output": table} for table in detector_info["RMGCalorimeterOutputScheme"] - ] - # there is always one energy per event, then remove one dimension from the array - calo_config["operations"]["edep"] = "ak.ravel(HITS.edep)" - - config["processing_groups"].append(calo_config) + if "RMGCalorimeterOutputScheme" in detector_info: + # special treatment for the calorimeter output scheme + calo_config = copy.deepcopy(default_config) + calo_config["name"] = "RMGCalorimeterOutputScheme" + calo_config["detector_mapping"] = [ + {"output": table} for table in detector_info["RMGCalorimeterOutputScheme"] + ] + # there is always one energy per event, then remove one dimension from the array + calo_config["operations"]["edep"] = "ak.ravel(HITS.edep)" + + config["processing_groups"].append(calo_config) # forward other tables as they are config["forward"] = [v for vals in detector_info_aux.values() for v in vals] diff --git a/src/RMGCalorimeterDetector.cc b/src/RMGCalorimeterDetector.cc index 2919b40e7..dc2112815 100644 --- a/src/RMGCalorimeterDetector.cc +++ b/src/RMGCalorimeterDetector.cc @@ -57,7 +57,6 @@ bool RMGCalorimeterDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*his RMGLog::OutDev(RMGLog::debug, "Processing calorimeter hits"); - // return if no energy is deposited // ignore optical photons if (step->GetTrack()->GetDefinition() == G4OpticalPhoton::OpticalPhotonDefinition()) return false; @@ -89,7 +88,7 @@ bool RMGCalorimeterDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*his _hit->energy_deposition = 0; fHitsCollection->insert(_hit); } - auto* hit = dynamic_cast(fHitsCollection->GetHit(0)); + auto* hit = (RMGDetectorHit*)(fHitsCollection->GetHit(0)); hit->physical_volume = pv; hit->detector_uid = det_uid; diff --git a/src/RMGCalorimeterOutputScheme.cc b/src/RMGCalorimeterOutputScheme.cc index 1429a129d..13bd6b687 100644 --- a/src/RMGCalorimeterOutputScheme.cc +++ b/src/RMGCalorimeterOutputScheme.cc @@ -150,6 +150,7 @@ void RMGCalorimeterOutputScheme::StoreEvent(const G4Event* event) { for (auto hit : *hit_coll->GetVector()) { + // return if no energy is deposited if (!hit or (hit->energy_deposition == 0)) continue; hit->Print(); diff --git a/src/RMGGermaniumDetector.cc b/src/RMGGermaniumDetector.cc index f879464e5..7d414ada4 100644 --- a/src/RMGGermaniumDetector.cc +++ b/src/RMGGermaniumDetector.cc @@ -57,7 +57,6 @@ bool RMGGermaniumDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*histo RMGLog::OutDev(RMGLog::debug, "Processing germanium detector hits"); - // return if no energy is deposited // ignore optical photons if (step->GetTrack()->GetDefinition() == G4OpticalPhoton::OpticalPhotonDefinition()) return false; diff --git a/src/RMGGermaniumOutputScheme.cc b/src/RMGGermaniumOutputScheme.cc index 1387469a3..6a9726ae2 100644 --- a/src/RMGGermaniumOutputScheme.cc +++ b/src/RMGGermaniumOutputScheme.cc @@ -241,6 +241,7 @@ void RMGGermaniumOutputScheme::StoreEvent(const G4Event* event) { for (auto hit : *hit_coll->GetVector()) { + // do not store event if no energy is deposited if (!hit or (hit->energy_deposition == 0 and this->fDiscardZeroEnergyHits)) continue; hit->Print(); From 029aa01c7b293efbafc0ea4779126db0d935e864 Mon Sep 17 00:00:00 2001 From: Luigi Pertoldi Date: Thu, 2 Oct 2025 10:38:53 +0200 Subject: [PATCH 4/4] fixies --- python/remage/ipc.py | 14 +++++++++++++- python/remage/post_proc.py | 10 +++++----- python/remage/utils.py | 10 ---------- src/RMGCalorimeterDetector.cc | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/python/remage/ipc.py b/python/remage/ipc.py index 2dd1e615b..dc4092765 100644 --- a/python/remage/ipc.py +++ b/python/remage/ipc.py @@ -55,6 +55,7 @@ import os import signal import subprocess +from collections import defaultdict from ._version import __version__ @@ -188,7 +189,7 @@ def __init__(self, ipc_info): """Storage structure for the IPC messages returned by ``remage-cpp``.""" self.ipc_info = ipc_info - def get(self, name: str, expected_len: int = 1) -> list[str]: + def get(self, name: str, expected_len: int = 1) -> list[str | list[str | tuple]]: """Return all messages of a given key ``name`` from the IPC message list. Parameters @@ -203,9 +204,20 @@ def get(self, name: str, expected_len: int = 1) -> list[str]: if len(msg) == expected_len + 1 and msg[0] == name ] if expected_len == 1: + # remove the unneeded extra dimension return [msg[0] for msg in msgs] return msgs + def get_as_dict(self, *args, **kwargs) -> dict[str, list[str | tuple]]: + """Same as :meth:`.get` but return a dictionary keyed by record key.""" + msgs = self.get(*args, **kwargs) + + d = defaultdict(list) + for k, v in msgs: + d[k].append(v) + + return dict(d) + def get_single(self, name: str, default: str) -> str: """Return the single single value for the key ``name`` or ``default`` if not present. diff --git a/python/remage/post_proc.py b/python/remage/post_proc.py index 1d27e797c..993c4fd75 100644 --- a/python/remage/post_proc.py +++ b/python/remage/post_proc.py @@ -43,8 +43,8 @@ def post_proc( } # these are mappings: -> [
,
, ...] - detector_info = utils._icp_to_dict(ipc_info.get("output_ntuple", 2)) - detector_info_aux = utils._icp_to_dict(ipc_info.get("output_ntuple_aux", 2)) + detector_info = ipc_info.get_as_dict("output_ntuple", 2) + detector_info_aux = ipc_info.get_as_dict("output_ntuple_aux", 2) assert len(output_file_exts) == 1 @@ -269,11 +269,11 @@ def get_reboost_config( if "RMGCalorimeterOutputScheme" in detector_info: # special treatment for the calorimeter output scheme + tables = detector_info["RMGCalorimeterOutputScheme"] + calo_config = copy.deepcopy(default_config) calo_config["name"] = "RMGCalorimeterOutputScheme" - calo_config["detector_mapping"] = [ - {"output": table} for table in detector_info["RMGCalorimeterOutputScheme"] - ] + calo_config["detector_mapping"] = [{"output": table} for table in tables] # there is always one energy per event, then remove one dimension from the array calo_config["operations"]["edep"] = "ak.ravel(HITS.edep)" diff --git a/python/remage/utils.py b/python/remage/utils.py index 7684a7cbd..e3fc94d2f 100644 --- a/python/remage/utils.py +++ b/python/remage/utils.py @@ -1,17 +1,7 @@ from __future__ import annotations -from collections import defaultdict - def _to_list(thing): if not isinstance(thing, tuple | list): return [thing] return thing - - -def _icp_to_dict(thing): - d = defaultdict(list) - for k, v in thing: - d[k].append(v) - - return dict(d) diff --git a/src/RMGCalorimeterDetector.cc b/src/RMGCalorimeterDetector.cc index dc2112815..c4d7b899d 100644 --- a/src/RMGCalorimeterDetector.cc +++ b/src/RMGCalorimeterDetector.cc @@ -88,7 +88,7 @@ bool RMGCalorimeterDetector::ProcessHits(G4Step* step, G4TouchableHistory* /*his _hit->energy_deposition = 0; fHitsCollection->insert(_hit); } - auto* hit = (RMGDetectorHit*)(fHitsCollection->GetHit(0)); + auto* hit = static_cast(fHitsCollection->GetHit(0)); hit->physical_volume = pv; hit->detector_uid = det_uid;