From 383a9ab8b98814f12b77645cb8857be847800bd2 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 15:34:56 -0500 Subject: [PATCH 01/16] refactor: consolidate LinkNavigator usage Migrate selected algorithms to link-based truth lookup via podio::LinkNavigator, rewire affected factories and pipeline wiring to consume link collections, and add shared LinkTruthUtils helpers to remove repeated per-event navigator and relation boilerplate. Also harden FarDetector link-absent handling and update PIDLookup algorithm tests for link-input semantics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../calorimetry/CalorimeterClusterRecoCoG.cc | 54 ++------------ .../calorimetry/CalorimeterClusterRecoCoG.h | 1 - .../calorimetry/CalorimeterClusterShape.cc | 24 ++++-- .../calorimetry/ImagingClusterReco.cc | 55 ++------------ .../calorimetry/ImagingClusterReco.h | 2 - .../fardetectors/FarDetectorLinearTracking.cc | 33 +++++---- .../fardetectors/FarDetectorLinearTracking.h | 15 ++-- .../FarDetectorTransportationPreML.cc | 32 ++++---- .../FarDetectorTransportationPreML.h | 6 +- src/algorithms/interfaces/LinkTruthUtils.h | 62 ++++++++++++++++ .../onnx/CalorimeterParticleIDPostML.cc | 21 ++++-- .../onnx/CalorimeterParticleIDPostML.h | 4 +- .../onnx/CalorimeterParticleIDPreML.cc | 73 ++++++++----------- .../onnx/CalorimeterParticleIDPreML.h | 10 ++- src/algorithms/pid/MatchToRICHPID.cc | 25 ++++--- src/algorithms/pid/MatchToRICHPID.h | 5 +- src/algorithms/pid_lut/PIDLookup.cc | 40 +++++----- src/algorithms/pid_lut/PIDLookup.h | 4 +- src/detectors/EEMC/EEMC.cc | 4 +- src/detectors/LOWQ2/LOWQ2.cc | 2 +- .../CalorimeterParticleIDPostML_factory.h | 4 +- .../CalorimeterParticleIDPreML_factory.h | 4 +- .../FarDetectorTransportationPreML_factory.h | 4 +- src/factories/pid/MatchToRICHPID_factory.h | 4 +- src/factories/pid_lut/PIDLookup_factory.h | 4 +- src/global/pid/pid.cc | 6 +- src/global/pid_lut/pid_lut.cc | 31 ++++++-- .../algorithms_test/pid_lut_PIDLookup.cc | 17 +++-- 28 files changed, 284 insertions(+), 262 deletions(-) create mode 100644 src/algorithms/interfaces/LinkTruthUtils.h diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc index b95476fae3..3fc57e5ce2 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc @@ -32,6 +32,8 @@ #include "CalorimeterClusterRecoCoG.h" #include "algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h" +#include "algorithms/interfaces/CompareObjectID.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -57,16 +59,12 @@ void CalorimeterClusterRecoCoG::process(const CalorimeterClusterRecoCoG::Input& auto [clusters, links, associations] = output; // Check if truth associations are possible - const bool do_assoc = mchitlinks != nullptr && !mchitlinks->empty(); + const truth::EventLinkNavigator link_nav(mchitlinks); + const bool do_assoc = link_nav.enabled(); if (!do_assoc) { debug("Provided MCRecoCalorimeterHitLink collection is empty. No truth associations " "will be performed."); } - // Build fast lookup once per event using podio::LinkNavigator - std::optional> link_nav; - if (do_assoc) { - link_nav.emplace(*mchitlinks); - } for (const auto& pcl : *proto) { // skip protoclusters with no hits @@ -87,7 +85,7 @@ void CalorimeterClusterRecoCoG::process(const CalorimeterClusterRecoCoG::Input& // If sim hits are available, associate cluster with MCParticle if (do_assoc) { - associate(cl, mchitassociations, *link_nav, links, associations); + associate(cl, mchitassociations, link_nav.navigator(), links, associations); } } } @@ -193,16 +191,8 @@ void CalorimeterClusterRecoCoG::associate( * of contributed energy over total sim hit energy. */ - // lambda to compare MCParticles - auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) { - if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) { - return (lhs.getObjectID().index < rhs.getObjectID().index); - } - return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID); - }; - // bookkeeping maps for associated primaries - std::map mapMCParToContrib(compare); + std::map> mapMCParToContrib; // -------------------------------------------------------------------------- // 1. get associated sim hits and sum energy @@ -228,7 +218,7 @@ void CalorimeterClusterRecoCoG::associate( // -------------------------------------------------------------------- // grab primary responsible for contribution & increment relevant sum // -------------------------------------------------------------------- - edm4hep::MCParticle primary = get_primary(contrib); + edm4hep::MCParticle primary = truth::primaryFrom(contrib); mapMCParToContrib[primary] += contrib.getEnergy(); trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}", @@ -246,17 +236,7 @@ void CalorimeterClusterRecoCoG::associate( // calculate weight const double weight = contribution / eSimHitSum; - // create link - auto link = links->create(); - link.setWeight(weight); - link.setFrom(cl); - link.setTo(part); - - // set association - auto assoc = assocs->create(); - assoc.setWeight(weight); - assoc.setRec(cl); - assoc.setSim(part); + truth::addWeightedRelation(cl, part, static_cast(weight), links, assocs); debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with " "weight ({})", @@ -265,22 +245,4 @@ void CalorimeterClusterRecoCoG::associate( } } -edm4hep::MCParticle -CalorimeterClusterRecoCoG::get_primary(const edm4hep::CaloHitContribution& contrib) { - // get contributing particle - const auto contributor = contrib.getParticle(); - - // walk back through parents to find primary - // - TODO finalize primary selection. This - // can be improved!! - edm4hep::MCParticle primary = contributor; - while (primary.parents_size() > 0) { - if (primary.getGeneratorStatus() != 0) { - break; - } - primary = primary.getParents(0); - } - return primary; -} - } // namespace eicrecon diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h index e86afa695a..7abf1f2ec9 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h @@ -88,7 +88,6 @@ class CalorimeterClusterRecoCoG : public CalorimeterClusterRecoCoGAlgorithm, const podio::LinkNavigator& link_nav, edm4eic::MCRecoClusterParticleLinkCollection* links, edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const; - static edm4hep::MCParticle get_primary(const edm4hep::CaloHitContribution& contrib); }; } // namespace eicrecon diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.cc b/src/algorithms/calorimetry/CalorimeterClusterShape.cc index cf5def115f..3661facaa4 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,20 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu const auto [in_clusters, in_associations] = input; auto [out_clusters, out_links, out_associations] = output; + edm4eic::MCRecoClusterParticleLinkCollection in_links; + std::optional> link_nav; + if (in_associations != nullptr) { + for (const auto& in_assoc : *in_associations) { + auto in_link = in_links.create(); + in_link.setFrom(in_assoc.getRec()); + in_link.setTo(in_assoc.getSim()); + in_link.setWeight(in_assoc.getWeight()); + } + if (!in_links.empty()) { + link_nav.emplace(in_links); + } + } + // exit if no clusters in collection if (in_clusters->empty()) { debug("No clusters in input collection."); @@ -228,17 +243,16 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu // ---------------------------------------------------------------------- // if provided, copy associations // ---------------------------------------------------------------------- - for (auto in_assoc : *in_associations) { - if (in_assoc.getRec() == in_clust) { - auto mc_par = in_assoc.getSim(); + if (link_nav) { + for (const auto& [mc_par, weight] : link_nav->getLinked(in_clust)) { auto out_link = out_links->create(); out_link.setFrom(out_clust); out_link.setTo(mc_par); - out_link.setWeight(in_assoc.getWeight()); + out_link.setWeight(weight); auto out_assoc = out_associations->create(); out_assoc.setRec(out_clust); out_assoc.setSim(mc_par); - out_assoc.setWeight(in_assoc.getWeight()); + out_assoc.setWeight(weight); } } // end input association loop } // end input cluster loop diff --git a/src/algorithms/calorimetry/ImagingClusterReco.cc b/src/algorithms/calorimetry/ImagingClusterReco.cc index 1a6fa4bf51..d4f3089e13 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.cc +++ b/src/algorithms/calorimetry/ImagingClusterReco.cc @@ -33,6 +33,8 @@ #include "algorithms/calorimetry/ClusterTypes.h" #include "algorithms/calorimetry/ImagingClusterReco.h" #include "algorithms/calorimetry/ImagingClusterRecoConfig.h" +#include "algorithms/interfaces/CompareObjectID.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -42,16 +44,12 @@ void ImagingClusterReco::process(const Input& input, const Output& output) const auto [clusters, links, associations, layers] = output; // Check if truth associations are possible - const bool do_assoc = mchitlinks != nullptr && !mchitlinks->empty(); + const truth::EventLinkNavigator link_nav(mchitlinks); + const bool do_assoc = link_nav.enabled(); if (!do_assoc) { debug("Provided MCRecoCalorimeterHitLink collection is empty. No truth associations " "will be performed."); } - // Build fast lookup once per event using podio::LinkNavigator - std::optional> link_nav; - if (do_assoc) { - link_nav.emplace(*mchitlinks); - } for (const auto& pcl : *proto) { if (!pcl.getHits().empty() && !pcl.getHits(0).isAvailable()) { @@ -77,7 +75,7 @@ void ImagingClusterReco::process(const Input& input, const Output& output) const // If sim hits are available, associate cluster with MCParticle if (do_assoc) { - associate_mc_particles(cl, mchitassociations, *link_nav, links, associations); + associate_mc_particles(cl, mchitassociations, link_nav.navigator(), links, associations); } } @@ -255,17 +253,8 @@ void ImagingClusterReco::associate_mc_particles( * of contributed energy over total sim hit energy. */ - // lambda to compare MCParticles - auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) { - if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) { - return (lhs.getObjectID().index < rhs.getObjectID().index); - } else { - return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID); - } - }; - // bookkeeping maps for associated primaries - std::map mapMCParToContrib(compare); + std::map> mapMCParToContrib; // -------------------------------------------------------------------------- // 1. get associated sim hits and sum energy @@ -290,7 +279,7 @@ void ImagingClusterReco::associate_mc_particles( // -------------------------------------------------------------------- // grab primary responsible for contribution & increment relevant sum // -------------------------------------------------------------------- - edm4hep::MCParticle primary = get_primary(contrib); + edm4hep::MCParticle primary = truth::primaryFrom(contrib); mapMCParToContrib[primary] += contrib.getEnergy(); trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}", @@ -308,17 +297,7 @@ void ImagingClusterReco::associate_mc_particles( // calculate weight const double weight = contribution / eSimHitSum; - // create link - auto link = links->create(); - link.setWeight(weight); - link.setFrom(cl); - link.setTo(part); - - // set association - auto assoc = assocs->create(); - assoc.setWeight(weight); - assoc.setRec(cl); - assoc.setSim(part); + truth::addWeightedRelation(cl, part, static_cast(weight), links, assocs); debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with " "weight ({})", @@ -327,22 +306,4 @@ void ImagingClusterReco::associate_mc_particles( } } -edm4hep::MCParticle -ImagingClusterReco::get_primary(const edm4hep::CaloHitContribution& contrib) const { - // get contributing particle - const auto contributor = contrib.getParticle(); - - // walk back through parents to find primary - // - TODO finalize primary selection. This - // can be improved!! - edm4hep::MCParticle primary = contributor; - while (primary.parents_size() > 0) { - if (primary.getGeneratorStatus() != 0) { - break; - } - primary = primary.getParents(0); - } - return primary; -} - } // namespace eicrecon diff --git a/src/algorithms/calorimetry/ImagingClusterReco.h b/src/algorithms/calorimetry/ImagingClusterReco.h index 51401a115d..7ee647b559 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.h +++ b/src/algorithms/calorimetry/ImagingClusterReco.h @@ -82,8 +82,6 @@ class ImagingClusterReco : public ImagingClusterRecoAlgorithm, const podio::LinkNavigator& link_nav, edm4eic::MCRecoClusterParticleLinkCollection* links, edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const; - - edm4hep::MCParticle get_primary(const edm4hep::CaloHitContribution& contrib) const; }; } // namespace eicrecon diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index 6d59689cf4..098ecf16b4 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -39,6 +38,7 @@ #include "FarDetectorLinearTracking.h" #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -66,6 +66,7 @@ void FarDetectorLinearTracking::process(const FarDetectorLinearTracking::Input& const auto [inputhits, hitLinks, assocHits] = input; auto [outputTracks, trackLinks, assocTracks] = output; + (void)assocHits; // Check the number of input collections is correct std::size_t nCollections = inputhits.size(); @@ -75,16 +76,12 @@ void FarDetectorLinearTracking::process(const FarDetectorLinearTracking::Input& } // Check if truth associations are possible - const bool do_assoc = hitLinks != nullptr && !hitLinks->empty(); + const truth::EventLinkNavigator link_nav(hitLinks); + const bool do_assoc = link_nav.enabled(); if (!do_assoc) { debug("Provided MCRecoTrackerHitLink collection is empty. No truth associations " "will be performed."); } - // Build fast lookup once per event using podio::LinkNavigator - std::optional> link_nav; - if (do_assoc) { - link_nav.emplace(*hitLinks); - } std::vector> convertedHits; std::vector> assocParts; @@ -103,7 +100,7 @@ void FarDetectorLinearTracking::process(const FarDetectorLinearTracking::Input& trace("No hits in layer"); return; } - ConvertClusters(*layerHits, *link_nav, *assocHits, convertedHits, assocParts); + ConvertClusters(*layerHits, link_nav, convertedHits, assocParts); } // Create a matrix to store the hit positions @@ -129,7 +126,7 @@ void FarDetectorLinearTracking::process(const FarDetectorLinearTracking::Input& if (layer == static_cast(m_cfg.n_layer) - 1) { // Check the combination, if chi2 limit is passed, add the track to the output checkHitCombination(&hitMatrix, outputTracks, trackLinks, assocTracks, inputhits, - assocParts, layerHitIndex); + assocParts, layerHitIndex, do_assoc); } else { layer++; continue; @@ -163,7 +160,7 @@ void FarDetectorLinearTracking::checkHitCombination( edm4eic::MCRecoTrackParticleAssociationCollection* assocTracks, const std::vector>& inputHits, const std::vector>& assocParts, - const std::vector& layerHitIndex) const { + const std::vector& layerHitIndex, const bool do_assoc) const { Eigen::Vector3d weightedAnchor = (*hitMatrix) * m_layerWeights / (m_layerWeights.sum()); @@ -210,8 +207,12 @@ void FarDetectorLinearTracking::checkHitCombination( std::unordered_map particleCount; for (std::size_t layer = 0; layer < layerHitIndex.size(); layer++) { track.addToMeasurements((*inputHits[layer])[layerHitIndex[layer]]); - const auto& assocParticle = assocParts[layer][layerHitIndex[layer]]; - particleCount[assocParticle]++; + if (do_assoc) { + const auto& assocParticle = assocParts[layer][layerHitIndex[layer]]; + if (assocParticle.isAvailable()) { + particleCount[assocParticle]++; + } + } } // Create track associations for each particle @@ -247,8 +248,7 @@ bool FarDetectorLinearTracking::checkHitPair(const Eigen::Vector3d& hit1, // Convert measurements into global coordinates void FarDetectorLinearTracking::ConvertClusters( const edm4eic::Measurement2DCollection& clusters, - const podio::LinkNavigator& link_nav, - [[maybe_unused]] const edm4eic::MCRecoTrackerHitAssociationCollection& assoc_hits, + const truth::EventLinkNavigator& link_nav, std::vector>& pointPositions, std::vector>& assoc_parts) const { @@ -277,16 +277,19 @@ void FarDetectorLinearTracking::ConvertClusters( } if (maxIndex == cluster.getWeights().size()) { // no maximum found (e.g. all weights zero, cluster size zero) + assocParticles.emplace_back(); continue; } auto maxHit = cluster.getHits()[maxIndex]; // Get associated raw hit auto rawHit = maxHit.getRawHit(); - const auto sim_hits = link_nav.getLinked(rawHit); + const auto sim_hits = link_nav.linked(rawHit); if (!sim_hits.empty()) { auto particle = sim_hits[0].o.getParticle(); assocParticles.push_back(particle); + } else { + assocParticles.emplace_back(); } } diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.h b/src/algorithms/fardetectors/FarDetectorLinearTracking.h index af17bd1e48..2fcd3d2f11 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.h +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -23,6 +22,7 @@ #include #include "FarDetectorLinearTrackingConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -64,18 +64,17 @@ class FarDetectorLinearTracking : public FarDetectorLinearTrackingAlgorithm, edm4eic::MCRecoTrackParticleAssociationCollection* assocTracks, const std::vector>& inputHits, const std::vector>& assocParts, - const std::vector& layerHitIndex) const; + const std::vector& layerHitIndex, bool do_assoc) const; /** Check if the last two hits are within a certain angle of the optimum direction **/ bool checkHitPair(const Eigen::Vector3d& hit1, const Eigen::Vector3d& hit2) const; /** Convert 2D clusters to 3D coordinates and match associated particle **/ - void - ConvertClusters(const edm4eic::Measurement2DCollection& clusters, - const podio::LinkNavigator& link_nav, - const edm4eic::MCRecoTrackerHitAssociationCollection& assoc_hits, - std::vector>& pointPositions, - std::vector>& assoc_parts) const; + void ConvertClusters( + const edm4eic::Measurement2DCollection& clusters, + const truth::EventLinkNavigator& link_nav, + std::vector>& pointPositions, + std::vector>& assoc_parts) const; }; } // namespace eicrecon diff --git a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc index f0beadfc14..265ae29681 100644 --- a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc +++ b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -19,8 +20,8 @@ void FarDetectorTransportationPreML::process( const FarDetectorTransportationPreML::Input& input, const FarDetectorTransportationPreML::Output& output) const { - const auto [inputTracks, mcAssociation, beamElectrons] = input; - auto [feature_tensors, target_tensors] = output; + const auto [inputTracks, trackLinks, beamElectrons] = input; + auto [feature_tensors, target_tensors] = output; //Set beam energy from first MCBeamElectron, using std::call_once if (beamElectrons != nullptr) { @@ -45,13 +46,18 @@ void FarDetectorTransportationPreML::process( feature_tensor.setElementType(1); // 1 - float edm4eic::MutableTensor target_tensor; - if (mcAssociation != nullptr) { + if (trackLinks != nullptr) { target_tensor = target_tensors->create(); target_tensor.addToShape(inputTracks->size()); target_tensor.addToShape(3); // px,py,pz target_tensor.setElementType(1); // 1 - float } + std::optional> link_nav; + if (trackLinks != nullptr && !trackLinks->empty()) { + link_nav.emplace(*trackLinks); + } + // Loop through inputTracks and simultaneously optionally associations if available // and fill the feature and target tensors for (const auto& track : *inputTracks) { @@ -66,18 +72,14 @@ void FarDetectorTransportationPreML::process( feature_tensor.addToFloatData(momentum.y); // diry feature_tensor.addToFloatData(momentum.z); // dirz - if ((mcAssociation != nullptr) && (!mcAssociation->empty())) { - //Loop through the MCRecoTrackParticleAssociationCollection finding the first one associated with the current track - for (const auto& assoc : *mcAssociation) { - if (assoc.getRec() == track) { - // Process the association if it exists and is non-empty - const auto& association = assoc.getSim(); // Assuming 1-to-1 mapping - auto MCElectronMomentum = association.getMomentum() / m_beamE; - target_tensor.addToFloatData(MCElectronMomentum.x); - target_tensor.addToFloatData(MCElectronMomentum.y); - target_tensor.addToFloatData(MCElectronMomentum.z); - break; // Exit loop after finding the first association - } + if (link_nav) { + // Use the first linked MC particle, matching previous first-association behavior. + const auto linked_particles = link_nav->getLinked(track); + if (!linked_particles.empty()) { + auto MCElectronMomentum = linked_particles.front().o.getMomentum() / m_beamE; + target_tensor.addToFloatData(MCElectronMomentum.x); + target_tensor.addToFloatData(MCElectronMomentum.y); + target_tensor.addToFloatData(MCElectronMomentum.z); } } } diff --git a/src/algorithms/fardetectors/FarDetectorTransportationPreML.h b/src/algorithms/fardetectors/FarDetectorTransportationPreML.h index 33fec063e0..0ba4871334 100644 --- a/src/algorithms/fardetectors/FarDetectorTransportationPreML.h +++ b/src/algorithms/fardetectors/FarDetectorTransportationPreML.h @@ -4,9 +4,9 @@ #pragma once #include +#include #include #include -#include #include #include #include @@ -20,7 +20,7 @@ namespace eicrecon { using FarDetectorTransportationPreMLAlgorithm = algorithms::Algorithm< algorithms::Input, + std::optional, std::optional>, algorithms::Output>>; @@ -31,7 +31,7 @@ class FarDetectorTransportationPreML : public FarDetectorTransportationPreMLAlgo FarDetectorTransportationPreML(std::string_view name) : FarDetectorTransportationPreMLAlgorithm{ name, - {"tracks", "trackAssociations", "beamElectrons"}, + {"tracks", "trackLinks", "beamElectrons"}, {"outputFeatureTensor", "outputTargetTensor"}, "Create tensor for input to far-detector magnetic transportation ML."} {} diff --git a/src/algorithms/interfaces/LinkTruthUtils.h b/src/algorithms/interfaces/LinkTruthUtils.h new file mode 100644 index 0000000000..ce786a5b6b --- /dev/null +++ b/src/algorithms/interfaces/LinkTruthUtils.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright (C) 2026 ePIC Collaboration + +#pragma once + +#include +#include +#include +#include +#include + +namespace eicrecon::truth { + +template class EventLinkNavigator { +public: + explicit EventLinkNavigator(const LinkCollectionT* links) + : m_enabled(links != nullptr && !links->empty()) { + if (m_enabled) { + m_nav.emplace(*links); + } + } + + bool enabled() const { return m_enabled; } + + const podio::LinkNavigator& navigator() const { return *m_nav; } + + template auto linked(const SrcT& src) const { + using ReturnT = decltype(std::declval>().getLinked(src)); + return m_enabled ? m_nav->getLinked(src) : ReturnT{}; + } + +private: + bool m_enabled = false; + std::optional> m_nav; +}; + +template +inline void addWeightedRelation(const RecT& rec, const SimT& sim, float weight, LinkCollT* links, + AssocCollT* assocs) { + auto link = links->create(); + link.setFrom(rec); + link.setTo(sim); + link.setWeight(weight); + + auto assoc = assocs->create(); + assoc.setRec(rec); + assoc.setSim(sim); + assoc.setWeight(weight); +} + +inline edm4hep::MCParticle primaryFrom(const edm4hep::CaloHitContribution& contrib) { + edm4hep::MCParticle primary = contrib.getParticle(); + while (primary.parents_size() > 0) { + if (primary.getGeneratorStatus() != 0) { + break; + } + primary = primary.getParents(0); + } + return primary; +} + +} // namespace eicrecon::truth diff --git a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc index 399dc9d8bc..02e33e3198 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -21,9 +22,14 @@ void CalorimeterParticleIDPostML::init() { void CalorimeterParticleIDPostML::process(const CalorimeterParticleIDPostML::Input& input, const CalorimeterParticleIDPostML::Output& output) const { - const auto [in_clusters, in_assocs, prediction_tensors] = input; + const auto [in_clusters, in_links, prediction_tensors] = input; auto [out_clusters, out_links, out_assocs, out_particle_ids] = output; + std::optional> link_nav; + if (in_links != nullptr && !in_links->empty()) { + link_nav.emplace(*in_links); + } + if (prediction_tensors->size() != 1) { error("Expected to find a single tensor, found {}", prediction_tensors->size()); throw std::runtime_error(""); @@ -81,15 +87,16 @@ void CalorimeterParticleIDPostML::process(const CalorimeterParticleIDPostML::Inp )); // propagate associations - for (auto in_assoc : *in_assocs) { - if (in_assoc.getRec() == in_cluster) { + if (link_nav) { + for (const auto& [sim_particle, weight] : link_nav->getLinked(in_cluster)) { auto out_link = out_links->create(); out_link.setFrom(out_cluster); - out_link.setTo(in_assoc.getSim()); - out_link.setWeight(in_assoc.getWeight()); - auto out_assoc = in_assoc.clone(); + out_link.setTo(sim_particle); + out_link.setWeight(weight); + auto out_assoc = out_assocs->create(); out_assoc.setRec(out_cluster); - out_assocs->push_back(out_assoc); + out_assoc.setSim(sim_particle); + out_assoc.setWeight(weight); } } } diff --git a/src/algorithms/onnx/CalorimeterParticleIDPostML.h b/src/algorithms/onnx/CalorimeterParticleIDPostML.h index 6fe2290eee..ee2c1baad9 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPostML.h +++ b/src/algorithms/onnx/CalorimeterParticleIDPostML.h @@ -19,7 +19,7 @@ namespace eicrecon { using CalorimeterParticleIDPostMLAlgorithm = algorithms::Algorithm< algorithms::Input, + std::optional, edm4eic::TensorCollection>, algorithms::Output, @@ -33,7 +33,7 @@ class CalorimeterParticleIDPostML : public CalorimeterParticleIDPostMLAlgorithm, CalorimeterParticleIDPostML(std::string_view name) : CalorimeterParticleIDPostMLAlgorithm{ name, - {"inputClusters", "inputClusterAssociations", "inputPredictionsTensor"}, + {"inputClusters", "inputClusterLinks", "inputPredictionsTensor"}, {"outputClusters", "outputClusterLinks", "outputClusterAssociations", "outputParticleIDs"}, ""} {} diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index 461ca42c97..439017df7e 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include @@ -23,41 +25,44 @@ void CalorimeterParticleIDPreML::init() { void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input& input, const CalorimeterParticleIDPreML::Output& output) const { - const auto [clusters, cluster_assocs] = input; + const auto [clusters, cluster_links] = input; auto [feature_tensors, target_tensors] = output; + if (cluster_links == nullptr || cluster_links->empty()) { + error("Cluster links are required for CalorimeterParticleIDPreML"); + throw std::runtime_error("Missing inputClusterLinks"); + } + edm4eic::MutableTensor feature_tensor = feature_tensors->create(); feature_tensor.addToShape(clusters->size()); feature_tensor.addToShape(11); // p, E/p, azimuthal, polar, 7 shape parameters feature_tensor.setElementType(1); // 1 - float edm4eic::MutableTensor target_tensor; - if (cluster_assocs != nullptr) { - target_tensor = target_tensors->create(); - target_tensor.addToShape(clusters->size()); - target_tensor.addToShape(2); // is electron, is hadron - target_tensor.setElementType(7); // 7 - int64 - } + target_tensor = target_tensors->create(); + target_tensor.addToShape(clusters->size()); + target_tensor.addToShape(2); // is electron, is hadron + target_tensor.setElementType(7); // 7 - int64 + + podio::LinkNavigator link_nav(*cluster_links); for (edm4eic::Cluster cluster : *clusters) { - double momentum = NAN; - { - // FIXME: use track momentum once matching to tracks becomes available - edm4eic::MCRecoClusterParticleAssociation best_assoc; - for (auto assoc : *cluster_assocs) { - if (assoc.getRec() == cluster) { - if ((not best_assoc.isAvailable()) || (assoc.getWeight() > best_assoc.getWeight())) { - best_assoc = assoc; - } - } - } - if (best_assoc.isAvailable()) { - momentum = edm4hep::utils::magnitude(best_assoc.getSim().getMomentum()); - } else { - warning("Can't find association for cluster. Skipping..."); - continue; + // FIXME: use track momentum once matching to tracks becomes available + edm4hep::MCParticle best_sim; + float best_weight = std::numeric_limits::lowest(); + bool found_assoc = false; + for (const auto& [sim_particle, weight] : link_nav.getLinked(cluster)) { + if (!found_assoc || weight > best_weight) { + best_sim = sim_particle; + best_weight = weight; + found_assoc = true; } } + if (!found_assoc) { + warning("Can't find association for cluster. Skipping..."); + continue; + } + const double momentum = edm4hep::utils::magnitude(best_sim.getMomentum()); feature_tensor.addToFloatData(momentum); feature_tensor.addToFloatData(cluster.getEnergy() / momentum); @@ -68,24 +73,10 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input feature_tensor.addToFloatData(cluster.getShapeParameters(par_ix)); } - if (cluster_assocs != nullptr) { - edm4eic::MCRecoClusterParticleAssociation best_assoc; - for (auto assoc : *cluster_assocs) { - if (assoc.getRec() == cluster) { - if ((not best_assoc.isAvailable()) || (assoc.getWeight() > best_assoc.getWeight())) { - best_assoc = assoc; - } - } - } - int64_t is_electron = 0; - int64_t is_pion = 0; - if (best_assoc.isAvailable()) { - is_electron = static_cast(best_assoc.getSim().getPDG() == 11); - is_pion = static_cast(best_assoc.getSim().getPDG() != 11); - } - target_tensor.addToInt64Data(is_pion); - target_tensor.addToInt64Data(is_electron); - } + int64_t is_electron = static_cast(best_sim.getPDG() == 11); + int64_t is_pion = static_cast(best_sim.getPDG() != 11); + target_tensor.addToInt64Data(is_pion); + target_tensor.addToInt64Data(is_electron); } std::size_t expected_num_entries = feature_tensor.getShape(0) * feature_tensor.getShape(1); diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.h b/src/algorithms/onnx/CalorimeterParticleIDPreML.h index 9ae4858d66..baee02aacf 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.h +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include @@ -17,7 +17,7 @@ namespace eicrecon { using CalorimeterParticleIDPreMLAlgorithm = algorithms::Algorithm< algorithms::Input>, + std::optional>, algorithms::Output>>; class CalorimeterParticleIDPreML : public CalorimeterParticleIDPreMLAlgorithm, @@ -25,8 +25,10 @@ class CalorimeterParticleIDPreML : public CalorimeterParticleIDPreMLAlgorithm, public: CalorimeterParticleIDPreML(std::string_view name) - : CalorimeterParticleIDPreMLAlgorithm{ - name, {"inputClusters"}, {"outputFeatureTensor", "outputTargetTensor"}, ""} {} + : CalorimeterParticleIDPreMLAlgorithm{name, + {"inputClusters", "inputClusterLinks"}, + {"outputFeatureTensor", "outputTargetTensor"}, + ""} {} void init() final; void process(const Input&, const Output&) const final; diff --git a/src/algorithms/pid/MatchToRICHPID.cc b/src/algorithms/pid/MatchToRICHPID.cc index b54a795712..dd150f0969 100644 --- a/src/algorithms/pid/MatchToRICHPID.cc +++ b/src/algorithms/pid/MatchToRICHPID.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -29,8 +30,9 @@ void MatchToRICHPID::init() {} void MatchToRICHPID::process(const MatchToRICHPID::Input& input, const MatchToRICHPID::Output& output) const { - const auto [parts_in, assocs_in, drich_cherenkov_pid] = input; - auto [parts_out, links_out, assocs_out, pids] = output; + const auto [parts_in, links_in, drich_cherenkov_pid] = input; + auto [parts_out, links_out, assocs_out, pids] = output; + podio::LinkNavigator link_nav(*links_in); for (auto part_in : *parts_in) { auto part_out = part_in.clone(); @@ -42,16 +44,15 @@ void MatchToRICHPID::process(const MatchToRICHPID::Input& input, part_out.getParticleIDUsed().isAvailable() ? part_out.getParticleIDUsed().getPDG() : 0); } - for (auto assoc_in : *assocs_in) { - if (assoc_in.getRec() == part_in) { - auto link_out = links_out->create(); - link_out.setFrom(part_out); - link_out.setTo(assoc_in.getSim()); - link_out.setWeight(assoc_in.getWeight()); - auto assoc_out = assoc_in.clone(); - assoc_out.setRec(part_out); - assocs_out->push_back(assoc_out); - } + for (const auto& [sim_particle, weight] : link_nav.getLinked(part_in)) { + auto link_out = links_out->create(); + link_out.setFrom(part_out); + link_out.setTo(sim_particle); + link_out.setWeight(weight); + auto assoc_out = assocs_out->create(); + assoc_out.setRec(part_out); + assoc_out.setSim(sim_particle); + assoc_out.setWeight(weight); } parts_out->push_back(part_out); diff --git a/src/algorithms/pid/MatchToRICHPID.h b/src/algorithms/pid/MatchToRICHPID.h index 65d3449ece..c4e7acf883 100644 --- a/src/algorithms/pid/MatchToRICHPID.h +++ b/src/algorithms/pid/MatchToRICHPID.h @@ -19,7 +19,7 @@ namespace eicrecon { using MatchToRICHPIDAlgorithm = algorithms::Algorithm< algorithms::Input, algorithms::Output< edm4eic::ReconstructedParticleCollection, edm4eic::MCRecoParticleLinkCollection, @@ -29,8 +29,7 @@ class MatchToRICHPID : public MatchToRICHPIDAlgorithm, public WithPodConfig #include #include +#include #include #include #include #include +#include #include #include #include @@ -54,8 +56,9 @@ void PIDLookup::init() { } void PIDLookup::process(const Input& input, const Output& output) const { - const auto [headers, recoparts_in, partassocs_in] = input; + const auto [headers, recoparts_in, partlinks_in] = input; auto [recoparts_out, partlinks_out, partassocs_out, partids_out] = output; + podio::LinkNavigator link_nav(*partlinks_in); // local random generator auto seed = m_uid.getUniqueID(*headers, name()); @@ -65,28 +68,31 @@ void PIDLookup::process(const Input& input, const Output& output) const { for (const auto& recopart_without_pid : *recoparts_in) { auto recopart = recopart_without_pid.clone(); - // Find MCParticle from associations and propagate the relevant ones further - auto best_assoc = edm4eic::MCRecoParticleAssociation::makeEmpty(); - for (auto assoc_in : *partassocs_in) { - if (assoc_in.getRec() == recopart_without_pid) { - if ((not best_assoc.isAvailable()) || (best_assoc.getWeight() < assoc_in.getWeight())) { - best_assoc = assoc_in; - } - auto link_out = partlinks_out->create(); - link_out.setFrom(recopart); - link_out.setTo(assoc_in.getSim()); - link_out.setWeight(assoc_in.getWeight()); - auto assoc_out = assoc_in.clone(); - assoc_out.setRec(recopart); - partassocs_out->push_back(assoc_out); + // Find MCParticle from links and propagate the relevant ones further + edm4hep::MCParticle best_sim; + float best_weight = std::numeric_limits::lowest(); + bool has_best = false; + for (const auto& [sim_particle, weight] : link_nav.getLinked(recopart_without_pid)) { + if (!has_best || best_weight < weight) { + best_sim = sim_particle; + best_weight = weight; + has_best = true; } + auto link_out = partlinks_out->create(); + link_out.setFrom(recopart); + link_out.setTo(sim_particle); + link_out.setWeight(weight); + auto assoc_out = partassocs_out->create(); + assoc_out.setRec(recopart); + assoc_out.setSim(sim_particle); + assoc_out.setWeight(weight); } - if (not best_assoc.isAvailable()) { + if (!has_best) { recoparts_out->push_back(recopart); continue; } - edm4hep::MCParticle mcpart = best_assoc.getSim(); + edm4hep::MCParticle mcpart = best_sim; int true_pdg = mcpart.getPDG(); int true_charge = mcpart.getCharge(); diff --git a/src/algorithms/pid_lut/PIDLookup.h b/src/algorithms/pid_lut/PIDLookup.h index 1df731acdb..94e16e9b04 100644 --- a/src/algorithms/pid_lut/PIDLookup.h +++ b/src/algorithms/pid_lut/PIDLookup.h @@ -26,7 +26,7 @@ namespace eicrecon { using PIDLookupAlgorithm = algorithms::Algorithm< algorithms::Input, + edm4eic::MCRecoParticleLinkCollection>, algorithms::Output< edm4eic::ReconstructedParticleCollection, edm4eic::MCRecoParticleLinkCollection, edm4eic::MCRecoParticleAssociationCollection, edm4hep::ParticleIDCollection>>; @@ -37,7 +37,7 @@ class PIDLookup : public PIDLookupAlgorithm, public WithPodConfigAdd(new JOmniFactoryGeneratorT( "TaggerTrackerTransportationPreML", - {"TaggerTrackerLocalTracks", "TaggerTrackerLocalTrackAssociations", "MCBeamElectrons"}, + {"TaggerTrackerLocalTracks", "TaggerTrackerLocalTrackLinks", "MCBeamElectrons"}, {"TaggerTrackerFeatureTensor", "TaggerTrackerTargetTensor"}, { .beamE = 10.0, diff --git a/src/factories/calorimetry/CalorimeterParticleIDPostML_factory.h b/src/factories/calorimetry/CalorimeterParticleIDPostML_factory.h index 0fb15d3882..f2fca7b603 100644 --- a/src/factories/calorimetry/CalorimeterParticleIDPostML_factory.h +++ b/src/factories/calorimetry/CalorimeterParticleIDPostML_factory.h @@ -20,7 +20,7 @@ class CalorimeterParticleIDPostML_factory std::unique_ptr m_algo; PodioInput m_cluster_input{this}; - PodioInput m_cluster_assoc_input{this}; + PodioInput m_cluster_link_input{this}; PodioInput m_prediction_tensor_input{this}; PodioOutput m_cluster_output{this}; @@ -37,7 +37,7 @@ class CalorimeterParticleIDPostML_factory } void Process(int32_t /* run_number */, uint64_t /* event_number */) { - m_algo->process({m_cluster_input(), m_cluster_assoc_input(), m_prediction_tensor_input()}, + m_algo->process({m_cluster_input(), m_cluster_link_input(), m_prediction_tensor_input()}, {m_cluster_output().get(), m_cluster_links_output().get(), m_cluster_assoc_output().get(), m_particle_id_output().get()}); } diff --git a/src/factories/calorimetry/CalorimeterParticleIDPreML_factory.h b/src/factories/calorimetry/CalorimeterParticleIDPreML_factory.h index 066843f902..8c68a09327 100644 --- a/src/factories/calorimetry/CalorimeterParticleIDPreML_factory.h +++ b/src/factories/calorimetry/CalorimeterParticleIDPreML_factory.h @@ -19,7 +19,7 @@ class CalorimeterParticleIDPreML_factory std::unique_ptr m_algo; PodioInput m_cluster_input{this}; - PodioInput m_cluster_assoc_input{this}; + PodioInput m_cluster_link_input{this}; PodioOutput m_feature_tensor_output{this}; PodioOutput m_target_tensor_output{this}; @@ -33,7 +33,7 @@ class CalorimeterParticleIDPreML_factory } void Process(int32_t /* run_number */, uint64_t /* event_number */) { - m_algo->process({m_cluster_input(), m_cluster_assoc_input()}, + m_algo->process({m_cluster_input(), m_cluster_link_input()}, {m_feature_tensor_output().get(), m_target_tensor_output().get()}); } }; diff --git a/src/factories/fardetectors/FarDetectorTransportationPreML_factory.h b/src/factories/fardetectors/FarDetectorTransportationPreML_factory.h index 769a0ea6a2..20b8332ec9 100644 --- a/src/factories/fardetectors/FarDetectorTransportationPreML_factory.h +++ b/src/factories/fardetectors/FarDetectorTransportationPreML_factory.h @@ -20,7 +20,7 @@ class FarDetectorTransportationPreML_factory std::unique_ptr m_algo; PodioInput m_track_input{this}; - PodioInput m_association_input{this}; + PodioInput m_link_input{this}; PodioInput m_beamelectrons_input{this}; PodioOutput m_feature_tensor_output{this}; @@ -39,7 +39,7 @@ class FarDetectorTransportationPreML_factory } void Process(int32_t /* run_number */, uint64_t /* event_number */) { - m_algo->process({m_track_input(), m_association_input(), m_beamelectrons_input()}, + m_algo->process({m_track_input(), m_link_input(), m_beamelectrons_input()}, {m_feature_tensor_output().get(), m_target_tensor_output().get()}); } }; diff --git a/src/factories/pid/MatchToRICHPID_factory.h b/src/factories/pid/MatchToRICHPID_factory.h index 2936530bdc..c7270318be 100644 --- a/src/factories/pid/MatchToRICHPID_factory.h +++ b/src/factories/pid/MatchToRICHPID_factory.h @@ -22,7 +22,7 @@ class MatchToRICHPID_factory : public JOmniFactory m_algo; PodioInput m_recoparticles_input{this}; - PodioInput m_assocs_input{this}; + PodioInput m_links_input{this}; PodioInput m_cherenkov_particle_ids_input{this}; PodioOutput m_recoparticles_output{this}; PodioOutput m_links_output{this}; @@ -38,7 +38,7 @@ class MatchToRICHPID_factory : public JOmniFactoryprocess({m_recoparticles_input(), m_assocs_input(), m_cherenkov_particle_ids_input()}, + m_algo->process({m_recoparticles_input(), m_links_input(), m_cherenkov_particle_ids_input()}, {m_recoparticles_output().get(), m_links_output().get(), m_assocs_output().get(), m_pids_output().get()}); } diff --git a/src/factories/pid_lut/PIDLookup_factory.h b/src/factories/pid_lut/PIDLookup_factory.h index 3f3ab531bb..4068add031 100644 --- a/src/factories/pid_lut/PIDLookup_factory.h +++ b/src/factories/pid_lut/PIDLookup_factory.h @@ -24,7 +24,7 @@ class PIDLookup_factory : public JOmniFactory m_event_headers_input{this}; PodioInput m_recoparticles_input{this}; - PodioInput m_recoparticle_assocs_input{this}; + PodioInput m_recoparticle_links_input{this}; PodioOutput m_recoparticles_output{this}; PodioOutput m_recoparticle_links_output{this}; PodioOutput m_recoparticle_assocs_output{this}; @@ -46,7 +46,7 @@ class PIDLookup_factory : public JOmniFactoryprocess( - {m_event_headers_input(), m_recoparticles_input(), m_recoparticle_assocs_input()}, + {m_event_headers_input(), m_recoparticles_input(), m_recoparticle_links_input()}, {m_recoparticles_output().get(), m_recoparticle_links_output().get(), m_recoparticle_assocs_output().get(), m_particleids_output().get()}); } diff --git a/src/global/pid/pid.cc b/src/global/pid/pid.cc index fe2f3e72e3..7f7fff4756 100644 --- a/src/global/pid/pid.cc +++ b/src/global/pid/pid.cc @@ -22,9 +22,9 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "ChargedParticlesWithAssociations", { - "ReconstructedChargedWithoutPIDParticles", // edm4eic::ReconstructedParticle - "ReconstructedChargedWithoutPIDParticleAssociations", // edm4eic::MCRecoParticleAssociationCollection - "DRICHMergedIrtCherenkovParticleID", // edm4eic::CherenkovParticleID + "ReconstructedChargedWithoutPIDParticles", // edm4eic::ReconstructedParticle + "ReconstructedChargedWithoutPIDParticleLinks", // edm4eic::MCRecoParticleLinkCollection + "DRICHMergedIrtCherenkovParticleID", // edm4eic::CherenkovParticleID }, { "ReconstructedChargedRealPIDParticles", // edm4eic::ReconstructedParticle diff --git a/src/global/pid_lut/pid_lut.cc b/src/global/pid_lut/pid_lut.cc index 9edae21842..371e237dd3 100644 --- a/src/global/pid_lut/pid_lut.cc +++ b/src/global/pid_lut/pid_lut.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -49,7 +50,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedTruthSeededChargedWithoutPIDParticles", - "ReconstructedTruthSeededChargedWithoutPIDParticleAssociations", + "ReconstructedTruthSeededChargedWithoutPIDParticleLinks", }, { "ReconstructedTruthSeededChargedWithPFRICHPIDParticles", @@ -64,7 +65,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedChargedWithoutPIDParticles", - "ReconstructedChargedWithoutPIDParticleAssociations", + "ReconstructedChargedWithoutPIDParticleLinks", }, { "ReconstructedChargedWithPFRICHPIDParticles", @@ -97,7 +98,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedTruthSeededChargedWithPFRICHPIDParticles", - "ReconstructedTruthSeededChargedWithPFRICHPIDParticleAssociations", + "ReconstructedTruthSeededChargedWithPFRICHPIDParticleLinks", }, { "ReconstructedTruthSeededChargedWithPFRICHTOFPIDParticles", @@ -112,7 +113,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedChargedWithPFRICHPIDParticles", - "ReconstructedChargedWithPFRICHPIDParticleAssociations", + "ReconstructedChargedWithPFRICHPIDParticleLinks", }, { "ReconstructedChargedWithPFRICHTOFPIDParticles", @@ -156,7 +157,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedTruthSeededChargedWithPFRICHTOFPIDParticles", - "ReconstructedTruthSeededChargedWithPFRICHTOFPIDParticleAssociations", + "ReconstructedTruthSeededChargedWithPFRICHTOFPIDParticleLinks", }, { "ReconstructedTruthSeededChargedWithPFRICHTOFDIRCPIDParticles", @@ -171,7 +172,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedChargedWithPFRICHTOFPIDParticles", - "ReconstructedChargedWithPFRICHTOFPIDParticleAssociations", + "ReconstructedChargedWithPFRICHTOFPIDParticleLinks", }, { "ReconstructedChargedWithPFRICHTOFDIRCPIDParticles", @@ -199,6 +200,13 @@ void InitPlugin(JApplication* app) { "TaggerTrackerReconstructedParticleAssociations"}, {"ReconstructedChargedWithPFRICHTOFDIRCLOWQ2PIDParticleAssociations"}, app)); + app->Add( + new JOmniFactoryGeneratorT>( + "ReconstructedChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks", + {"ReconstructedChargedWithPFRICHTOFDIRCPIDParticleLinks", + "TaggerTrackerReconstructedParticleLinks"}, + {"ReconstructedChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks"}, app)); + // And the same for truth seeded particles and associations app->Add( @@ -215,6 +223,13 @@ void InitPlugin(JApplication* app) { "TaggerTrackerReconstructedParticleAssociations"}, {"ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticleAssociations"}, app)); + app->Add( + new JOmniFactoryGeneratorT>( + "ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks", + {"ReconstructedTruthSeededChargedWithPFRICHTOFDIRCPIDParticleLinks", + "TaggerTrackerReconstructedParticleLinks"}, + {"ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks"}, app)); + //------------------------------------------------------------------------- // DRICH PID //------------------------------------------------------------------------- @@ -249,7 +264,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticles", - "ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticleAssociations", + "ReconstructedTruthSeededChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks", }, { "ReconstructedTruthSeededChargedParticles", @@ -264,7 +279,7 @@ void InitPlugin(JApplication* app) { { "EventHeader", "ReconstructedWithPFRICHTOFDIRCLOWQ2PIDChargedParticles", - "ReconstructedChargedWithPFRICHTOFDIRCLOWQ2PIDParticleAssociations", + "ReconstructedChargedWithPFRICHTOFDIRCLOWQ2PIDParticleLinks", }, { "ReconstructedChargedParticles", diff --git a/src/tests/algorithms_test/pid_lut_PIDLookup.cc b/src/tests/algorithms_test/pid_lut_PIDLookup.cc index 63ba444961..9da6b44f5f 100644 --- a/src/tests/algorithms_test/pid_lut_PIDLookup.cc +++ b/src/tests/algorithms_test/pid_lut_PIDLookup.cc @@ -51,9 +51,9 @@ TEST_CASE("particles acquire PID", "[PIDLookup]") { auto headers = std::make_unique(); auto header = headers->create(1, 1, 12345678, 1.0); - auto parts_in = std::make_unique(); - auto assocs_in = std::make_unique(); - auto mcparts = std::make_unique(); + auto parts_in = std::make_unique(); + auto mcparts = std::make_unique(); + edm4eic::MCRecoParticleLinkCollection links_in; parts_in->create(0, // std::int32_t type 0.5, // float energy @@ -78,19 +78,20 @@ TEST_CASE("particles acquire PID", "[PIDLookup]") { 9 // int32_t helicity (9 if unset) ); - auto assoc_in = assocs_in->create(); - assoc_in.setRec((*parts_in)[0]); - assoc_in.setSim((*mcparts)[0]); + auto link_in = links_in.create(); + link_in.setFrom((*parts_in)[0]); + link_in.setTo((*mcparts)[0]); + link_in.setWeight(0.F); auto parts_out = std::make_unique(); auto assocs_out = std::make_unique(); auto partids_out = std::make_unique(); edm4eic::MCRecoParticleLinkCollection links_out; - algo.process({headers.get(), parts_in.get(), assocs_in.get()}, + algo.process({headers.get(), parts_in.get(), &links_in}, {parts_out.get(), &links_out, assocs_out.get(), partids_out.get()}); REQUIRE((*parts_in).size() == (*parts_out).size()); - REQUIRE((*assocs_in).size() == (*assocs_out).size()); + REQUIRE(links_in.size() == (*assocs_out).size()); REQUIRE( 0 == (*partids_out).size()); // Since our table is empty, there will not be a successful lookup From c9ae7a89632ec16396e0779b61d390217b1471e3 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 16:37:21 -0500 Subject: [PATCH 02/16] fix: Apply suggestions from code review (headers; map; optional::value) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/algorithms/fardetectors/FarDetectorLinearTracking.cc | 6 ++++-- src/algorithms/interfaces/LinkTruthUtils.h | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index 098ecf16b4..d041203971 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -36,10 +36,12 @@ #include #include +#include + #include "FarDetectorLinearTracking.h" #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h" +#include "algorithms/interfaces/CompareObjectID.h" #include "algorithms/interfaces/LinkTruthUtils.h" - namespace eicrecon { void FarDetectorLinearTracking::init() { @@ -204,7 +206,7 @@ void FarDetectorLinearTracking::checkHitCombination( charge, chi2, ndf, pdg); // Add Measurement2D relations and count occurrence of particles contributing to the track - std::unordered_map particleCount; + std::map> particleCount; for (std::size_t layer = 0; layer < layerHitIndex.size(); layer++) { track.addToMeasurements((*inputHits[layer])[layerHitIndex[layer]]); if (do_assoc) { diff --git a/src/algorithms/interfaces/LinkTruthUtils.h b/src/algorithms/interfaces/LinkTruthUtils.h index ce786a5b6b..0f088750ff 100644 --- a/src/algorithms/interfaces/LinkTruthUtils.h +++ b/src/algorithms/interfaces/LinkTruthUtils.h @@ -22,8 +22,7 @@ template class EventLinkNavigator { bool enabled() const { return m_enabled; } - const podio::LinkNavigator& navigator() const { return *m_nav; } - + const podio::LinkNavigator& navigator() const { return m_nav.value(); } template auto linked(const SrcT& src) const { using ReturnT = decltype(std::declval>().getLinked(src)); return m_enabled ? m_nav->getLinked(src) : ReturnT{}; From f053ebcaf454fd2b086b3a8ed84dfe36cf98ab45 Mon Sep 17 00:00:00 2001 From: epic-capybara <139920704+epic-capybara@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:49:32 -0400 Subject: [PATCH 03/16] refactor: unify LinkNavigator truth-link handling (fix: iwyu) (#2782) This PR applies the include-what-you-use fixes as suggested by https://github.com/eic/EICrecon/actions/runs/30128356954. Please merge this PR into the branch `wdconinc-linknavigator-migration` to resolve failures in PR #2780. Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../calorimetry/CalorimeterClusterRecoCoG.cc | 3 ++- .../calorimetry/CalorimeterClusterRecoCoG.h | 2 -- src/algorithms/calorimetry/CalorimeterClusterShape.cc | 1 + src/algorithms/calorimetry/ImagingClusterReco.cc | 6 ++---- src/algorithms/calorimetry/ImagingClusterReco.h | 4 ---- .../fardetectors/FarDetectorLinearTracking.cc | 7 +++---- .../fardetectors/FarDetectorTransportationPreML.cc | 3 ++- src/algorithms/onnx/CalorimeterParticleIDPostML.cc | 1 + src/algorithms/onnx/CalorimeterParticleIDPreML.cc | 11 +++++------ src/global/pid_lut/pid_lut.cc | 2 ++ 10 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc index 3fc57e5ce2..ce4064d334 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -20,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h index 7abf1f2ec9..29495b3549 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h @@ -17,8 +17,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.cc b/src/algorithms/calorimetry/CalorimeterClusterShape.cc index 3661facaa4..6f87f10916 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/src/algorithms/calorimetry/ImagingClusterReco.cc b/src/algorithms/calorimetry/ImagingClusterReco.cc index d4f3089e13..72b6858bf0 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.cc +++ b/src/algorithms/calorimetry/ImagingClusterReco.cc @@ -9,6 +9,8 @@ */ #include +#include +#include #include #include #include @@ -16,8 +18,6 @@ #include #include #include -#include -#include #include #include // IWYU pragma: keep #include @@ -25,9 +25,7 @@ #include #include #include -#include #include -#include #include #include "algorithms/calorimetry/ClusterTypes.h" diff --git a/src/algorithms/calorimetry/ImagingClusterReco.h b/src/algorithms/calorimetry/ImagingClusterReco.h index 7ee647b559..a7736f74e6 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.h +++ b/src/algorithms/calorimetry/ImagingClusterReco.h @@ -18,11 +18,7 @@ #include #include #include -#include -// Event Model related classes -#include #include -#include #include #include #include diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index d041203971..8981273cf7 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -20,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -30,18 +30,17 @@ #include #include #include +#include #include #include #include -#include #include -#include - #include "FarDetectorLinearTracking.h" #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h" #include "algorithms/interfaces/CompareObjectID.h" #include "algorithms/interfaces/LinkTruthUtils.h" + namespace eicrecon { void FarDetectorLinearTracking::init() { diff --git a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc index 265ae29681..bfc7e4332a 100644 --- a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc +++ b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc @@ -6,8 +6,9 @@ #include #include #include -#include #include +#include +#include #include "FarDetectorTransportationPreML.h" #include "algorithms/fardetectors/FarDetectorTransportationPreML.h" diff --git a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc index 02e33e3198..4a0c3fa8ec 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include "CalorimeterParticleIDPostML.h" diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index 439017df7e..e89888f2da 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -1,18 +1,17 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // Copyright (C) 2024 Dmitry Kalinkin -#include -#include #include #include #include -#include +#include #include -#include +#include +#include #include #include - -#include +#include +#include #include "CalorimeterParticleIDPreML.h" diff --git a/src/global/pid_lut/pid_lut.cc b/src/global/pid_lut/pid_lut.cc index 371e237dd3..4872f75e94 100644 --- a/src/global/pid_lut/pid_lut.cc +++ b/src/global/pid_lut/pid_lut.cc @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include #include From 40f0a8458321aa157282073e50afe4a652653f98 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 17:51:08 -0500 Subject: [PATCH 04/16] fix: clang-tidy: modernize-use-auto, readability-container-size-empty Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/algorithms/onnx/CalorimeterParticleIDPreML.cc | 4 ++-- src/tests/algorithms_test/pid_lut_PIDLookup.cc | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index e89888f2da..125942589f 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -72,8 +72,8 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input feature_tensor.addToFloatData(cluster.getShapeParameters(par_ix)); } - int64_t is_electron = static_cast(best_sim.getPDG() == 11); - int64_t is_pion = static_cast(best_sim.getPDG() != 11); + auto is_electron = static_cast(best_sim.getPDG() == 11); + auto is_pion = static_cast(best_sim.getPDG() != 11); target_tensor.addToInt64Data(is_pion); target_tensor.addToInt64Data(is_electron); } diff --git a/src/tests/algorithms_test/pid_lut_PIDLookup.cc b/src/tests/algorithms_test/pid_lut_PIDLookup.cc index 9da6b44f5f..3d5ad6b00a 100644 --- a/src/tests/algorithms_test/pid_lut_PIDLookup.cc +++ b/src/tests/algorithms_test/pid_lut_PIDLookup.cc @@ -93,8 +93,7 @@ TEST_CASE("particles acquire PID", "[PIDLookup]") { REQUIRE((*parts_in).size() == (*parts_out).size()); REQUIRE(links_in.size() == (*assocs_out).size()); REQUIRE( - 0 == - (*partids_out).size()); // Since our table is empty, there will not be a successful lookup + (*partids_out).empty()); // Since our table is empty, there will not be a successful lookup // Verify that links were created and match the associations REQUIRE(links_out.size() == (*assocs_out).size()); From be1df746570e0feee516e799d7b80f33b825f4c7 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 15:34:56 -0500 Subject: [PATCH 05/16] refactor: consolidate LinkNavigator usage Migrate selected algorithms to link-based truth lookup via podio::LinkNavigator, rewire affected factories and pipeline wiring to consume link collections, and add shared LinkTruthUtils helpers to remove repeated per-event navigator and relation boilerplate. Also harden FarDetector link-absent handling and update PIDLookup algorithm tests for link-input semantics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../fardetectors/FarDetectorLinearTracking.cc | 1 - src/algorithms/interfaces/LinkTruthUtils.h | 2 +- .../onnx/CalorimeterParticleIDPreML.cc | 34 +++++++++---------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index 8981273cf7..5d695faeb3 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/src/algorithms/interfaces/LinkTruthUtils.h b/src/algorithms/interfaces/LinkTruthUtils.h index 0f088750ff..f8dbb9b37a 100644 --- a/src/algorithms/interfaces/LinkTruthUtils.h +++ b/src/algorithms/interfaces/LinkTruthUtils.h @@ -22,7 +22,7 @@ template class EventLinkNavigator { bool enabled() const { return m_enabled; } - const podio::LinkNavigator& navigator() const { return m_nav.value(); } + const podio::LinkNavigator& navigator() const { return *m_nav; } template auto linked(const SrcT& src) const { using ReturnT = decltype(std::declval>().getLinked(src)); return m_enabled ? m_nav->getLinked(src) : ReturnT{}; diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index 125942589f..d9a83268bb 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -14,6 +13,7 @@ #include #include "CalorimeterParticleIDPreML.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -26,11 +26,9 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input const auto [clusters, cluster_links] = input; auto [feature_tensors, target_tensors] = output; - - if (cluster_links == nullptr || cluster_links->empty()) { - error("Cluster links are required for CalorimeterParticleIDPreML"); - throw std::runtime_error("Missing inputClusterLinks"); - } + const truth::EventLinkNavigator link_nav( + cluster_links); + const bool fill_targets = link_nav.enabled(); edm4eic::MutableTensor feature_tensor = feature_tensors->create(); feature_tensor.addToShape(clusters->size()); @@ -38,19 +36,19 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input feature_tensor.setElementType(1); // 1 - float edm4eic::MutableTensor target_tensor; - target_tensor = target_tensors->create(); - target_tensor.addToShape(clusters->size()); - target_tensor.addToShape(2); // is electron, is hadron - target_tensor.setElementType(7); // 7 - int64 - - podio::LinkNavigator link_nav(*cluster_links); + if (fill_targets) { + target_tensor = target_tensors->create(); + target_tensor.addToShape(clusters->size()); + target_tensor.addToShape(2); // is electron, is hadron + target_tensor.setElementType(7); // 7 - int64 + } for (edm4eic::Cluster cluster : *clusters) { // FIXME: use track momentum once matching to tracks becomes available edm4hep::MCParticle best_sim; float best_weight = std::numeric_limits::lowest(); bool found_assoc = false; - for (const auto& [sim_particle, weight] : link_nav.getLinked(cluster)) { + for (const auto& [sim_particle, weight] : link_nav.linked(cluster)) { if (!found_assoc || weight > best_weight) { best_sim = sim_particle; best_weight = weight; @@ -72,10 +70,12 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input feature_tensor.addToFloatData(cluster.getShapeParameters(par_ix)); } - auto is_electron = static_cast(best_sim.getPDG() == 11); - auto is_pion = static_cast(best_sim.getPDG() != 11); - target_tensor.addToInt64Data(is_pion); - target_tensor.addToInt64Data(is_electron); + if (fill_targets) { + int64_t is_electron = static_cast(best_sim.getPDG() == 11); + int64_t is_pion = static_cast(best_sim.getPDG() != 11); + target_tensor.addToInt64Data(is_pion); + target_tensor.addToInt64Data(is_electron); + } } std::size_t expected_num_entries = feature_tensor.getShape(0) * feature_tensor.getShape(1); From 52a5b713076fa3c4cc8d18d436c206450d36f7e1 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 16:37:21 -0500 Subject: [PATCH 06/16] fix: Apply suggestions from code review (headers; map; optional::value) Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/algorithms/fardetectors/FarDetectorLinearTracking.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index 5d695faeb3..430db9e653 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -35,11 +35,12 @@ #include #include +#include + #include "FarDetectorLinearTracking.h" #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h" #include "algorithms/interfaces/CompareObjectID.h" #include "algorithms/interfaces/LinkTruthUtils.h" - namespace eicrecon { void FarDetectorLinearTracking::init() { From 42baa18231de9c7c9de9e53298571e2f67d52a38 Mon Sep 17 00:00:00 2001 From: epic-capybara <139920704+epic-capybara@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:49:32 -0400 Subject: [PATCH 07/16] refactor: unify LinkNavigator truth-link handling (fix: iwyu) (#2782) This PR applies the include-what-you-use fixes as suggested by https://github.com/eic/EICrecon/actions/runs/30128356954. Please merge this PR into the branch `wdconinc-linknavigator-migration` to resolve failures in PR #2780. Auto-generated by [create-pull-request][1] [1]: https://github.com/peter-evans/create-pull-request Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/algorithms/fardetectors/FarDetectorLinearTracking.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc index 430db9e653..8981273cf7 100644 --- a/src/algorithms/fardetectors/FarDetectorLinearTracking.cc +++ b/src/algorithms/fardetectors/FarDetectorLinearTracking.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -35,12 +36,11 @@ #include #include -#include - #include "FarDetectorLinearTracking.h" #include "algorithms/fardetectors/FarDetectorLinearTrackingConfig.h" #include "algorithms/interfaces/CompareObjectID.h" #include "algorithms/interfaces/LinkTruthUtils.h" + namespace eicrecon { void FarDetectorLinearTracking::init() { From 425e0e23aa836bda5ac79bfb34d2b63c2471f4c0 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 24 Jul 2026 17:51:08 -0500 Subject: [PATCH 08/16] fix: clang-tidy: modernize-use-auto, readability-container-size-empty Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/algorithms/onnx/CalorimeterParticleIDPreML.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index d9a83268bb..5b400d4142 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -71,8 +71,8 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input } if (fill_targets) { - int64_t is_electron = static_cast(best_sim.getPDG() == 11); - int64_t is_pion = static_cast(best_sim.getPDG() != 11); + auto is_electron = static_cast(best_sim.getPDG() == 11); + auto is_pion = static_cast(best_sim.getPDG() != 11); target_tensor.addToInt64Data(is_pion); target_tensor.addToInt64Data(is_electron); } From ac5c9f1b16f9d8f6f0edaacabbd245c039659ce4 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 15:16:17 -0500 Subject: [PATCH 09/16] refactor: switch CalorimeterClusterShape to link-based input Replace the MCRecoClusterParticleAssociation input with MCRecoClusterParticleLink in the algorithm, factory, and all detector wiring. The intermediate local-link-collection workaround in the algorithm is removed; it now constructs an EventLinkNavigator directly from the link input and delegates relation emission to the shared truth::addWeightedRelation helper. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../calorimetry/CalorimeterClusterShape.cc | 38 ++++--------------- .../calorimetry/CalorimeterClusterShape.h | 5 +-- src/detectors/B0ECAL/B0ECAL.cc | 4 +- src/detectors/BEMC/BEMC.cc | 5 +-- src/detectors/BHCAL/BHCAL.cc | 6 +-- src/detectors/EEMC/EEMC.cc | 7 ++-- src/detectors/EHCAL/EHCAL.cc | 6 +-- src/detectors/FEMC/FEMC.cc | 6 +-- src/detectors/FHCAL/FHCAL.cc | 11 +++--- src/detectors/LUMISPECCAL/LUMISPECCAL.cc | 5 +-- src/detectors/ZDC/ZDC.cc | 12 +++--- .../CalorimeterClusterShape_factory.h | 4 +- 12 files changed, 40 insertions(+), 69 deletions(-) diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.cc b/src/algorithms/calorimetry/CalorimeterClusterShape.cc index 6f87f10916..f024550a49 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.cc @@ -11,9 +11,6 @@ #include #include #include -#include -#include -#include #include #include #include // IWYU pragma: keep @@ -22,13 +19,13 @@ #include #include #include -#include #include #include #include #include #include "algorithms/calorimetry/CalorimeterClusterShapeConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -64,22 +61,10 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu const CalorimeterClusterShape::Output& output) const { // grab inputs/outputs - const auto [in_clusters, in_associations] = input; + const auto [in_clusters, in_links] = input; auto [out_clusters, out_links, out_associations] = output; - edm4eic::MCRecoClusterParticleLinkCollection in_links; - std::optional> link_nav; - if (in_associations != nullptr) { - for (const auto& in_assoc : *in_associations) { - auto in_link = in_links.create(); - in_link.setFrom(in_assoc.getRec()); - in_link.setTo(in_assoc.getSim()); - in_link.setWeight(in_assoc.getWeight()); - } - if (!in_links.empty()) { - link_nav.emplace(in_links); - } - } + const truth::EventLinkNavigator link_nav(in_links); // exit if no clusters in collection if (in_clusters->empty()) { @@ -242,20 +227,13 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu out_clusters->push_back(out_clust); // ---------------------------------------------------------------------- - // if provided, copy associations + // if provided, copy links and associations // ---------------------------------------------------------------------- - if (link_nav) { - for (const auto& [mc_par, weight] : link_nav->getLinked(in_clust)) { - auto out_link = out_links->create(); - out_link.setFrom(out_clust); - out_link.setTo(mc_par); - out_link.setWeight(weight); - auto out_assoc = out_associations->create(); - out_assoc.setRec(out_clust); - out_assoc.setSim(mc_par); - out_assoc.setWeight(weight); + if (link_nav.enabled()) { + for (const auto& [mc_par, weight] : link_nav.navigator().getLinked(in_clust)) { + truth::addWeightedRelation(out_clust, mc_par, weight, out_links, out_associations); } - } // end input association loop + } // end input link loop } // end input cluster loop debug("Completed processing input clusters"); diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.h b/src/algorithms/calorimetry/CalorimeterClusterShape.h index c3e86177eb..9b28059ca7 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.h +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.h @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -26,7 +25,7 @@ namespace eicrecon { // -------------------------------------------------------------------------- using CalorimeterClusterShapeAlgorithm = algorithms::Algorithm< algorithms::Input>, + std::optional>, algorithms::Output, std::optional>>; @@ -46,7 +45,7 @@ class CalorimeterClusterShape : public CalorimeterClusterShapeAlgorithm, CalorimeterClusterShape(std::string_view name) : CalorimeterClusterShapeAlgorithm{ name, - {"inputClusters", "inputMCClusterAssociations"}, + {"inputClusters", "inputMCClusterLinks"}, {"outputClusters", "outputMCClusterLinks", "outputMCClusterAssociations"}, "Computes cluster shape parameters"} {} diff --git a/src/detectors/B0ECAL/B0ECAL.cc b/src/detectors/B0ECAL/B0ECAL.cc index 667b52ec0f..a7d82b4f7f 100644 --- a/src/detectors/B0ECAL/B0ECAL.cc +++ b/src/detectors/B0ECAL/B0ECAL.cc @@ -95,7 +95,7 @@ void InitPlugin(JApplication* app) { app)); app->Add(new JOmniFactoryGeneratorT( - "B0ECalClusters", {"B0ECalClustersWithoutShapes", "B0ECalClusterAssociationsWithoutShapes"}, + "B0ECalClusters", {"B0ECalClustersWithoutShapes", "B0ECalClusterLinksWithoutShapes"}, {"B0ECalClusters", "B0ECalClusterLinks", "B0ECalClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -114,7 +114,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "B0ECalTruthClusters", - {"B0ECalTruthClustersWithoutShapes", "B0ECalTruthClusterAssociationsWithoutShapes"}, + {"B0ECalTruthClustersWithoutShapes", "B0ECalTruthClusterLinksWithoutShapes"}, {"B0ECalTruthClusters", "B0ECalTruthClusterLinks", "B0ECalTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); } diff --git a/src/detectors/BEMC/BEMC.cc b/src/detectors/BEMC/BEMC.cc index 0628a86917..a060a9e4d2 100644 --- a/src/detectors/BEMC/BEMC.cc +++ b/src/detectors/BEMC/BEMC.cc @@ -311,7 +311,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelScFiClusters", - {"EcalBarrelScFiClustersWithoutShapes", "EcalBarrelScFiClusterAssociationsWithoutShapes"}, + {"EcalBarrelScFiClustersWithoutShapes", "EcalBarrelScFiClusterLinksWithoutShapes"}, {"EcalBarrelScFiClusters", "EcalBarrelScFiClusterLinks", "EcalBarrelScFiClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -441,8 +441,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "EcalBarrelImagingClusters", - {"EcalBarrelImagingClustersWithoutShapes", - "EcalBarrelImagingClusterAssociationsWithoutShapes"}, + {"EcalBarrelImagingClustersWithoutShapes", "EcalBarrelImagingClusterLinksWithoutShapes"}, {"EcalBarrelImagingClusters", "EcalBarrelImagingClusterLinks", "EcalBarrelImagingClusterAssociations"}, {.longitudinalShowerInfoAvailable = false, .energyWeight = "log", .logWeightBase = 6.2}, diff --git a/src/detectors/BHCAL/BHCAL.cc b/src/detectors/BHCAL/BHCAL.cc index 04ebfdd430..a2248a211a 100644 --- a/src/detectors/BHCAL/BHCAL.cc +++ b/src/detectors/BHCAL/BHCAL.cc @@ -137,7 +137,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalBarrelClusters", - {"HcalBarrelClustersWithoutShapes", "HcalBarrelClusterAssociationsWithoutShapes"}, + {"HcalBarrelClustersWithoutShapes", "HcalBarrelClusterLinksWithoutShapes"}, {"HcalBarrelClusters", "HcalBarrelClusterLinks", "HcalBarrelClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -156,7 +156,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalBarrelTruthClusters", - {"HcalBarrelTruthClustersWithoutShapes", "HcalBarrelTruthClusterAssociationsWithoutShapes"}, + {"HcalBarrelTruthClustersWithoutShapes", "HcalBarrelTruthClusterLinksWithoutShapes"}, {"HcalBarrelTruthClusters", "HcalBarrelTruthClusterLinks", "HcalBarrelTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -191,7 +191,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalBarrelSplitMergeClusters", {"HcalBarrelSplitMergeClustersWithoutShapes", - "HcalBarrelSplitMergeClusterAssociationsWithoutShapes"}, + "HcalBarrelSplitMergeClusterLinksWithoutShapes"}, {"HcalBarrelSplitMergeClusters", "HcalBarrelSplitMergeClusterLinks", "HcalBarrelSplitMergeClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); diff --git a/src/detectors/EEMC/EEMC.cc b/src/detectors/EEMC/EEMC.cc index f8e0f51a10..e8f0ce81d4 100644 --- a/src/detectors/EEMC/EEMC.cc +++ b/src/detectors/EEMC/EEMC.cc @@ -119,7 +119,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalEndcapNTruthClusters", - {"EcalEndcapNTruthClustersWithoutShapes", "EcalEndcapNTruthClusterAssociationsWithoutShapes"}, + {"EcalEndcapNTruthClustersWithoutShapes", "EcalEndcapNTruthClusterLinksWithoutShapes"}, {"EcalEndcapNTruthClusters", "EcalEndcapNTruthClusterLinks", "EcalEndcapNTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 4.6}, app)); @@ -145,8 +145,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalEndcapNClustersWithoutPID", - {"EcalEndcapNClustersWithoutPIDAndShapes", - "EcalEndcapNClusterAssociationsWithoutPIDAndShapes"}, + {"EcalEndcapNClustersWithoutPIDAndShapes", "EcalEndcapNClusterLinksWithoutPIDAndShapes"}, {"EcalEndcapNClustersWithoutPID", "EcalEndcapNClusterLinksWithoutPID", "EcalEndcapNClusterAssociationsWithoutPID"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -224,7 +223,7 @@ void InitPlugin(JApplication* app) { "EcalEndcapNSplitMergeClusters", {"EcalEndcapNSplitMergeClustersWithoutShapes", - "EcalEndcapNSplitMergeClusterAssociationsWithoutShapes"}, + "EcalEndcapNSplitMergeClusterLinksWithoutShapes"}, {"EcalEndcapNSplitMergeClusters", "EcalEndcapNSplitMergeClusterLinks", "EcalEndcapNSplitMergeClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); diff --git a/src/detectors/EHCAL/EHCAL.cc b/src/detectors/EHCAL/EHCAL.cc index 03d69ceaf3..621ddf01bb 100644 --- a/src/detectors/EHCAL/EHCAL.cc +++ b/src/detectors/EHCAL/EHCAL.cc @@ -116,7 +116,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapNTruthClusters", - {"HcalEndcapNTruthClustersWithoutShapes", "HcalEndcapNTruthClusterAssociationsWithoutShapes"}, + {"HcalEndcapNTruthClustersWithoutShapes", "HcalEndcapNTruthClusterLinksWithoutShapes"}, {"HcalEndcapNTruthClusters", "HcalEndcapNTruthClusterLinks", "HcalEndcapNTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -139,7 +139,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "HcalEndcapNClusters", - {"HcalEndcapNClustersWithoutShapes", "HcalEndcapNClusterAssociationsWithoutShapes"}, + {"HcalEndcapNClustersWithoutShapes", "HcalEndcapNClusterLinksWithoutShapes"}, {"HcalEndcapNClusters", "HcalEndcapNClusterLinks", "HcalEndcapNClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); app->Add(new JOmniFactoryGeneratorT( @@ -173,7 +173,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalEndcapNSplitMergeClusters", {"HcalEndcapNSplitMergeClustersWithoutShapes", - "HcalEndcapNSplitMergeClusterAssociationsWithoutShapes"}, + "HcalEndcapNSplitMergeClusterLinksWithoutShapes"}, {"HcalEndcapNSplitMergeClusters", "HcalEndcapNSplitMergeClusterLinks", "HcalEndcapNSplitMergeClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); diff --git a/src/detectors/FEMC/FEMC.cc b/src/detectors/FEMC/FEMC.cc index b101023ac1..80666d79da 100644 --- a/src/detectors/FEMC/FEMC.cc +++ b/src/detectors/FEMC/FEMC.cc @@ -177,7 +177,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalEndcapPTruthClusters", - {"EcalEndcapPTruthClustersWithoutShapes", "EcalEndcapPTruthClusterAssociationsWithoutShapes"}, + {"EcalEndcapPTruthClustersWithoutShapes", "EcalEndcapPTruthClusterLinksWithoutShapes"}, {"EcalEndcapPTruthClusters", "EcalEndcapPTruthClusterLinks", "EcalEndcapPTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -202,7 +202,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalEndcapPClusters", - {"EcalEndcapPClustersWithoutShapes", "EcalEndcapPClusterAssociationsWithoutShapes"}, + {"EcalEndcapPClustersWithoutShapes", "EcalEndcapPClusterLinksWithoutShapes"}, {"EcalEndcapPClusters", "EcalEndcapPClusterLinks", "EcalEndcapPClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -241,7 +241,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalEndcapPSplitMergeClusters", {"EcalEndcapPSplitMergeClustersWithoutShapes", - "EcalEndcapPSplitMergeClusterAssociationsWithoutShapes"}, + "EcalEndcapPSplitMergeClusterLinksWithoutShapes"}, {"EcalEndcapPSplitMergeClusters", "EcalEndcapPSplitMergeClusterLinks", "EcalEndcapPSplitMergeClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); diff --git a/src/detectors/FHCAL/FHCAL.cc b/src/detectors/FHCAL/FHCAL.cc index 3702f6ee3c..8b5096b10d 100644 --- a/src/detectors/FHCAL/FHCAL.cc +++ b/src/detectors/FHCAL/FHCAL.cc @@ -136,7 +136,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalEndcapPInsertTruthClusters", {"HcalEndcapPInsertTruthClustersWithoutShapes", - "HcalEndcapPInsertTruthClusterAssociationsWithoutShapes"}, + "HcalEndcapPInsertTruthClusterLinksWithoutShapes"}, {"HcalEndcapPInsertTruthClusters", "HcalEndcapPInsertTruthClusterLinks", "HcalEndcapPInsertTruthClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -161,8 +161,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalEndcapPInsertClusters", - {"HcalEndcapPInsertClustersWithoutShapes", - "HcalEndcapPInsertClusterAssociationsWithoutShapes"}, + {"HcalEndcapPInsertClustersWithoutShapes", "HcalEndcapPInsertClusterLinksWithoutShapes"}, {"HcalEndcapPInsertClusters", "HcalEndcapPInsertClusterLinks", "HcalEndcapPInsertClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, @@ -274,7 +273,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "LFHCALTruthClusters", - {"LFHCALTruthClustersWithoutShapes", "LFHCALTruthClusterAssociationsWithoutShapes"}, + {"LFHCALTruthClustersWithoutShapes", "LFHCALTruthClusterLinksWithoutShapes"}, {"LFHCALTruthClusters", "LFHCALTruthClusterLinks", "LFHCALTruthClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 4.5}, app)); @@ -297,7 +296,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( - "LFHCALClusters", {"LFHCALClustersWithoutShapes", "LFHCALClusterAssociationsWithoutShapes"}, + "LFHCALClusters", {"LFHCALClustersWithoutShapes", "LFHCALClusterLinksWithoutShapes"}, {"LFHCALClusters", "LFHCALClusterLinks", "LFHCALClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 4.5}, app)); @@ -334,7 +333,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "LFHCALSplitMergeClusters", - {"LFHCALSplitMergeClustersWithoutShapes", "LFHCALSplitMergeClusterAssociationsWithoutShapes"}, + {"LFHCALSplitMergeClustersWithoutShapes", "LFHCALSplitMergeClusterLinksWithoutShapes"}, {"LFHCALSplitMergeClusters", "LFHCALSplitMergeClusterLinks", "LFHCALSplitMergeClusterAssociations"}, {.longitudinalShowerInfoAvailable = true}, app)); diff --git a/src/detectors/LUMISPECCAL/LUMISPECCAL.cc b/src/detectors/LUMISPECCAL/LUMISPECCAL.cc index 8fd5a33eac..a6bdc9a731 100644 --- a/src/detectors/LUMISPECCAL/LUMISPECCAL.cc +++ b/src/detectors/LUMISPECCAL/LUMISPECCAL.cc @@ -100,7 +100,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "EcalLumiSpecClusters", - {"EcalLumiSpecClustersWithoutShapes", "EcalLumiSpecClusterAssociationsWithoutShapes"}, + {"EcalLumiSpecClustersWithoutShapes", "EcalLumiSpecClusterLinksWithoutShapes"}, {"EcalLumiSpecClusters", "EcalLumiSpecClusterLinks", "EcalLumiSpecClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -118,8 +118,7 @@ void InitPlugin(JApplication* app) { )); app->Add(new JOmniFactoryGeneratorT( "EcalLumiSpecTruthClusters", - {"EcalLumiSpecTruthClustersWithoutShapes", - "EcalLumiSpecTruthClusterAssociationsWithoutShapes"}, + {"EcalLumiSpecTruthClustersWithoutShapes", "EcalLumiSpecTruthClusterLinksWithoutShapes"}, {"EcalLumiSpecTruthClusters", "EcalLumiSpecTruthClusterLinks", "EcalLumiSpecTruthClusterAssociations"}, {.energyWeight = "log", .logWeightBase = 4.6}, app)); diff --git a/src/detectors/ZDC/ZDC.cc b/src/detectors/ZDC/ZDC.cc index cc4c528ffb..b7ef816b55 100644 --- a/src/detectors/ZDC/ZDC.cc +++ b/src/detectors/ZDC/ZDC.cc @@ -106,7 +106,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalFarForwardZDCTruthClusters", {"EcalFarForwardZDCTruthClustersWithoutShapes", - "EcalFarForwardZDCTruthClusterAssociationsWithoutShapes"}, + "EcalFarForwardZDCTruthClusterLinksWithoutShapes"}, {"EcalFarForwardZDCTruthClusters", "EcalFarForwardZDCTruthClusterLinks", "EcalFarForwardZDCTruthClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -131,8 +131,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "EcalFarForwardZDCClusters", - {"EcalFarForwardZDCClustersWithoutShapes", - "EcalFarForwardZDCClusterAssociationsWithoutShapes"}, + {"EcalFarForwardZDCClustersWithoutShapes", "EcalFarForwardZDCClusterLinksWithoutShapes"}, {"EcalFarForwardZDCClusters", "EcalFarForwardZDCClusterLinks", "EcalFarForwardZDCClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 6.2}, app)); @@ -244,8 +243,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalFarForwardZDCClusters", - {"HcalFarForwardZDCClustersWithoutShapes", - "HcalFarForwardZDCClusterAssociationsWithoutShapes"}, + {"HcalFarForwardZDCClustersWithoutShapes", "HcalFarForwardZDCClusterLinksWithoutShapes"}, {"HcalFarForwardZDCClusters", "HcalFarForwardZDCClusterLinks", "HcalFarForwardZDCClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, @@ -301,7 +299,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalFarForwardZDCTruthClusters", {"HcalFarForwardZDCTruthClustersWithoutShapes", - "HcalFarForwardZDCTruthClusterAssociationsWithoutShapes"}, + "HcalFarForwardZDCTruthClusterLinksWithoutShapes"}, {"HcalFarForwardZDCTruthClusters", "HcalFarForwardZDCTruthClusterLinks", "HcalFarForwardZDCTruthClusterAssociations"}, {.longitudinalShowerInfoAvailable = true, .energyWeight = "log", .logWeightBase = 3.6}, app)); @@ -328,7 +326,7 @@ void InitPlugin(JApplication* app) { app->Add(new JOmniFactoryGeneratorT( "HcalFarForwardZDCClustersBaseline", {"HcalFarForwardZDCClustersBaselineWithoutShapes", - "HcalFarForwardZDCClusterAssociationsBaselineWithoutShapes"}, + "HcalFarForwardZDCClusterLinksBaselineWithoutShapes"}, {"HcalFarForwardZDCClustersBaseline", "HcalFarForwardZDCClusterLinksBaseline", "HcalFarForwardZDCClusterAssociationsBaseline"}, {.longitudinalShowerInfoAvailable = true, diff --git a/src/factories/calorimetry/CalorimeterClusterShape_factory.h b/src/factories/calorimetry/CalorimeterClusterShape_factory.h index 2e53cb95e9..8ccfb266b1 100644 --- a/src/factories/calorimetry/CalorimeterClusterShape_factory.h +++ b/src/factories/calorimetry/CalorimeterClusterShape_factory.h @@ -22,7 +22,7 @@ class CalorimeterClusterShape_factory // input collections PodioInput m_clusters_input{this}; - PodioInput m_assocs_input{this}; + PodioInput m_links_input{this}; // output collections PodioOutput m_clusters_output{this}; @@ -52,7 +52,7 @@ class CalorimeterClusterShape_factory } void Process(int32_t /* run_number */, uint64_t /* event_number */) { - m_algo->process({m_clusters_input(), m_assocs_input()}, + m_algo->process({m_clusters_input(), m_links_input()}, {m_clusters_output().get(), m_links_output().get(), m_assocs_output().get()}); } From e115781387a5e82f5067f766fc4346df9997f5f0 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 16:33:30 -0500 Subject: [PATCH 10/16] refactor: remove unsafe LinkTruthUtils navigator accessor Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../calorimetry/CalorimeterClusterRecoCoG.cc | 6 +++--- .../calorimetry/CalorimeterClusterRecoCoG.h | 13 +++++++------ .../calorimetry/CalorimeterClusterShape.cc | 2 +- src/algorithms/calorimetry/ImagingClusterReco.cc | 6 +++--- src/algorithms/calorimetry/ImagingClusterReco.h | 4 ++-- .../fardetectors/FarDetectorTransportationPreML.cc | 11 ++++------- src/algorithms/interfaces/LinkTruthUtils.h | 2 -- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc index ce4064d334..9c1a8eba65 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc @@ -86,7 +86,7 @@ void CalorimeterClusterRecoCoG::process(const CalorimeterClusterRecoCoG::Input& // If sim hits are available, associate cluster with MCParticle if (do_assoc) { - associate(cl, mchitassociations, link_nav.navigator(), links, associations); + associate(cl, mchitassociations, link_nav, links, associations); } } } @@ -176,7 +176,7 @@ CalorimeterClusterRecoCoG::reconstruct(const edm4eic::ProtoCluster& pcl) const { void CalorimeterClusterRecoCoG::associate( const edm4eic::Cluster& cl, [[maybe_unused]] const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations, - const podio::LinkNavigator& link_nav, + const truth::EventLinkNavigator& link_nav, edm4eic::MCRecoClusterParticleLinkCollection* links, edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const { // -------------------------------------------------------------------------- @@ -202,7 +202,7 @@ void CalorimeterClusterRecoCoG::associate( for (auto clhit : cl.getHits()) { // Get linked sim hits using LinkNavigator - const auto vecAssocSimHits = link_nav.getLinked(clhit.getRawHit()); + const auto vecAssocSimHits = link_nav.linked(clhit.getRawHit()); for (const auto& [simHit, weight] : vecAssocSimHits) { eSimHitSum += simHit.getEnergy(); diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h index 29495b3549..8d73e4e0e3 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -29,6 +28,7 @@ #include #include "CalorimeterClusterRecoCoGConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" #include "algorithms/interfaces/WithPodConfig.h" static double constWeight(double /*E*/, double /*tE*/, double /*p*/, int /*type*/) { return 1.0; } @@ -81,11 +81,12 @@ class CalorimeterClusterRecoCoG : public CalorimeterClusterRecoCoGAlgorithm, private: std::optional reconstruct(const edm4eic::ProtoCluster& pcl) const; - void associate(const edm4eic::Cluster& cl, - const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations, - const podio::LinkNavigator& link_nav, - edm4eic::MCRecoClusterParticleLinkCollection* links, - edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const; + void + associate(const edm4eic::Cluster& cl, + const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations, + const truth::EventLinkNavigator& link_nav, + edm4eic::MCRecoClusterParticleLinkCollection* links, + edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const; }; } // namespace eicrecon diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.cc b/src/algorithms/calorimetry/CalorimeterClusterShape.cc index f024550a49..2e5aaa598e 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.cc @@ -230,7 +230,7 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu // if provided, copy links and associations // ---------------------------------------------------------------------- if (link_nav.enabled()) { - for (const auto& [mc_par, weight] : link_nav.navigator().getLinked(in_clust)) { + for (const auto& [mc_par, weight] : link_nav.linked(in_clust)) { truth::addWeightedRelation(out_clust, mc_par, weight, out_links, out_associations); } } // end input link loop diff --git a/src/algorithms/calorimetry/ImagingClusterReco.cc b/src/algorithms/calorimetry/ImagingClusterReco.cc index 72b6858bf0..9134a4ed54 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.cc +++ b/src/algorithms/calorimetry/ImagingClusterReco.cc @@ -73,7 +73,7 @@ void ImagingClusterReco::process(const Input& input, const Output& output) const // If sim hits are available, associate cluster with MCParticle if (do_assoc) { - associate_mc_particles(cl, mchitassociations, link_nav.navigator(), links, associations); + associate_mc_particles(cl, mchitassociations, link_nav, links, associations); } } @@ -235,7 +235,7 @@ ImagingClusterReco::fit_track(const std::vector& layers void ImagingClusterReco::associate_mc_particles( const edm4eic::Cluster& cl, [[maybe_unused]] const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations, - const podio::LinkNavigator& link_nav, + const truth::EventLinkNavigator& link_nav, edm4eic::MCRecoClusterParticleLinkCollection* links, edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const { // -------------------------------------------------------------------------- @@ -260,7 +260,7 @@ void ImagingClusterReco::associate_mc_particles( double eSimHitSum = 0.; for (auto clhit : cl.getHits()) { // Get linked sim hits using LinkNavigator - const auto vecAssocSimHits = link_nav.getLinked(clhit.getRawHit()); + const auto vecAssocSimHits = link_nav.linked(clhit.getRawHit()); for (const auto& [simHit, weight] : vecAssocSimHits) { eSimHitSum += simHit.getEnergy(); diff --git a/src/algorithms/calorimetry/ImagingClusterReco.h b/src/algorithms/calorimetry/ImagingClusterReco.h index a7736f74e6..439c7e9691 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.h +++ b/src/algorithms/calorimetry/ImagingClusterReco.h @@ -18,13 +18,13 @@ #include #include #include -#include #include #include #include #include #include "ImagingClusterRecoConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" #include "algorithms/interfaces/WithPodConfig.h" namespace eicrecon { @@ -75,7 +75,7 @@ class ImagingClusterReco : public ImagingClusterRecoAlgorithm, void associate_mc_particles( const edm4eic::Cluster& cl, const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations, - const podio::LinkNavigator& link_nav, + const truth::EventLinkNavigator& link_nav, edm4eic::MCRecoClusterParticleLinkCollection* links, edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const; }; diff --git a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc index bfc7e4332a..2dceea0b3a 100644 --- a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc +++ b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -12,6 +11,7 @@ #include "FarDetectorTransportationPreML.h" #include "algorithms/fardetectors/FarDetectorTransportationPreML.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -54,10 +54,7 @@ void FarDetectorTransportationPreML::process( target_tensor.setElementType(1); // 1 - float } - std::optional> link_nav; - if (trackLinks != nullptr && !trackLinks->empty()) { - link_nav.emplace(*trackLinks); - } + const truth::EventLinkNavigator link_nav(trackLinks); // Loop through inputTracks and simultaneously optionally associations if available // and fill the feature and target tensors @@ -73,9 +70,9 @@ void FarDetectorTransportationPreML::process( feature_tensor.addToFloatData(momentum.y); // diry feature_tensor.addToFloatData(momentum.z); // dirz - if (link_nav) { + if (link_nav.enabled()) { // Use the first linked MC particle, matching previous first-association behavior. - const auto linked_particles = link_nav->getLinked(track); + const auto linked_particles = link_nav.linked(track); if (!linked_particles.empty()) { auto MCElectronMomentum = linked_particles.front().o.getMomentum() / m_beamE; target_tensor.addToFloatData(MCElectronMomentum.x); diff --git a/src/algorithms/interfaces/LinkTruthUtils.h b/src/algorithms/interfaces/LinkTruthUtils.h index f8dbb9b37a..f72d648189 100644 --- a/src/algorithms/interfaces/LinkTruthUtils.h +++ b/src/algorithms/interfaces/LinkTruthUtils.h @@ -21,8 +21,6 @@ template class EventLinkNavigator { } bool enabled() const { return m_enabled; } - - const podio::LinkNavigator& navigator() const { return *m_nav; } template auto linked(const SrcT& src) const { using ReturnT = decltype(std::declval>().getLinked(src)); return m_enabled ? m_nav->getLinked(src) : ReturnT{}; From 271af30fea57a95444827b1434dcd425f3dac12d Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 16:40:04 -0500 Subject: [PATCH 11/16] fix: gate target tensor creation on enabled track links Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../fardetectors/FarDetectorTransportationPreML.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc index 2dceea0b3a..84ec52dbe0 100644 --- a/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc +++ b/src/algorithms/fardetectors/FarDetectorTransportationPreML.cc @@ -46,16 +46,16 @@ void FarDetectorTransportationPreML::process( feature_tensor.addToShape(6); // x,y,z,dirx,diry,dirz feature_tensor.setElementType(1); // 1 - float + const truth::EventLinkNavigator link_nav(trackLinks); + edm4eic::MutableTensor target_tensor; - if (trackLinks != nullptr) { + if (link_nav.enabled()) { target_tensor = target_tensors->create(); target_tensor.addToShape(inputTracks->size()); target_tensor.addToShape(3); // px,py,pz target_tensor.setElementType(1); // 1 - float } - const truth::EventLinkNavigator link_nav(trackLinks); - // Loop through inputTracks and simultaneously optionally associations if available // and fill the feature and target tensors for (const auto& track : *inputTracks) { From 0cbc8bd97e22d25ba5b1cb5455c37935a6c8f865 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 16:44:47 -0500 Subject: [PATCH 12/16] refactor: enforce non-null truth relation outputs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc | 6 +++++- src/algorithms/calorimetry/CalorimeterClusterShape.cc | 6 +++++- src/algorithms/calorimetry/ImagingClusterReco.cc | 6 +++++- src/algorithms/interfaces/LinkTruthUtils.h | 6 ++++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc index 9c1a8eba65..82d41088c4 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterRecoCoG.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -237,7 +238,10 @@ void CalorimeterClusterRecoCoG::associate( // calculate weight const double weight = contribution / eSimHitSum; - truth::addWeightedRelation(cl, part, static_cast(weight), links, assocs); + truth::addWeightedRelation( + cl, part, static_cast(weight), + gsl::not_null{links}, + gsl::not_null{assocs}); debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with " "weight ({})", diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.cc b/src/algorithms/calorimetry/CalorimeterClusterShape.cc index 2e5aaa598e..f8d52ec2a2 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.cc +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -231,7 +232,10 @@ void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& inpu // ---------------------------------------------------------------------- if (link_nav.enabled()) { for (const auto& [mc_par, weight] : link_nav.linked(in_clust)) { - truth::addWeightedRelation(out_clust, mc_par, weight, out_links, out_associations); + truth::addWeightedRelation( + out_clust, mc_par, weight, + gsl::not_null{out_links}, + gsl::not_null{out_associations}); } } // end input link loop } // end input cluster loop diff --git a/src/algorithms/calorimetry/ImagingClusterReco.cc b/src/algorithms/calorimetry/ImagingClusterReco.cc index 9134a4ed54..d49d724b67 100644 --- a/src/algorithms/calorimetry/ImagingClusterReco.cc +++ b/src/algorithms/calorimetry/ImagingClusterReco.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -295,7 +296,10 @@ void ImagingClusterReco::associate_mc_particles( // calculate weight const double weight = contribution / eSimHitSum; - truth::addWeightedRelation(cl, part, static_cast(weight), links, assocs); + truth::addWeightedRelation( + cl, part, static_cast(weight), + gsl::not_null{links}, + gsl::not_null{assocs}); debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with " "weight ({})", diff --git a/src/algorithms/interfaces/LinkTruthUtils.h b/src/algorithms/interfaces/LinkTruthUtils.h index f72d648189..bc353813c5 100644 --- a/src/algorithms/interfaces/LinkTruthUtils.h +++ b/src/algorithms/interfaces/LinkTruthUtils.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -32,8 +33,9 @@ template class EventLinkNavigator { }; template -inline void addWeightedRelation(const RecT& rec, const SimT& sim, float weight, LinkCollT* links, - AssocCollT* assocs) { +inline void addWeightedRelation(const RecT& rec, const SimT& sim, float weight, + gsl::not_null links, + gsl::not_null assocs) { auto link = links->create(); link.setFrom(rec); link.setTo(sim); From b37063a9490c094a1e6fdf79e365baa76fed6502 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 16:49:07 -0500 Subject: [PATCH 13/16] refactor: use event link helper in post-ML pid Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../onnx/CalorimeterParticleIDPostML.cc | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc index 4a0c3fa8ec..623b731c93 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPostML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPostML.cc @@ -3,9 +3,7 @@ #include #include -#include -#include -#include +#include #include #include #include @@ -13,6 +11,7 @@ #include #include "CalorimeterParticleIDPostML.h" +#include "algorithms/interfaces/LinkTruthUtils.h" namespace eicrecon { @@ -26,10 +25,7 @@ void CalorimeterParticleIDPostML::process(const CalorimeterParticleIDPostML::Inp const auto [in_clusters, in_links, prediction_tensors] = input; auto [out_clusters, out_links, out_assocs, out_particle_ids] = output; - std::optional> link_nav; - if (in_links != nullptr && !in_links->empty()) { - link_nav.emplace(*in_links); - } + const truth::EventLinkNavigator link_nav(in_links); if (prediction_tensors->size() != 1) { error("Expected to find a single tensor, found {}", prediction_tensors->size()); @@ -88,16 +84,12 @@ void CalorimeterParticleIDPostML::process(const CalorimeterParticleIDPostML::Inp )); // propagate associations - if (link_nav) { - for (const auto& [sim_particle, weight] : link_nav->getLinked(in_cluster)) { - auto out_link = out_links->create(); - out_link.setFrom(out_cluster); - out_link.setTo(sim_particle); - out_link.setWeight(weight); - auto out_assoc = out_assocs->create(); - out_assoc.setRec(out_cluster); - out_assoc.setSim(sim_particle); - out_assoc.setWeight(weight); + if (link_nav.enabled()) { + for (const auto& [sim_particle, weight] : link_nav.linked(in_cluster)) { + truth::addWeightedRelation( + out_cluster, sim_particle, weight, + gsl::not_null{out_links}, + gsl::not_null{out_assocs}); } } } From 1cf347943850e8e573824bb34c142719d1af0cc9 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 16:50:51 -0500 Subject: [PATCH 14/16] refactor: use checked linked accessor in pid flows Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/algorithms/pid/MatchToRICHPID.cc | 8 +++----- src/algorithms/pid_lut/PIDLookup.cc | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/algorithms/pid/MatchToRICHPID.cc b/src/algorithms/pid/MatchToRICHPID.cc index dd150f0969..2b0c675cfb 100644 --- a/src/algorithms/pid/MatchToRICHPID.cc +++ b/src/algorithms/pid/MatchToRICHPID.cc @@ -8,11 +8,8 @@ #include #include #include -#include #include #include -#include -#include #include #include #include @@ -22,6 +19,7 @@ #include #include "algorithms/pid/ConvertParticleID.h" +#include "algorithms/interfaces/LinkTruthUtils.h" #include "algorithms/pid/MatchToRICHPIDConfig.h" namespace eicrecon { @@ -32,7 +30,7 @@ void MatchToRICHPID::process(const MatchToRICHPID::Input& input, const MatchToRICHPID::Output& output) const { const auto [parts_in, links_in, drich_cherenkov_pid] = input; auto [parts_out, links_out, assocs_out, pids] = output; - podio::LinkNavigator link_nav(*links_in); + const truth::EventLinkNavigator link_nav(links_in); for (auto part_in : *parts_in) { auto part_out = part_in.clone(); @@ -44,7 +42,7 @@ void MatchToRICHPID::process(const MatchToRICHPID::Input& input, part_out.getParticleIDUsed().isAvailable() ? part_out.getParticleIDUsed().getPDG() : 0); } - for (const auto& [sim_particle, weight] : link_nav.getLinked(part_in)) { + for (const auto& [sim_particle, weight] : link_nav.linked(part_in)) { auto link_out = links_out->create(); link_out.setFrom(part_out); link_out.setTo(sim_particle); diff --git a/src/algorithms/pid_lut/PIDLookup.cc b/src/algorithms/pid_lut/PIDLookup.cc index 7af906dcf4..c4f157f912 100644 --- a/src/algorithms/pid_lut/PIDLookup.cc +++ b/src/algorithms/pid_lut/PIDLookup.cc @@ -7,9 +7,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -21,6 +18,7 @@ #include "algorithms/pid_lut/PIDLookup.h" #include "algorithms/pid_lut/PIDLookupConfig.h" +#include "algorithms/interfaces/LinkTruthUtils.h" #include "services/pid_lut/PIDLookupTableSvc.h" namespace eicrecon { @@ -58,7 +56,7 @@ void PIDLookup::init() { void PIDLookup::process(const Input& input, const Output& output) const { const auto [headers, recoparts_in, partlinks_in] = input; auto [recoparts_out, partlinks_out, partassocs_out, partids_out] = output; - podio::LinkNavigator link_nav(*partlinks_in); + const truth::EventLinkNavigator link_nav(partlinks_in); // local random generator auto seed = m_uid.getUniqueID(*headers, name()); @@ -72,7 +70,7 @@ void PIDLookup::process(const Input& input, const Output& output) const { edm4hep::MCParticle best_sim; float best_weight = std::numeric_limits::lowest(); bool has_best = false; - for (const auto& [sim_particle, weight] : link_nav.getLinked(recopart_without_pid)) { + for (const auto& [sim_particle, weight] : link_nav.linked(recopart_without_pid)) { if (!has_best || best_weight < weight) { best_sim = sim_particle; best_weight = weight; From 4525f29a86b4f650b7b1c6b491d293553ef69bd2 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Mon, 3 Aug 2026 17:05:04 -0500 Subject: [PATCH 15/16] fix: revert CalorimeterParticleIDPreML to optional input links --- .../onnx/CalorimeterParticleIDPreML.cc | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc index 5b400d4142..1832ca37a6 100644 --- a/src/algorithms/onnx/CalorimeterParticleIDPreML.cc +++ b/src/algorithms/onnx/CalorimeterParticleIDPreML.cc @@ -44,22 +44,25 @@ void CalorimeterParticleIDPreML::process(const CalorimeterParticleIDPreML::Input } for (edm4eic::Cluster cluster : *clusters) { - // FIXME: use track momentum once matching to tracks becomes available + double momentum = NAN; edm4hep::MCParticle best_sim; - float best_weight = std::numeric_limits::lowest(); - bool found_assoc = false; - for (const auto& [sim_particle, weight] : link_nav.linked(cluster)) { - if (!found_assoc || weight > best_weight) { - best_sim = sim_particle; - best_weight = weight; - found_assoc = true; + if (fill_targets) { + // FIXME: use track momentum once matching to tracks becomes available + float best_weight = std::numeric_limits::lowest(); + bool found_assoc = false; + for (const auto& [sim_particle, weight] : link_nav.linked(cluster)) { + if (!found_assoc || weight > best_weight) { + best_sim = sim_particle; + best_weight = weight; + found_assoc = true; + } } + if (!found_assoc) { + warning("Can't find association for cluster. Skipping..."); + continue; + } + momentum = edm4hep::utils::magnitude(best_sim.getMomentum()); } - if (!found_assoc) { - warning("Can't find association for cluster. Skipping..."); - continue; - } - const double momentum = edm4hep::utils::magnitude(best_sim.getMomentum()); feature_tensor.addToFloatData(momentum); feature_tensor.addToFloatData(cluster.getEnergy() / momentum); From e63651d2ece3089f3bb04ad23c204db08e53367b Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Tue, 18 Aug 2026 15:51:56 -0500 Subject: [PATCH 16/16] fix: include cluster association type in shape header Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/algorithms/calorimetry/CalorimeterClusterShape.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/algorithms/calorimetry/CalorimeterClusterShape.h b/src/algorithms/calorimetry/CalorimeterClusterShape.h index 9b28059ca7..7350d67969 100644 --- a/src/algorithms/calorimetry/CalorimeterClusterShape.h +++ b/src/algorithms/calorimetry/CalorimeterClusterShape.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include