From 2f914a49177e563c62275993169e1d5d6dc9bcc6 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Mon, 14 Jul 2025 14:00:56 +0200 Subject: [PATCH 1/6] store copy_nr in DetectorMeta --- include/RMGDetectorMetadata.hh | 2 ++ src/RMGHardware.cc | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/RMGDetectorMetadata.hh b/include/RMGDetectorMetadata.hh index 76f8eed88..987b677a3 100644 --- a/include/RMGDetectorMetadata.hh +++ b/include/RMGDetectorMetadata.hh @@ -27,7 +27,9 @@ enum RMGDetectorType { struct RMGDetectorMetadata { RMGDetectorType type; int uid; + /** @brief name of the referenced physical volume */ std::string name; + int copy_nr; }; #endif diff --git a/src/RMGHardware.cc b/src/RMGHardware.cc index 5ad85fd26..03cb2a1b5 100644 --- a/src/RMGHardware.cc +++ b/src/RMGHardware.cc @@ -300,7 +300,7 @@ void RMGHardware::RegisterDetector( fActiveDetectors.insert(type); // FIXME: can this be done with emplace? - auto r_value = fDetectorMetadata.insert({{pv_name, copy_nr}, {type, uid, pv_name}}); + auto r_value = fDetectorMetadata.insert({{pv_name, copy_nr}, {type, uid, pv_name, copy_nr}}); if (!r_value.second) { // if insertion did not take place RMGLog::OutFormat( RMGLog::warning, From 7de39e4332532ef692fe7c9f44a052352296a968 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Mon, 14 Jul 2025 14:02:01 +0200 Subject: [PATCH 2/6] refactor global position code into RMGNavigationTools --- include/RMGNavigationTools.hh | 23 ++++++++++ include/RMGVertexConfinement.hh | 13 ------ src/RMGNavigationTools.cc | 75 +++++++++++++++++++++++++++++++++ src/RMGVertexConfinement.cc | 64 +--------------------------- 4 files changed, 99 insertions(+), 76 deletions(-) diff --git a/include/RMGNavigationTools.hh b/include/RMGNavigationTools.hh index 98b5c6739..01db16ea3 100644 --- a/include/RMGNavigationTools.hh +++ b/include/RMGNavigationTools.hh @@ -18,8 +18,11 @@ #include #include +#include #include "G4LogicalVolume.hh" +#include "G4RotationMatrix.hh" +#include "G4ThreeVector.hh" #include "G4VPhysicalVolume.hh" // TODO: write function that locates points in global coordinates by using an @@ -85,6 +88,26 @@ namespace RMGNavigationTools { * number, and logs their names, copy numbers, and the names of their logical parent volumes. */ void PrintListOfPhysicalVolumes(); + + struct VolumeTreeEntry { + VolumeTreeEntry() = delete; + VolumeTreeEntry(const VolumeTreeEntry&) = default; + VolumeTreeEntry(G4VPhysicalVolume* pv) { physvol = pv; } + + G4VPhysicalVolume* physvol; + + G4ThreeVector vol_global_translation; // origin + G4RotationMatrix vol_global_rotation; // identity + std::vector partial_rotations; + std::vector partial_translations; + }; + + /** + * @brief find all ways to reach the world volume from a given physical volume. + * @param pv the physical volume to start with. + */ + std::vector FindGlobalPositions(G4VPhysicalVolume* pv); + } // namespace RMGNavigationTools #endif diff --git a/include/RMGVertexConfinement.hh b/include/RMGVertexConfinement.hh index 32551974b..bda130ce8 100644 --- a/include/RMGVertexConfinement.hh +++ b/include/RMGVertexConfinement.hh @@ -288,19 +288,6 @@ class RMGVertexConfinement : public RMGVVertexGenerator { private: - struct VolumeTreeEntry { - VolumeTreeEntry() = delete; - VolumeTreeEntry(const VolumeTreeEntry&) = default; - VolumeTreeEntry(G4VPhysicalVolume* pv) { physvol = pv; } - - G4VPhysicalVolume* physvol; - - G4ThreeVector vol_global_translation; // origin - G4RotationMatrix vol_global_rotation; // identity - std::vector partial_rotations; - std::vector partial_translations; - }; - void InitializePhysicalVolumes(); void InitializeGeometricalVolumes(bool use_excluded_volumes); bool ActualGenerateVertex(G4ThreeVector& v); diff --git a/src/RMGNavigationTools.cc b/src/RMGNavigationTools.cc index 8164aff1a..3ec8bbb87 100644 --- a/src/RMGNavigationTools.cc +++ b/src/RMGNavigationTools.cc @@ -16,12 +16,14 @@ #include "RMGNavigationTools.hh" #include +#include #include #include "G4LogicalVolume.hh" #include "G4LogicalVolumeStore.hh" #include "G4Material.hh" #include "G4PhysicalVolumeStore.hh" +#include "G4TransportationManager.hh" #include "G4UnitsTable.hh" #include "RMGLog.hh" @@ -157,4 +159,77 @@ void RMGNavigationTools::PrintListOfPhysicalVolumes() { RMGLog::Out(RMGLog::summary, "Total: ", volumes.size(), " volumes"); } + +std::vector RMGNavigationTools::FindGlobalPositions( + G4VPhysicalVolume* pv +) { + auto world_volume = G4TransportationManager::GetTransportationManager() + ->GetNavigatorForTracking() + ->GetWorldVolume(); + + std::vector trees; + // queue for paths to the mother volume that still have to be searched. + std::queue q; + q.emplace(pv); + + for (; !q.empty(); q.pop()) { + auto v = q.front(); + + if (!v.physvol) + RMGLog::OutDev( + RMGLog::fatal, + "nullptr detected in loop condition, this is unexpected. ", + "Blame RMGNavigationTools::FindDirectMother?" + ); + + v.partial_rotations.push_back(v.physvol->GetObjectRotationValue()); + v.partial_translations.push_back(v.physvol->GetObjectTranslation()); + + v.vol_global_rotation = v.partial_rotations.back() * v.vol_global_rotation; + + for (auto m : RMGNavigationTools::FindDirectMothers(v.physvol)) { + if (m != world_volume) { + auto v_m = VolumeTreeEntry(v); // create a copy of the current helper object. + v_m.physvol = m; + q.push(v_m); + } else { // we finished that branch! + trees.push_back(v); + } + } + } + + RMGLog::OutFormatDev( + RMGLog::debug, + "Found {} ways to reach world volume from {}", + trees.size(), + pv->GetName() + ); + + // finalize all found paths to the mother volume. + for (auto&& v : trees) { + // world volume not included in loop + v.partial_translations.emplace_back(); // origin + v.partial_rotations.emplace_back(); // identity + + // partial_rotations[0] and partial_translations[0] refer to the target + // volume partial_rotations[1] and partial_translations[1], to the direct + // mother, etc. It is necessary to rotate with respect to the frame of the + // mother. If there are no rotations (or only the target volume is + // rotated): rotations are identity matrices and vol_global_translation = + // sum(partial_translations) + for (size_t i = 0; i < v.partial_translations.size() - 1; i++) { + G4ThreeVector tmp = v.partial_translations[i]; + for (size_t j = i + 1; j < v.partial_rotations.size() - 1; j++) { + tmp *= v.partial_rotations[j]; + } + v.vol_global_translation += tmp; + } + } + + if (trees.empty()) + RMGLog::OutDev(RMGLog::fatal, "No path to world volume found, that should not be!"); + + return trees; +} + // vim: tabstop=2 shiftwidth=2 expandtab diff --git a/src/RMGVertexConfinement.cc b/src/RMGVertexConfinement.cc index 8945035bd..9a99d9e83 100644 --- a/src/RMGVertexConfinement.cc +++ b/src/RMGVertexConfinement.cc @@ -15,8 +15,6 @@ #include "RMGVertexConfinement.hh" -#include - #include "G4AutoLock.hh" #include "G4Box.hh" #include "G4GenericMessenger.hh" @@ -603,68 +601,8 @@ void RMGVertexConfinement::InitializePhysicalVolumes() { // determine solid transformation w.r.t. world volume reference // found paths to the mother volume. - std::vector trees; - - // queue for paths to the mother volume that still have to be searched. - std::queue q; - q.emplace(el.physical_volume); - - for (; !q.empty(); q.pop()) { - auto v = q.front(); - - if (!v.physvol) - RMGLog::OutDev( - RMGLog::fatal, - "nullptr detected in loop condition, this is unexpected. ", - "Blame RMGNavigationTools::FindDirectMother?" - ); - - v.partial_rotations.push_back(v.physvol->GetObjectRotationValue()); - v.partial_translations.push_back(v.physvol->GetObjectTranslation()); - - v.vol_global_rotation = v.partial_rotations.back() * v.vol_global_rotation; - - for (auto m : RMGNavigationTools::FindDirectMothers(v.physvol)) { - if (m != world_volume) { - auto v_m = VolumeTreeEntry(v); // create a copy of the current helper object. - v_m.physvol = m; - q.push(v_m); - } else { // we finished that branch! - trees.push_back(v); - } - } - } - - RMGLog::OutFormatDev( - RMGLog::debug, - "Found {} ways to reach world volume from {}", - trees.size(), - el.physical_volume->GetName() - ); - - // finalize all found paths to the mother volume. - for (auto&& v : trees) { - // world volume not included in loop - v.partial_translations.emplace_back(); // origin - v.partial_rotations.emplace_back(); // identity - - // partial_rotations[0] and partial_translations[0] refer to the target - // volume partial_rotations[1] and partial_translations[1], to the direct - // mother, etc. It is necessary to rotate with respect to the frame of the - // mother. If there are no rotations (or only the target volume is - // rotated): rotations are identity matrices and vol_global_translation = - // sum(partial_translations) - for (size_t i = 0; i < v.partial_translations.size() - 1; i++) { - G4ThreeVector tmp = v.partial_translations[i]; - for (size_t j = i + 1; j < v.partial_rotations.size() - 1; j++) { - tmp *= v.partial_rotations[j]; - } - v.vol_global_translation += tmp; - } - } + auto trees = RMGNavigationTools::FindGlobalPositions(el.physical_volume); - if (trees.empty()) - RMGLog::OutDev(RMGLog::fatal, "No path to world volume found, that should not be!"); // assign first found transformation to current sampling solid el.rotation = trees[0].vol_global_rotation; el.translation = trees[0].vol_global_translation; From cf78a0b87c4200557d0d9a8626bfd910d9cff541 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Mon, 14 Jul 2025 14:03:23 +0200 Subject: [PATCH 3/6] store global positions of detectors --- include/RMGGermaniumOutputScheme.hh | 4 +++ src/RMGGermaniumOutputScheme.cc | 44 +++++++++++++++++++++++++++++ src/RMGTrackOutputScheme.cc | 2 ++ 3 files changed, 50 insertions(+) diff --git a/include/RMGGermaniumOutputScheme.hh b/include/RMGGermaniumOutputScheme.hh index abffd7d58..4bfa06900 100644 --- a/include/RMGGermaniumOutputScheme.hh +++ b/include/RMGGermaniumOutputScheme.hh @@ -111,6 +111,8 @@ class RMGGermaniumOutputScheme : public RMGVOutputScheme { fPreClusterPars.track_energy_threshold = threshold; } + void EndOfRunAction(const G4Run*) override; + protected: [[nodiscard]] std::string GetNtupleNameFlat() const override { return "germanium"; } @@ -141,6 +143,8 @@ class RMGGermaniumOutputScheme : public RMGVOutputScheme { // mode of position to store RMGOutputTools::PositionMode fPositionMode = RMGOutputTools::PositionMode::kAverage; + + std::map fDetectorOrigins; }; #endif diff --git a/src/RMGGermaniumOutputScheme.cc b/src/RMGGermaniumOutputScheme.cc index b8a594124..61e15789c 100644 --- a/src/RMGGermaniumOutputScheme.cc +++ b/src/RMGGermaniumOutputScheme.cc @@ -26,8 +26,10 @@ #include "RMGGermaniumDetector.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" @@ -55,6 +57,18 @@ void RMGGermaniumOutputScheme::AssignOutputNames(G4AnalysisManager* ana_man) { const auto det_cons = RMGManager::Instance()->GetDetectorConstruction(); const auto detectors = det_cons->GetDetectorMetadataMap(); + auto detector_origins_id = rmg_man->CreateAndRegisterAuxNtuple( + "detector_origins", + "RMGGermaniumOutputScheme", + ana_man + ); + ana_man->CreateNtupleSColumn(detector_origins_id, "name"); + CreateNtupleFOrDColumn(ana_man, detector_origins_id, "xloc_in_m", fStoreSinglePrecisionPosition); + CreateNtupleFOrDColumn(ana_man, detector_origins_id, "yloc_in_m", fStoreSinglePrecisionPosition); + CreateNtupleFOrDColumn(ana_man, detector_origins_id, "zloc_in_m", fStoreSinglePrecisionPosition); + ana_man->FinishNtuple(detector_origins_id); + RMGIpc::SendIpcNonBlocking(RMGIpc::CreateMessage("output_ntuple_deduplicate", "detector_origins")); + std::set registered_uids; std::map registered_ntuples; for (auto&& det : detectors) { @@ -64,6 +78,17 @@ void RMGGermaniumOutputScheme::AssignOutputNames(G4AnalysisManager* ana_man) { auto had_uid = registered_uids.emplace(det.second.uid); if (!had_uid.second) continue; + auto pv = RMGNavigationTools::FindPhysicalVolume(det.second.name, det.second.copy_nr); + 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 + ); + } + fDetectorOrigins.insert({det.second.name, trees[0].vol_global_translation}); + auto ntuple_name = this->GetNtupleName(det.second); auto ntuple_reg = registered_ntuples.find(ntuple_name); if (ntuple_reg != registered_ntuples.end()) { @@ -340,6 +365,25 @@ std::optional RMGGermaniumOutputScheme::StackingActionNewStage(const int s return ShouldDiscardEvent(event) ? std::make_optional(false) : std::nullopt; } +void RMGGermaniumOutputScheme::EndOfRunAction(const G4Run*) { + auto rmg_man = RMGOutputManager::Instance(); + if (!rmg_man->IsPersistencyEnabled() || + (G4Threading::IsMasterThread() && !RMGManager::Instance()->IsExecSequential())) + return; + + const auto ana_man = G4AnalysisManager::Instance(); + auto ntuple_id = rmg_man->GetAuxNtupleID("detector_origins"); + + for (const auto& [det, v] : fDetectorOrigins) { + int col_id = 0; + ana_man->FillNtupleSColumn(ntuple_id, col_id++, det); + FillNtupleFOrDColumn(ana_man, ntuple_id, col_id++, v.getX() / u::m, fStoreSinglePrecisionPosition); + FillNtupleFOrDColumn(ana_man, ntuple_id, col_id++, v.getY() / u::m, fStoreSinglePrecisionPosition); + FillNtupleFOrDColumn(ana_man, ntuple_id, col_id++, v.getZ() / u::m, fStoreSinglePrecisionPosition); + ana_man->AddNtupleRow(ntuple_id); + } +} + void RMGGermaniumOutputScheme::SetPositionModeString(std::string mode) { try { diff --git a/src/RMGTrackOutputScheme.cc b/src/RMGTrackOutputScheme.cc index 111c3ca39..68ca30952 100644 --- a/src/RMGTrackOutputScheme.cc +++ b/src/RMGTrackOutputScheme.cc @@ -20,6 +20,7 @@ #include "G4EventManager.hh" #include "G4OpticalPhoton.hh" +#include "RMGIpc.hh" #include "RMGLog.hh" #include "RMGOutputManager.hh" @@ -53,6 +54,7 @@ void RMGTrackOutputScheme::AssignOutputNames(G4AnalysisManager* ana_man) { ana_man->CreateNtupleIColumn(pid, "procid"); ana_man->CreateNtupleSColumn(pid, "name"); ana_man->FinishNtuple(pid); + RMGIpc::SendIpcNonBlocking(RMGIpc::CreateMessage("output_ntuple_deduplicate", "processes")); } void RMGTrackOutputScheme::TrackingActionPre(const G4Track* track) { From 209267e0003aae51074c21ca4c679e90c6f45c21 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Mon, 14 Jul 2025 14:04:06 +0200 Subject: [PATCH 4/6] deduplicate tables on post-proc and convert to structs --- python/remage/post_proc.py | 47 +++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/python/remage/post_proc.py b/python/remage/post_proc.py index 19eef64c2..dd249ad32 100644 --- a/python/remage/post_proc.py +++ b/python/remage/post_proc.py @@ -7,9 +7,10 @@ from pathlib import Path import h5py +import numpy as np import pygama.evt import reboost -from lgdo import lh5 +from lgdo import Array, Scalar, Struct, lh5 from lgdo.lh5.concat import lh5concat from . import utils @@ -70,12 +71,12 @@ def post_proc( time_start = time.time() - if not flat_output: - # if merging is on, write everything to a single file - output_files: list[str] | str = ( - remage_files if not merge_output_files else main_output_file - ) + # if merging is on, write everything to a single file + output_files: list[str] | str = ( + remage_files if not merge_output_files else main_output_file + ) + if not flat_output: msg = ( "Reshaping " + ("and merging " if merge_output_files else "") @@ -154,6 +155,12 @@ def post_proc( ipc_info.set("output", main_output_file) + # deduplicate entries of the process table. + ntuples_to_deduplicate = set(ipc_info.get("output_ntuple_deduplicate")) + for file in utils._to_list(output_files): + for table in ntuples_to_deduplicate: + deduplicate_table(file, table, "name", not flat_output) + msg = f"Finished post-processing which took {int(time.time() - time_start)} s" log.info(msg) @@ -186,6 +193,34 @@ def copy_links( del links_group[link_name] +def deduplicate_table( + file: str, table_name: str, unique_col: str, to_struct: bool +) -> None: + table = lh5.read(table_name, file) + table_old = { + col: (table[col].view_as("np").copy(), table[col].attrs) for col in table + } + _, uniq_idx = np.unique(table_old[unique_col][0], return_index=True) + table.resize(len(uniq_idx)) + for col, (nda, attrs) in table_old.items(): + table[col] = Array(nda[uniq_idx], attrs=attrs) + + if to_struct: + keys = list(set(table.keys()) - {unique_col}) + d = {} + for idx in range(table.size): + obj = ( + Struct({col: Scalar(table[col].nda[idx]) for col in keys}) + if len(keys) > 1 + else Scalar(table[keys[0]].nda[idx]) + ) + d[table[unique_col].nda[idx].decode("utf-8")] = obj + + lh5.write(Struct(d), table_name, file, wo_mode="overwrite") + else: + lh5.write(table, table_name, file, wo_mode="overwrite") + + def get_reboost_config( reshape_table_list: Sequence[str], other_table_list: Sequence[str], From 6b3645d89fce0239de9f9abdc1a2d685de42dc48 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Mon, 14 Jul 2025 14:20:01 +0200 Subject: [PATCH 5/6] adjust tests --- tests/output/dumps/ntuple-no-stp-pproc.lh5.ls | 7 ++++--- .../output/dumps/ntuple-optionals-pproc.lh5.ls | 16 +++++++++++++--- tests/output/dumps/ntuple-optionals.hdf5.ls | 17 +++++++++++++++++ tests/output/dumps/ntuple-optionals.lh5.ls | 5 +++++ tests/output/dumps/ntuple-per-det-pproc.lh5.ls | 9 +++++++++ .../dumps/ntuple-per-det-vol-pproc.lh5.ls | 9 +++++++++ tests/output/dumps/ntuple-per-det-vol.hdf5.ls | 17 +++++++++++++++++ tests/output/dumps/ntuple-per-det-vol.lh5.ls | 5 +++++ tests/output/dumps/ntuple-per-det.hdf5.ls | 17 +++++++++++++++++ tests/output/dumps/ntuple-per-det.lh5.ls | 5 +++++ .../dumps/ntuple-single-table-pproc.lh5.ls | 9 +++++++++ ...ntuple-single-table-with-tracks-pproc.lh5.ls | 16 +++++++++++++--- .../ntuple-single-table-with-tracks.hdf5.ls | 17 +++++++++++++++++ .../ntuple-single-table-with-tracks.lh5.ls | 5 +++++ tests/output/dumps/ntuple-single-table.hdf5.ls | 17 +++++++++++++++++ tests/output/dumps/ntuple-single-table.lh5.ls | 5 +++++ tests/output/macros/ntuple-no-stp.mac | 2 ++ tests/output/run-test-hdf5.sh | 4 ++-- 18 files changed, 171 insertions(+), 11 deletions(-) diff --git a/tests/output/dumps/ntuple-no-stp-pproc.lh5.ls b/tests/output/dumps/ntuple-no-stp-pproc.lh5.ls index 353d7a57d..3f0a6359b 100644 --- a/tests/output/dumps/ntuple-no-stp-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-no-stp-pproc.lh5.ls @@ -1,7 +1,8 @@ / -├── processes · table{name,procid} -│ ├── name · array<1>{string} -│ └── procid · array<1>{real} +├── processes · struct{compt,eBrem,phot} +│ ├── compt · real +│ ├── eBrem · real +│ └── phot · real └── tracks · table{ekin,evtid,parent_trackid,particle,procid,px,py,pz,time,trackid,xloc,yloc,zloc} ├── ekin · array<1>{real} ── {'units': 'MeV'} ├── evtid · array<1>{real} diff --git a/tests/output/dumps/ntuple-optionals-pproc.lh5.ls b/tests/output/dumps/ntuple-optionals-pproc.lh5.ls index 44bc28583..d6d59cb4f 100644 --- a/tests/output/dumps/ntuple-optionals-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-optionals-pproc.lh5.ls @@ -1,4 +1,13 @@ / +├── detector_origins · struct{det1,det2} +│ ├── det1 · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── det2 · struct{xloc,yloc,zloc} +│ ├── xloc · real +│ ├── yloc · real +│ └── zloc · real ├── particles · table{ekin,evtid,particle,px,py,pz,vertexid} │ ├── ekin · array<1>{real} ── {'units': 'MeV'} │ ├── evtid · array<1>{real} @@ -7,9 +16,10 @@ │ ├── py · array<1>{real} ── {'units': 'MeV'} │ ├── pz · array<1>{real} ── {'units': 'MeV'} │ └── vertexid · array<1>{real} -├── processes · table{name,procid} -│ ├── name · array<1>{string} -│ └── procid · array<1>{real} +├── processes · struct{compt,eBrem,phot} +│ ├── compt · real +│ ├── eBrem · real +│ └── phot · real ├── stp · struct{det1,det2,optdet1,optdet2,scint1,scint2} │ ├── __by_uid__ · struct{det001,det002,det011,det012,det101,det102} │ │ ├── det001 -> /stp/scint1 diff --git a/tests/output/dumps/ntuple-optionals.hdf5.ls b/tests/output/dumps/ntuple-optionals.hdf5.ls index 22e41dec6..857bba1d5 100644 --- a/tests/output/dumps/ntuple-optionals.hdf5.ls +++ b/tests/output/dumps/ntuple-optionals.hdf5.ls @@ -59,6 +59,23 @@ stp/det2/yloc_in_m/pages stp/det2/zloc_in_m stp/det2/zloc_in_m/entries stp/det2/zloc_in_m/pages +stp/detector_origins +stp/detector_origins/columns +stp/detector_origins/entries +stp/detector_origins/forms +stp/detector_origins/name +stp/detector_origins/name/entries +stp/detector_origins/name/pages +stp/detector_origins/names +stp/detector_origins/xloc_in_m +stp/detector_origins/xloc_in_m/entries +stp/detector_origins/xloc_in_m/pages +stp/detector_origins/yloc_in_m +stp/detector_origins/yloc_in_m/entries +stp/detector_origins/yloc_in_m/pages +stp/detector_origins/zloc_in_m +stp/detector_origins/zloc_in_m/entries +stp/detector_origins/zloc_in_m/pages stp/optdet1 stp/optdet1/columns stp/optdet1/entries diff --git a/tests/output/dumps/ntuple-optionals.lh5.ls b/tests/output/dumps/ntuple-optionals.lh5.ls index cfe2d4050..c633a4da5 100644 --- a/tests/output/dumps/ntuple-optionals.lh5.ls +++ b/tests/output/dumps/ntuple-optionals.lh5.ls @@ -1,4 +1,9 @@ / +├── detector_origins · table{name,xloc,yloc,zloc} +│ ├── name · array<1>{string} +│ ├── xloc · array<1>{real} ── {'units': 'm'} +│ ├── yloc · array<1>{real} ── {'units': 'm'} +│ └── zloc · array<1>{real} ── {'units': 'm'} ├── particles · table{ekin,evtid,particle,px,py,pz,vertexid} │ ├── ekin · array<1>{real} ── {'units': 'MeV'} │ ├── evtid · array<1>{real} diff --git a/tests/output/dumps/ntuple-per-det-pproc.lh5.ls b/tests/output/dumps/ntuple-per-det-pproc.lh5.ls index e3cd7c460..d2c715bb9 100644 --- a/tests/output/dumps/ntuple-per-det-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-per-det-pproc.lh5.ls @@ -1,4 +1,13 @@ / +├── detector_origins · struct{det1,det2} +│ ├── det1 · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── det2 · struct{xloc,yloc,zloc} +│ ├── xloc · real +│ ├── yloc · real +│ └── zloc · real ├── stp · struct{det001,det002,det011,det012,det101,det102} │ ├── __by_uid__ · struct{det001,det002,det011,det012,det101,det102} │ │ ├── det001 -> /stp/det001 diff --git a/tests/output/dumps/ntuple-per-det-vol-pproc.lh5.ls b/tests/output/dumps/ntuple-per-det-vol-pproc.lh5.ls index 8b4df18d2..bc2879f3c 100644 --- a/tests/output/dumps/ntuple-per-det-vol-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-per-det-vol-pproc.lh5.ls @@ -1,4 +1,13 @@ / +├── detector_origins · struct{det1,det2} +│ ├── det1 · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── det2 · struct{xloc,yloc,zloc} +│ ├── xloc · real +│ ├── yloc · real +│ └── zloc · real ├── stp · struct{det1,det2,optdet1,optdet2,scint1,scint2} │ ├── __by_uid__ · struct{det001,det002,det011,det012,det101,det102} │ │ ├── det001 -> /stp/scint1 diff --git a/tests/output/dumps/ntuple-per-det-vol.hdf5.ls b/tests/output/dumps/ntuple-per-det-vol.hdf5.ls index 3ad1476f1..4f07dbee9 100644 --- a/tests/output/dumps/ntuple-per-det-vol.hdf5.ls +++ b/tests/output/dumps/ntuple-per-det-vol.hdf5.ls @@ -59,6 +59,23 @@ stp/det2/yloc_in_m/pages stp/det2/zloc_in_m stp/det2/zloc_in_m/entries stp/det2/zloc_in_m/pages +stp/detector_origins +stp/detector_origins/columns +stp/detector_origins/entries +stp/detector_origins/forms +stp/detector_origins/name +stp/detector_origins/name/entries +stp/detector_origins/name/pages +stp/detector_origins/names +stp/detector_origins/xloc_in_m +stp/detector_origins/xloc_in_m/entries +stp/detector_origins/xloc_in_m/pages +stp/detector_origins/yloc_in_m +stp/detector_origins/yloc_in_m/entries +stp/detector_origins/yloc_in_m/pages +stp/detector_origins/zloc_in_m +stp/detector_origins/zloc_in_m/entries +stp/detector_origins/zloc_in_m/pages stp/optdet1 stp/optdet1/columns stp/optdet1/entries diff --git a/tests/output/dumps/ntuple-per-det-vol.lh5.ls b/tests/output/dumps/ntuple-per-det-vol.lh5.ls index b1ac7e88a..e0502414e 100644 --- a/tests/output/dumps/ntuple-per-det-vol.lh5.ls +++ b/tests/output/dumps/ntuple-per-det-vol.lh5.ls @@ -1,4 +1,9 @@ / +├── detector_origins · table{name,xloc,yloc,zloc} +│ ├── name · array<1>{string} +│ ├── xloc · array<1>{real} ── {'units': 'm'} +│ ├── yloc · array<1>{real} ── {'units': 'm'} +│ └── zloc · array<1>{real} ── {'units': 'm'} ├── stp · struct{det1,det2,optdet1,optdet2,scint1,scint2} │ ├── __by_uid__ · struct{det001,det002,det011,det012,det101,det102} │ │ ├── det001 -> /stp/scint1 diff --git a/tests/output/dumps/ntuple-per-det.hdf5.ls b/tests/output/dumps/ntuple-per-det.hdf5.ls index db480aa7f..817fc0986 100644 --- a/tests/output/dumps/ntuple-per-det.hdf5.ls +++ b/tests/output/dumps/ntuple-per-det.hdf5.ls @@ -139,6 +139,23 @@ stp/det102/time_in_ns/pages stp/det102/wavelength_in_nm stp/det102/wavelength_in_nm/entries stp/det102/wavelength_in_nm/pages +stp/detector_origins +stp/detector_origins/columns +stp/detector_origins/entries +stp/detector_origins/forms +stp/detector_origins/name +stp/detector_origins/name/entries +stp/detector_origins/name/pages +stp/detector_origins/names +stp/detector_origins/xloc_in_m +stp/detector_origins/xloc_in_m/entries +stp/detector_origins/xloc_in_m/pages +stp/detector_origins/yloc_in_m +stp/detector_origins/yloc_in_m/entries +stp/detector_origins/yloc_in_m/pages +stp/detector_origins/zloc_in_m +stp/detector_origins/zloc_in_m/entries +stp/detector_origins/zloc_in_m/pages stp/vtx stp/vtx/columns stp/vtx/entries diff --git a/tests/output/dumps/ntuple-per-det.lh5.ls b/tests/output/dumps/ntuple-per-det.lh5.ls index e90a6ddaa..1e7f4e5f4 100644 --- a/tests/output/dumps/ntuple-per-det.lh5.ls +++ b/tests/output/dumps/ntuple-per-det.lh5.ls @@ -1,4 +1,9 @@ / +├── detector_origins · table{name,xloc,yloc,zloc} +│ ├── name · array<1>{string} +│ ├── xloc · array<1>{real} ── {'units': 'm'} +│ ├── yloc · array<1>{real} ── {'units': 'm'} +│ └── zloc · array<1>{real} ── {'units': 'm'} ├── stp · struct{det001,det002,det011,det012,det101,det102} │ ├── __by_uid__ · struct{det001,det002,det011,det012,det101,det102} │ │ ├── det001 -> /stp/det001 diff --git a/tests/output/dumps/ntuple-single-table-pproc.lh5.ls b/tests/output/dumps/ntuple-single-table-pproc.lh5.ls index 42665f938..1b774f1f9 100644 --- a/tests/output/dumps/ntuple-single-table-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-single-table-pproc.lh5.ls @@ -1,4 +1,13 @@ / +├── detector_origins · struct{det1,det2} +│ ├── det1 · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── det2 · struct{xloc,yloc,zloc} +│ ├── xloc · real +│ ├── yloc · real +│ └── zloc · real ├── stp · struct{germanium,optical,scintillator} │ ├── __by_uid__ · struct{det001,det011,det101} │ │ ├── det001 -> /stp/scintillator diff --git a/tests/output/dumps/ntuple-single-table-with-tracks-pproc.lh5.ls b/tests/output/dumps/ntuple-single-table-with-tracks-pproc.lh5.ls index 5d1212571..9a381316e 100644 --- a/tests/output/dumps/ntuple-single-table-with-tracks-pproc.lh5.ls +++ b/tests/output/dumps/ntuple-single-table-with-tracks-pproc.lh5.ls @@ -1,7 +1,17 @@ / -├── processes · table{name,procid} -│ ├── name · array<1>{string} -│ └── procid · array<1>{real} +├── detector_origins · struct{det1,det2} +│ ├── det1 · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── det2 · struct{xloc,yloc,zloc} +│ ├── xloc · real +│ ├── yloc · real +│ └── zloc · real +├── processes · struct{compt,eBrem,phot} +│ ├── compt · real +│ ├── eBrem · real +│ └── phot · real ├── stp · struct{germanium,optical,scintillator} │ ├── __by_uid__ · struct{det001,det011,det101} │ │ ├── det001 -> /stp/scintillator diff --git a/tests/output/dumps/ntuple-single-table-with-tracks.hdf5.ls b/tests/output/dumps/ntuple-single-table-with-tracks.hdf5.ls index accc842fc..4cc5a1c14 100644 --- a/tests/output/dumps/ntuple-single-table-with-tracks.hdf5.ls +++ b/tests/output/dumps/ntuple-single-table-with-tracks.hdf5.ls @@ -1,6 +1,23 @@ default_histograms header stp +stp/detector_origins +stp/detector_origins/columns +stp/detector_origins/entries +stp/detector_origins/forms +stp/detector_origins/name +stp/detector_origins/name/entries +stp/detector_origins/name/pages +stp/detector_origins/names +stp/detector_origins/xloc_in_m +stp/detector_origins/xloc_in_m/entries +stp/detector_origins/xloc_in_m/pages +stp/detector_origins/yloc_in_m +stp/detector_origins/yloc_in_m/entries +stp/detector_origins/yloc_in_m/pages +stp/detector_origins/zloc_in_m +stp/detector_origins/zloc_in_m/entries +stp/detector_origins/zloc_in_m/pages stp/germanium stp/germanium/columns stp/germanium/det_uid diff --git a/tests/output/dumps/ntuple-single-table-with-tracks.lh5.ls b/tests/output/dumps/ntuple-single-table-with-tracks.lh5.ls index d6dfcfec1..fa02c5f02 100644 --- a/tests/output/dumps/ntuple-single-table-with-tracks.lh5.ls +++ b/tests/output/dumps/ntuple-single-table-with-tracks.lh5.ls @@ -1,4 +1,9 @@ / +├── detector_origins · table{name,xloc,yloc,zloc} +│ ├── name · array<1>{string} +│ ├── xloc · array<1>{real} ── {'units': 'm'} +│ ├── yloc · array<1>{real} ── {'units': 'm'} +│ └── zloc · array<1>{real} ── {'units': 'm'} ├── processes · table{name,procid} │ ├── name · array<1>{string} │ └── procid · array<1>{real} diff --git a/tests/output/dumps/ntuple-single-table.hdf5.ls b/tests/output/dumps/ntuple-single-table.hdf5.ls index 542d065f3..35d1051ab 100644 --- a/tests/output/dumps/ntuple-single-table.hdf5.ls +++ b/tests/output/dumps/ntuple-single-table.hdf5.ls @@ -1,6 +1,23 @@ default_histograms header stp +stp/detector_origins +stp/detector_origins/columns +stp/detector_origins/entries +stp/detector_origins/forms +stp/detector_origins/name +stp/detector_origins/name/entries +stp/detector_origins/name/pages +stp/detector_origins/names +stp/detector_origins/xloc_in_m +stp/detector_origins/xloc_in_m/entries +stp/detector_origins/xloc_in_m/pages +stp/detector_origins/yloc_in_m +stp/detector_origins/yloc_in_m/entries +stp/detector_origins/yloc_in_m/pages +stp/detector_origins/zloc_in_m +stp/detector_origins/zloc_in_m/entries +stp/detector_origins/zloc_in_m/pages stp/germanium stp/germanium/columns stp/germanium/det_uid diff --git a/tests/output/dumps/ntuple-single-table.lh5.ls b/tests/output/dumps/ntuple-single-table.lh5.ls index 05b5994e0..5b53a95ed 100644 --- a/tests/output/dumps/ntuple-single-table.lh5.ls +++ b/tests/output/dumps/ntuple-single-table.lh5.ls @@ -1,4 +1,9 @@ / +├── detector_origins · table{name,xloc,yloc,zloc} +│ ├── name · array<1>{string} +│ ├── xloc · array<1>{real} ── {'units': 'm'} +│ ├── yloc · array<1>{real} ── {'units': 'm'} +│ └── zloc · array<1>{real} ── {'units': 'm'} ├── stp · struct{germanium,optical,scintillator} │ ├── __by_uid__ · struct{det001,det011,det101} │ │ ├── det001 -> /stp/scintillator diff --git a/tests/output/macros/ntuple-no-stp.mac b/tests/output/macros/ntuple-no-stp.mac index 9d887e52e..58bfdfb35 100644 --- a/tests/output/macros/ntuple-no-stp.mac +++ b/tests/output/macros/ntuple-no-stp.mac @@ -2,6 +2,8 @@ /control/execute macros/_init.mac +/RMG/Manager/Randomization/Seed 1403045745 + /RMG/Generator/Confine UnConfined /RMG/Generator/Select GPS diff --git a/tests/output/run-test-hdf5.sh b/tests/output/run-test-hdf5.sh index 748097558..1ccd395d8 100755 --- a/tests/output/run-test-hdf5.sh +++ b/tests/output/run-test-hdf5.sh @@ -63,7 +63,7 @@ fi # extract written lh5 structure & compare with expectation. "$lh5ls" -a "$output_lh5" | sed -r 's/\x1B\[[0-9;]*[mK]//g' > "$output_dump_lh5" -diff "$output_dump_lh5" "$output_exp_lh5" +diff -u "$output_dump_lh5" "$output_exp_lh5" # ------------------------------------- # TEST remage post-precessed LH5 output @@ -75,6 +75,6 @@ diff "$output_dump_lh5" "$output_exp_lh5" # extract written lh5 structure & compare with expectation. "$lh5ls" -a "$output_lh5_jag" | sed -r 's/\x1B\[[0-9;]*[mK]//g' > "$output_dump_lh5_jag" -diff "$output_dump_lh5_jag" "$output_exp_lh5_jag" +diff -u "$output_dump_lh5_jag" "$output_exp_lh5_jag" set +vx From 86046fdfdba54babe00705edd937423ec384f0b0 Mon Sep 17 00:00:00 2001 From: Manuel Huber Date: Tue, 29 Jul 2025 16:54:29 +0200 Subject: [PATCH 6/6] docs: add docs for detector_origins --- docs/manual/output.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/manual/output.md b/docs/manual/output.md index c975d4f78..810f0c239 100644 --- a/docs/manual/output.md +++ b/docs/manual/output.md @@ -573,6 +573,41 @@ More information on how to use the TCM is provided in {ref}`manual-analysis`, while more documentation about how the TCM is generated is available at {func}`pygama.evt.tcm.generate_tcm_cols`. +## Detector origins + +_remage_ stores the global coordinates of each Germanium detectors in a LH5 +struct called `detector_origins` (or in a table if reshaping is off): + +``` +/ +├── detector_origins · struct{B00000C,B00000D,...} +│ ├── B00000C · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ ├── B00000D · struct{xloc,yloc,zloc} +│ │ ├── xloc · real +│ │ ├── yloc · real +│ │ └── zloc · real +│ └── ... +└── ... +``` + +:::{note} + +For most volume types, the origin is the center of the volume, with some notable +exceptions: + +- generic polycones (as used for the detectors in _legend-pygeom-hpges_) have + their own origin defined which is not at the center. +- for boolean solids, the origin is the same as for the first constituent. With + subtractions, the origin might even be outside the volume. +- The + [list in the official documentation](https://geant4-userdoc.web.cern.ch/UsersGuides/ForApplicationDeveloper/html/Detector/Geometry/geomSolids.html) + contains all rules. + +::: + ## The vertex table _remage_ stores data about the simulated event vertex in a table named `vtx`. In