Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-08 07:40:56

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2026 Derek Anderson
0003 
0004 #include <edm4eic/Track.h>
0005 #include <edm4eic/TrackPoint.h>
0006 #include <edm4hep/utils/vector_utils.h>
0007 #include <podio/ObjectID.h>
0008 #include <podio/RelationRange.h>
0009 #include <podio/detail/Link.h>
0010 #include <podio/detail/LinkCollectionImpl.h>
0011 #include <cmath>
0012 #include <cstdint>
0013 #include <limits>
0014 #include <map>
0015 #include <memory>
0016 #include <tuple>
0017 #include <utility>
0018 #include <vector>
0019 
0020 #include "TrackClusterSubtractor.h"
0021 #include "algorithms/interfaces/CompareObjectID.h"
0022 #include "algorithms/particle_flow/TrackClusterSubtractorConfig.h"
0023 
0024 namespace eicrecon {
0025 
0026 /*! Subtract energy of matched tracks via the following algorithm.
0027  *
0028  *    1. Build a map of each cluster onto a list of matched
0029  *       track projections.
0030  *    2. Flag any un-matched clusters as remnants.
0031  *    3. For each cluster, subtract the sum of momenta of
0032  *       all matched tracks scaled by the specified fraction
0033  *       from the cluster's energy.
0034  *    4. For each matched cluster:
0035  *       a. If subtracted energy is greater than zero, create
0036  *          a remnant cluster with the subtracted energy
0037  *       b. And create an expected cluster with energy equal
0038  *          to the difference between the total and the remnant.
0039  */
0040 void TrackClusterSubtractor::process(const TrackClusterSubtractor::Input& input,
0041                                      const TrackClusterSubtractor::Output& output) const {
0042 
0043   // grab inputs/outputs
0044   const auto [in_matches, in_clusters, in_projections] = input;
0045 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0046   auto [out_remnants, out_expectants, out_links, out_matches] = output;
0047 #else
0048   auto [out_remnants, out_expectants, out_matches] = output;
0049 #endif
0050 
0051   // exit if no clusters in collection
0052   if (in_clusters->size() == 0) {
0053     debug("No clusters in collection");
0054     return;
0055   }
0056 
0057   // emit debugging message if no matched tracks in collection
0058   if (in_matches->size() == 0) {
0059     debug("No matched tracks in collection.");
0060   }
0061 
0062   // --------------------------------------------------------------------------
0063   // 1. Build map of clusters onto projections
0064   // --------------------------------------------------------------------------
0065   std::map<edm4eic::Cluster, segment_vector, CompareObjectID<edm4eic::Cluster>>
0066       mapClusterToProjections;
0067   for (const auto& match : *in_matches) {
0068     for (const auto& project : *in_projections) {
0069 
0070       // pick out corresponding projection from track
0071       if (match.getTrack() != project.getTrack()) {
0072         continue;
0073       } else {
0074         mapClusterToProjections[match.getCluster()].push_back(project);
0075       }
0076 
0077     } // end projection loop
0078   } // end track-cluster match loop
0079   debug("Built map of clusters-onto-tracks, size = {}", mapClusterToProjections.size());
0080 
0081   // --------------------------------------------------------------------------
0082   // 2. Any unmatched clusters are remnants by definition
0083   // --------------------------------------------------------------------------
0084   for (const auto& cluster : *in_clusters) {
0085     if (mapClusterToProjections.count(cluster) == 0) {
0086       auto remain_cluster = cluster.clone();
0087       out_remnants->push_back(remain_cluster);
0088     }
0089   }
0090   debug("Finished copying unmatched clusters to remnants, {} remnant clusters",
0091         out_remnants->size());
0092 
0093   // --------------------------------------------------------------------------
0094   // 3. Subtract energy for tracks
0095   // --------------------------------------------------------------------------
0096   for (const auto& [cluster, projections] : mapClusterToProjections) {
0097 
0098     // do subtraction
0099     const double eTrackSum   = sum_track_energy(projections);
0100     const double eToSubtract = m_cfg.energyFractionToSubtract * eTrackSum;
0101     const double eSubtracted = cluster.getEnergy() - eToSubtract;
0102     trace("Subtracted {} GeV from cluster with {} GeV", eToSubtract, cluster.getEnergy());
0103 
0104     // if track sum is NOT greater than calorimeter energy within
0105     // tolerances, set remainder to nonzero value
0106     const bool isGreaterThan      = is_track_energy_greater_than_calo(eSubtracted);
0107     const double eSubtractedToUse = isGreaterThan ? 0. : eSubtracted;
0108 
0109     // ------------------------------------------------------------------------
0110     // 4(a). If track energy not greater than calo, create output remnant
0111     // ------------------------------------------------------------------------
0112     if (!isGreaterThan) {
0113       auto remain_cluster = cluster.clone();
0114       remain_cluster.setEnergy(eSubtractedToUse);
0115       out_remnants->push_back(remain_cluster);
0116       trace("Created remnant cluster with {} GeV", remain_cluster.getEnergy());
0117     }
0118 
0119     // ------------------------------------------------------------------------
0120     // 4(b). Create cluster with energy equal to eTotal - eRemnant and match
0121     // ------------------------------------------------------------------------
0122     auto expect_cluster = cluster.clone();
0123     expect_cluster.setEnergy(cluster.getEnergy() - eSubtractedToUse);
0124     out_expectants->push_back(expect_cluster);
0125     trace("Created expected cluster with {} GeV (originally {} GeV)", expect_cluster.getEnergy(),
0126           cluster.getEnergy());
0127 
0128     // create a track-cluster match for expected clusters
0129     for (const auto& project : projections) {
0130 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 7, 0)
0131       edm4eic::MutableTrackClusterLink link = out_links->create();
0132       link.setFrom(expect_cluster);
0133       link.setTo(project.getTrack());
0134       link.setWeight(1.0); // FIXME placeholder
0135 #endif
0136 
0137       edm4eic::MutableTrackClusterMatch match = out_matches->create();
0138       match.setCluster(expect_cluster);
0139       match.setTrack(project.getTrack());
0140       match.setWeight(1.0); // FIXME placeholder
0141       trace("Matched expected cluster {} to track {}", expect_cluster.getObjectID().index,
0142             project.getTrack().getObjectID().index);
0143     }
0144   } // end cluster-to-projections loop
0145   debug("Finished subtraction, {} remnant clusters and {} expected clusters", out_remnants->size(),
0146         out_expectants->size());
0147 
0148 } // end 'process(Input&, Output&)'
0149 
0150 /*! Sums energy of tracks projected to the surface in the calorimeter
0151  *  specified by `surfaceToUse`. Uses PDG of track to select mass
0152  *  for energy; if not available, uses mass set by `defaultPDG`.
0153  *
0154  *  @param[in] projections vector of projections matched to
0155  *                         a cluster
0156  *
0157  *  @return Sum of energy
0158  */
0159 double TrackClusterSubtractor::sum_track_energy(const segment_vector& projections) const {
0160 
0161   double energySum = 0.0;
0162   for (const auto& project : projections) {
0163 
0164     // measure momentum at specified surface
0165     double momentum     = 0.0;
0166     bool momentum_valid = false;
0167     for (const auto& point : project.getPoints()) {
0168       if (point.surface != m_cfg.surfaceToUse) {
0169         continue;
0170       } else {
0171         momentum       = edm4hep::utils::magnitude(point.momentum);
0172         momentum_valid = true;
0173         break;
0174       }
0175     }
0176     if (!momentum_valid) {
0177       continue;
0178     }
0179 
0180     // get mass based on track pdg
0181     int pdgToUse = project.getTrack().getPdg();
0182     if (pdgToUse == 0) {
0183       pdgToUse = m_cfg.defaultPDG;
0184     }
0185     const double mass = m_parSvc.particle(pdgToUse).mass;
0186 
0187     // increment sum
0188     energySum += std::sqrt((momentum * momentum) + (mass * mass));
0189   }
0190 
0191   trace("Sum of track energy = {} GeV", energySum);
0192   return energySum;
0193 
0194 } // end 'sum_track_energy(segment_vector&)'
0195 
0196 /*! Checks if the sum of tracks' energy is greater than a calorimeter cluster
0197  *  energy by checking if their difference is either:
0198  *
0199  *    1. smaller than than an epsilon (if doNSigmaCut is false), or
0200  *    2. within nSigmaMax of zero (if doNSigmaCut is true) based on
0201  *       the computed sum of tracks' variance and the calorimeter
0202  *       resolution.
0203  *
0204  *  \param[in] difference energy difference between the cluster and
0205  *                        sum of tracks
0206  *  \param[in] variance   the variance on the sume of track energy
0207  */
0208 bool TrackClusterSubtractor::is_track_energy_greater_than_calo(const double difference) const {
0209 
0210   // if < 0, automatically return true
0211   if (difference < 0) {
0212     return true;
0213   }
0214 
0215   // do appropriate comparison
0216   bool isGreaterThan = false;
0217   if (m_cfg.doNSigmaCut) {
0218 
0219     // calculate n sigma squared
0220     const double totalVariance = (m_cfg.trackResolution * m_cfg.trackResolution) +
0221                                  (m_cfg.calorimeterResolution * m_cfg.calorimeterResolution);
0222     const uint32_t nSigma2 =
0223         static_cast<uint32_t>(std::floor((difference * difference) / totalVariance));
0224     const uint32_t nSigmaMax2 = m_cfg.nSigmaMax * m_cfg.nSigmaMax;
0225 
0226     isGreaterThan = (nSigma2 < nSigmaMax2);
0227     if (isGreaterThan) {
0228       trace(
0229           "Within {} NSigma^2, track energy sum greater than calorimeter cluster: difference = {} "
0230           "GeV, nSigma^2 = {}",
0231           nSigmaMax2, difference, nSigma2);
0232     }
0233   } else {
0234     isGreaterThan = difference < std::numeric_limits<double>::epsilon();
0235     if (isGreaterThan) {
0236       trace("Track energy sum greater than calorimeter cluster: difference = {} GeV", difference);
0237     }
0238   }
0239   return isGreaterThan;
0240 
0241 } // end 'is_track_energy_greater_than_calo(double)'
0242 } // namespace eicrecon