File indexing completed on 2026-09-15 09:31:21
0001
0002
0003
0004
0005
0006
0007
0008 #include <algorithm>
0009 #include <cmath>
0010
0011 #include <fmt/format.h>
0012
0013 #include "Gaudi/Algorithm.h"
0014 #include "GaudiKernel/RndmGenerators.h"
0015
0016 #include <k4FWCore/DataHandle.h>
0017
0018
0019 #include "edm4hep/MCParticleCollection.h"
0020 #include "edm4eic/ClusterCollection.h"
0021 #include "edm4eic/MCRecoClusterParticleAssociationCollection.h"
0022 #include "edm4eic/MCRecoParticleAssociationCollection.h"
0023 #include "edm4eic/ReconstructedParticleCollection.h"
0024 #include "edm4eic/TrackParametersCollection.h"
0025 #include "edm4hep/utils/vector_utils.h"
0026
0027 namespace Jug::Fast {
0028
0029 class MatchClusters : public Gaudi::Algorithm {
0030 private:
0031
0032 mutable k4FWCore::DataHandle<edm4hep::MCParticleCollection> m_inputMCParticles{"MCParticles", Gaudi::DataHandle::Reader, this};
0033 mutable k4FWCore::DataHandle<edm4eic::ReconstructedParticleCollection> m_inputParticles{"ReconstructedChargedParticles",
0034 Gaudi::DataHandle::Reader, this};
0035 mutable k4FWCore::DataHandle<edm4eic::MCRecoParticleAssociationCollection> m_inputParticlesAssoc{"ReconstructedChargedParticlesAssoc",
0036 Gaudi::DataHandle::Reader, this};
0037 Gaudi::Property<std::vector<std::string>> m_inputClusters{this, "inputClusters", {}, "Clusters to be aggregated"};
0038 Gaudi::Property<std::vector<std::string>> m_inputClustersAssoc{this, "inputClustersAssoc", {}, "Cluster associations to be aggregated"};
0039 std::vector<k4FWCore::DataHandle<edm4eic::ClusterCollection>*> m_inputClustersCollections;
0040 std::vector<k4FWCore::DataHandle<edm4eic::MCRecoClusterParticleAssociationCollection>*> m_inputClustersAssocCollections;
0041
0042
0043 mutable k4FWCore::DataHandle<edm4eic::ReconstructedParticleCollection> m_outputParticles{"ReconstructedParticles",
0044 Gaudi::DataHandle::Writer, this};
0045 mutable k4FWCore::DataHandle<edm4eic::MCRecoParticleAssociationCollection> m_outputParticlesAssoc{"ReconstructedParticlesAssoc",
0046 Gaudi::DataHandle::Writer, this};
0047
0048 public:
0049 MatchClusters(const std::string& name, ISvcLocator* svcLoc)
0050 : Gaudi::Algorithm(name, svcLoc) {
0051 declareProperty("inputMCParticles", m_inputMCParticles, "MCParticles");
0052 declareProperty("inputParticles", m_inputParticles, "ReconstructedChargedParticles");
0053 declareProperty("inputParticlesAssoc", m_inputParticlesAssoc, "ReconstructedChargedParticlesAssoc");
0054 declareProperty("outputParticles", m_outputParticles, "ReconstructedParticles");
0055 declareProperty("outputParticlesAssoc", m_outputParticlesAssoc, "ReconstructedParticlesAssoc");
0056 }
0057
0058 StatusCode initialize() override {
0059 if (Gaudi::Algorithm::initialize().isFailure()) {
0060 return StatusCode::FAILURE;
0061 }
0062 m_inputClustersCollections = getClusterCollections(m_inputClusters);
0063 m_inputClustersAssocCollections = getClusterAssociations(m_inputClustersAssoc);
0064 return StatusCode::SUCCESS;
0065 }
0066 StatusCode execute(const EventContext&) const override {
0067 if (msgLevel(MSG::DEBUG)) {
0068 debug() << "Processing cluster info for new event" << endmsg;
0069 }
0070
0071 const auto& mcparticles = *(m_inputMCParticles.get());
0072 const auto& inparts = *(m_inputParticles.get());
0073 const auto& inpartsassoc = *(m_inputParticlesAssoc.get());
0074 auto& outparts = *(m_outputParticles.createAndPut());
0075 auto& outpartsassoc = *(m_outputParticlesAssoc.createAndPut());
0076
0077 if (msgLevel(MSG::DEBUG)) {
0078 debug() << "Step 0/2: Getting indexed list of clusters..." << endmsg;
0079 }
0080
0081
0082 auto clusterMap = indexedClusters(m_inputClustersCollections, m_inputClustersAssocCollections);
0083
0084
0085
0086 if (msgLevel(MSG::DEBUG)) {
0087 debug() << "Step 1/2: Matching clusters to charged particles..." << endmsg;
0088 }
0089 for (const auto& inpart: inparts) {
0090 if (msgLevel(MSG::DEBUG)) {
0091 debug() << " --> Processing charged particle " << inpart.getObjectID().index
0092 << ", PDG: " << inpart.getPDG()
0093 << ", energy: " << inpart.getEnergy()
0094 << endmsg;
0095 }
0096
0097 auto outpart = inpart.clone();
0098 outparts.push_back(outpart);
0099
0100 int mcID = -1;
0101 edm4hep::MCParticle simParticle;
0102
0103
0104 for (const auto& assoc: inpartsassoc) {
0105 if (assoc.getRec() == inpart) {
0106 simParticle = assoc.getSim();
0107 mcID = simParticle.getObjectID().index;
0108 break;
0109 }
0110 }
0111
0112 if (msgLevel(MSG::VERBOSE)) {
0113 verbose() << " --> Found particle with mcID " << mcID << endmsg;
0114 }
0115
0116 if (mcID < 0) {
0117 if (msgLevel(MSG::DEBUG)) {
0118 debug() << " --> cannot match track without associated mcID" << endmsg;
0119 }
0120 continue;
0121 }
0122
0123 if (clusterMap.count(mcID)) {
0124 const auto& clus = clusterMap[mcID];
0125 if (msgLevel(MSG::DEBUG)) {
0126 debug() << " --> found matching cluster with energy: " << clus.getEnergy() << endmsg;
0127 }
0128 clusterMap.erase(mcID);
0129 }
0130
0131
0132 auto assoc = outpartsassoc.create();
0133 assoc.setWeight(1.0);
0134 assoc.setRec(outpart);
0135 assoc.setSim(simParticle);
0136 }
0137
0138
0139
0140 if (msgLevel(MSG::DEBUG)) {
0141 debug() << "Step 2/2: Creating neutrals for remaining clusters..." << endmsg;
0142 }
0143 for (const auto& [mcID, clus] : clusterMap) {
0144 if (msgLevel(MSG::DEBUG)) {
0145 debug() << " --> Processing unmatched cluster with energy: " << clus.getEnergy()
0146 << endmsg;
0147 }
0148
0149
0150 const auto& mc = mcparticles[mcID];
0151 const double mass = (!mc.getCharge()) ? mc.getMass() : 0;
0152 const int32_t pdg = (!mc.getCharge()) ? mc.getPDG() : 0;
0153 if (msgLevel(MSG::DEBUG)) {
0154 if (mc.getCharge()) {
0155 debug() << " --> associated mcparticle is not a neutral (PDG: " << mc.getPDG()
0156 << "), setting the reconstructed particle ID to 0 (unidentified)" << endmsg;
0157 }
0158 debug() << " --> found matching associated mcparticle with PDG: " << pdg << ", energy: " << mc.getEnergy()
0159 << endmsg;
0160 }
0161
0162
0163 const auto outpart = reconstruct_neutral(clus, mass, pdg);
0164 if (msgLevel(MSG::DEBUG)) {
0165 debug() << " --> Reconstructed neutral particle with PDG: " << outpart.getPDG()
0166 << ", energy: " << outpart.getEnergy()
0167 << endmsg;
0168 }
0169 outparts.push_back(outpart);
0170
0171
0172 auto assoc = outpartsassoc.create();
0173 assoc.setWeight(1.0);
0174 assoc.setRec(outpart);
0175 assoc.setSim(mcparticles[mcID]);
0176 }
0177 return StatusCode::SUCCESS;
0178 }
0179
0180 private:
0181 std::vector<k4FWCore::DataHandle<edm4eic::ClusterCollection>*> getClusterCollections(const std::vector<std::string>& cols) {
0182 std::vector<k4FWCore::DataHandle<edm4eic::ClusterCollection>*> ret;
0183 for (const auto& colname : cols) {
0184 debug() << "initializing cluster collection: " << colname << endmsg;
0185 ret.push_back(new k4FWCore::DataHandle<edm4eic::ClusterCollection>{colname, Gaudi::DataHandle::Reader, this});
0186 }
0187 return ret;
0188 }
0189
0190 std::vector<k4FWCore::DataHandle<edm4eic::MCRecoClusterParticleAssociationCollection>*> getClusterAssociations(const std::vector<std::string>& cols) {
0191 std::vector<k4FWCore::DataHandle<edm4eic::MCRecoClusterParticleAssociationCollection>*> ret;
0192 for (const auto& colname : cols) {
0193 debug() << "initializing cluster association collection: " << colname << endmsg;
0194 ret.push_back(new k4FWCore::DataHandle<edm4eic::MCRecoClusterParticleAssociationCollection>{colname, Gaudi::DataHandle::Reader, this});
0195 }
0196 return ret;
0197 }
0198
0199
0200
0201 std::map<int, edm4eic::Cluster>
0202 indexedClusters(
0203 const std::vector<k4FWCore::DataHandle<edm4eic::ClusterCollection>*>& cluster_collections,
0204 const std::vector<k4FWCore::DataHandle<edm4eic::MCRecoClusterParticleAssociationCollection>*>& associations_collections
0205 ) const {
0206 std::map<int, edm4eic::Cluster> matched = {};
0207
0208
0209 for (const auto& cluster_handle : cluster_collections) {
0210 const auto& clusters = *(cluster_handle->get());
0211
0212
0213 for (const auto& cluster : clusters) {
0214
0215 int mcID = -1;
0216
0217
0218 for (const auto& associations_handle : associations_collections) {
0219 const auto& associations = *(associations_handle->get());
0220
0221
0222 for (const auto& assoc : associations) {
0223 if (assoc.getRec() == cluster) {
0224 mcID = assoc.getSim().getObjectID().index;
0225 break;
0226 }
0227 }
0228
0229
0230 if (mcID != -1) {
0231 break;
0232 }
0233 }
0234
0235 if (msgLevel(MSG::VERBOSE)) {
0236 verbose() << " --> Found cluster with mcID " << mcID << " and energy "
0237 << cluster.getEnergy() << endmsg;
0238 }
0239
0240 if (mcID < 0) {
0241 if (msgLevel(MSG::VERBOSE)) {
0242 verbose() << " --> WARNING: no valid MC truth link found, skipping cluster..." << endmsg;
0243 }
0244 continue;
0245 }
0246
0247 const bool duplicate = matched.count(mcID);
0248 if (duplicate) {
0249 if (msgLevel(MSG::VERBOSE)) {
0250 verbose() << " --> WARNING: this is a duplicate mcID, keeping the higher energy cluster" << endmsg;
0251 }
0252 if (cluster.getEnergy() < matched[mcID].getEnergy()) {
0253 continue;
0254 }
0255 }
0256 matched[mcID] = cluster;
0257 }
0258 }
0259 return matched;
0260 }
0261
0262
0263
0264 edm4eic::MutableReconstructedParticle reconstruct_neutral(const edm4eic::Cluster& clus, const double mass,
0265 const int32_t pdg) const {
0266 const float energy = clus.getEnergy();
0267 const float p = energy < mass ? 0 : std::sqrt(energy * energy - mass * mass);
0268 const auto position = clus.getPosition();
0269 const auto momentum = p * (position / edm4hep::utils::magnitude(position));
0270
0271 edm4eic::MutableReconstructedParticle part;
0272 part.setMomentum(momentum);
0273 part.setPDG(pdg);
0274 part.setCharge(0);
0275 part.setEnergy(energy);
0276 part.setMass(mass);
0277 return part;
0278 }
0279 };
0280
0281
0282 DECLARE_COMPONENT(MatchClusters)
0283
0284 }