Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-09-27 07:02:57

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2024 Derek Anderson
0003 
0004 #include <edm4eic/CalorimeterHit.h>
0005 #include <edm4hep/Vector3f.h>
0006 #include <edm4hep/utils/vector_utils.h>
0007 #include <fmt/core.h>
0008 #include <podio/ObjectID.h>
0009 #include <podio/RelationRange.h>
0010 #include <stdint.h>
0011 #include <cmath>
0012 #include <cstddef>
0013 #include <gsl/pointers>
0014 
0015 // algorithm definition
0016 #include "TrackClusterMergeSplitter.h"
0017 #include "algorithms/calorimetry/TrackClusterMergeSplitterConfig.h"
0018 
0019 
0020 
0021 namespace eicrecon {
0022 
0023   // --------------------------------------------------------------------------
0024   //! Initialize algorithm
0025   // --------------------------------------------------------------------------
0026   void TrackClusterMergeSplitter::init(const dd4hep::Detector* detector) {
0027 
0028     // grab detector id
0029     m_idCalo = detector -> constant<int>(m_cfg.idCalo);
0030     debug("Collecting projections to detector with system id {}", m_idCalo);
0031 
0032   }  // end 'init(dd4hep::Detector*)'
0033 
0034 
0035 
0036   // --------------------------------------------------------------------------
0037   //! Process inputs
0038   // --------------------------------------------------------------------------
0039   /*! Primary algorithm call: algorithm ingests a collection
0040    *  protoclusters and a collection of track projections.
0041    *  It then decides to merge or split protoclusters according
0042    *  to the following algorithm:
0043    *    1. Identify all tracks projections pointing to the
0044    *       specified calorimeter.
0045    *    2. Match relevant track projections to protoclusters
0046    *       based on distance between projection and the energy-
0047    *       weighted barycenter of the protocluster;
0048    *    3. For each cluster-track pair:
0049    *       i.  Calculate the significance of the pair's
0050    *           E/p relative to the provided mean E/p and
0051    *           its RMS; and
0052    *       ii. If the significance is less than the
0053    *           significance specified by `minSigCut`,
0054    *           merge all clusters within `drAdd`.
0055    *    4. Create a protocluster for each merged cluster
0056    *       and copy all unused protoclusters into output.
0057    *       - If multiple tracks point to the same merged
0058    *         cluster, create a new protocluster for each
0059    *         projection with hit weighted relative to
0060    *         the track momentum.
0061    */
0062   void TrackClusterMergeSplitter::process(
0063     const TrackClusterMergeSplitter::Input& input,
0064     const TrackClusterMergeSplitter::Output& output
0065   ) const {
0066 
0067     // grab inputs/outputs
0068     const auto [in_protoclusters, in_projections] = input;
0069     auto [out_protoclusters] = output;
0070 
0071     // exit if no clusters in collection
0072     if (in_protoclusters->size() == 0) {
0073       debug("No proto-clusters in input collection.");
0074       return;
0075     }
0076 
0077     // ------------------------------------------------------------------------
0078     // 1. Identify projections to calorimeter
0079     // ------------------------------------------------------------------------
0080     VecProj vecProject;
0081     get_projections(in_projections, vecProject);
0082 
0083     // ------------------------------------------------------------------------
0084     // 2. Match relevant projections to clusters
0085     // ------------------------------------------------------------------------
0086     MapToVecProj mapProjToSplit;
0087     if (vecProject.size() == 0) {
0088       debug("No projections to match clusters to.");
0089       return;
0090     } else {
0091       match_clusters_to_tracks(in_protoclusters, vecProject, mapProjToSplit);
0092     }
0093 
0094     // ------------------------------------------------------------------------
0095     // 3. Loop over projection-cluster pairs to check if merging is needed
0096     // ------------------------------------------------------------------------
0097     SetClust setUsedClust;
0098     MapToVecClust mapClustToMerge;
0099     for (auto& [clustSeed, vecMatchProj] : mapProjToSplit) {
0100 
0101       // at this point, track-cluster matches are 1-to-1
0102       // so grab matched track
0103       auto projSeed = vecMatchProj.front();
0104 
0105       // skip if cluster is already used
0106       if (setUsedClust.count(clustSeed)) {
0107         continue;
0108       }
0109 
0110       // add cluster to list and flag as used
0111       mapClustToMerge[clustSeed].push_back( clustSeed );
0112       setUsedClust.insert( clustSeed );
0113 
0114       // grab cluster energy and projection momentum
0115       const float eClustSeed = get_cluster_energy(clustSeed);
0116       const float eProjSeed = m_cfg.avgEP * edm4hep::utils::magnitude(projSeed.momentum);
0117 
0118       // ----------------------------------------------------------------------
0119       // 3.i. Calculate significance
0120       // ----------------------------------------------------------------------
0121       const float sigSeed = (eClustSeed - eProjSeed) / m_cfg.sigEP;
0122       trace("Seed energy = {}, expected energy = {}, significance = {}", eClustSeed, eProjSeed, sigSeed);
0123 
0124       // ----------------------------------------------------------------------
0125       // 3.ii. If significance is above threshold, do nothing.
0126       //       Otherwise identify clusters to merge.
0127       // ----------------------------------------------------------------------
0128       if (sigSeed > m_cfg.minSigCut) {
0129         continue;
0130       }
0131 
0132       // get eta, phi of seed
0133       const auto posSeed = get_cluster_position(clustSeed);
0134       const float etaSeed = edm4hep::utils::eta(posSeed);
0135       const float phiSeed = edm4hep::utils::angleAzimuthal(posSeed);
0136 
0137       // loop over other clusters
0138       float eClustSum = eClustSeed;
0139       float sigSum = sigSeed;
0140       for (auto in_cluster : *in_protoclusters) {
0141 
0142         // ignore used clusters
0143         if (setUsedClust.count(in_cluster)) {
0144           continue;
0145         }
0146 
0147         // get eta, phi of cluster
0148         const auto posClust = get_cluster_position(in_cluster);
0149         const float etaClust = edm4hep::utils::eta(posClust);
0150         const float phiClust = edm4hep::utils::angleAzimuthal(posClust);
0151 
0152         // get distance to seed
0153         const float drToSeed = std::hypot(
0154           etaSeed - etaClust,
0155           std::remainder(phiSeed - phiClust, 2. * M_PI)
0156         );
0157 
0158         // --------------------------------------------------------------------
0159         // If inside merging-window, add to list of clusters to merge
0160         // --------------------------------------------------------------------
0161         if (drToSeed > m_cfg.drAdd) {
0162           continue;
0163         } else {
0164           mapClustToMerge[clustSeed].push_back( in_cluster );
0165           setUsedClust.insert( in_cluster );
0166         }
0167 
0168         // --------------------------------------------------------------------
0169         // if picked up cluster w/ matched track, add projection to list
0170         // --------------------------------------------------------------------
0171         if (mapProjToSplit.count(in_cluster)) {
0172           vecMatchProj.insert(
0173             vecMatchProj.end(),
0174             mapProjToSplit[in_cluster].begin(),
0175             mapProjToSplit[in_cluster].end()
0176           );
0177         }
0178 
0179         // increment sums and output debugging
0180         eClustSum += get_cluster_energy(in_cluster);
0181         sigSum = (eClustSum - eProjSeed) / m_cfg.sigEP;
0182         trace(
0183           "{} clusters to merge: current sum = {}, current significance = {}, {} track(s) pointing to merged cluster",
0184           mapClustToMerge[clustSeed].size(),
0185           eClustSum,
0186           sigSum,
0187           vecMatchProj.size()
0188         );
0189       }  // end cluster loop
0190     }  // end matched cluster-projection loop
0191 
0192     // ------------------------------------------------------------------------
0193     // 4. Create an output protocluster for each merged cluster and for
0194     //    each track pointing to merged cluster
0195     // ------------------------------------------------------------------------
0196     for (auto& [clustSeed, vecClustToMerge] : mapClustToMerge) {
0197       merge_and_split_clusters(
0198         vecClustToMerge,
0199         mapProjToSplit[clustSeed],
0200         out_protoclusters
0201       );
0202     }  // end clusters to merge loop
0203 
0204     // ------------------------------------------------------------------------
0205     // copy unused clusters to output
0206     // ------------------------------------------------------------------------
0207     for (auto in_cluster : *in_protoclusters) {
0208 
0209       // ignore used clusters
0210       if (setUsedClust.count(in_cluster)) {
0211         continue;
0212       }
0213 
0214       // copy cluster and add to output collection
0215       edm4eic::MutableProtoCluster out_cluster = in_cluster.clone();
0216       out_protoclusters->push_back(out_cluster);
0217       trace("Copied input cluster {} onto output cluster {}",
0218         in_cluster.getObjectID().index,
0219         out_cluster.getObjectID().index
0220       );
0221 
0222     }  // end cluster loop
0223 
0224   }  // end 'process(Input&, Output&)'
0225 
0226 
0227 
0228   // --------------------------------------------------------------------------
0229   //! Collect projections pointing to calorimeter
0230   // --------------------------------------------------------------------------
0231   void TrackClusterMergeSplitter::get_projections(
0232     const edm4eic::TrackSegmentCollection* projections,
0233     VecProj& relevant_projects
0234   ) const {
0235 
0236     // return if projections are empty
0237     if (projections->size() == 0) {
0238       debug("No projections in input collection.");
0239       return;
0240     }
0241 
0242     // collect projections
0243     for (auto project : *projections) {
0244       for (auto point : project.getPoints()) {
0245         if (
0246           (point.system  == m_idCalo) &&
0247           (point.surface == 1)
0248         ) {
0249           relevant_projects.push_back(point);
0250         }
0251       }  // end point loop
0252     }  // end projection loop
0253     debug("Collected relevant projections: {} to process", relevant_projects.size());
0254 
0255   }  // end 'get_projections(edm4eic::CalorimeterHit&, edm4eic::TrackSegmentCollection&, VecTrkPoint&)'
0256 
0257 
0258 
0259   // --------------------------------------------------------------------------
0260   //! Match clusters to track projections
0261   // --------------------------------------------------------------------------
0262   /*! FIXME remove this once cluster-track matching has been centralized
0263    */
0264   void TrackClusterMergeSplitter::match_clusters_to_tracks(
0265     const edm4eic::ProtoClusterCollection* clusters,
0266     const VecProj& projections,
0267     MapToVecProj& matches
0268   ) const {
0269 
0270 
0271     // loop over relevant projections
0272     for (uint32_t iProject = 0; iProject < projections.size(); ++iProject) {
0273 
0274       // grab projection
0275       auto project = projections[iProject];
0276 
0277       // get eta, phi of projection
0278       const float etaProj = edm4hep::utils::eta(project.position);
0279       const float phiProj = edm4hep::utils::angleAzimuthal(project.position);
0280 
0281       // to store matched cluster
0282       edm4eic::ProtoCluster match;
0283 
0284       // find closest cluster
0285       bool foundMatch = false;
0286       float dMatch = m_cfg.drAdd;
0287       for (auto cluster : *clusters) {
0288 
0289         // get eta, phi of cluster
0290         const auto  posClust = get_cluster_position(cluster);
0291         const float etaClust = edm4hep::utils::eta(posClust);
0292         const float phiClust = edm4hep::utils::angleAzimuthal(posClust);
0293 
0294         // calculate distance to centroid
0295         const float dist = std::hypot(
0296           etaProj - etaClust,
0297           std::remainder(phiProj - phiClust, 2. * M_PI)
0298         );
0299 
0300         // if closer, set match to current projection
0301         if (dist <= dMatch) {
0302           foundMatch = true;
0303           dMatch = dist;
0304           match = cluster;
0305         }
0306       }  // end cluster loop
0307 
0308       // record match if found
0309       if (foundMatch) {
0310         matches[match].push_back(project);
0311         trace("Matched cluster to track projection: eta-phi distance = {}", dMatch);
0312       }
0313     }  // end cluster loop
0314     debug("Finished matching clusters to track projections: {} matches", matches.size());
0315 
0316   }  // end 'match_clusters_to_tracks(edm4eic::ClusterCollection*, VecTrkPoint&, MapToVecProj&)'
0317 
0318 
0319 
0320   // --------------------------------------------------------------------------
0321   //! Merge identified clusters and split if needed
0322   // --------------------------------------------------------------------------
0323   /*! If multiple tracks are pointing to merged cluster, a new protocluster
0324    *  is created for each track w/ hits weighted by its distance to the track
0325    *  and the track's momentum.
0326    */
0327   void TrackClusterMergeSplitter::merge_and_split_clusters(
0328     const VecClust& to_merge,
0329     const VecProj& to_split,
0330     edm4eic::ProtoClusterCollection* out_protoclusters
0331   ) const {
0332 
0333     // if only 1 matched track, no need to split
0334     if (to_split.size() == 1) {
0335       edm4eic::MutableProtoCluster new_clust = out_protoclusters->create();
0336       for (const auto& old_clust : to_merge) {
0337         for (const auto& hit : old_clust.getHits()) {
0338           new_clust.addToHits( hit );
0339           new_clust.addToWeights( 1. );
0340         }
0341         trace("Merged input cluster {} into output cluster {}", old_clust.getObjectID().index, new_clust.getObjectID().index);
0342       }
0343       return;
0344     }
0345 
0346     // otherwise split merged cluster for each matched track
0347     std::vector<edm4eic::MutableProtoCluster> new_clusters;
0348     for (const auto& proj : to_split) {
0349       new_clusters.push_back( out_protoclusters->create() );
0350     }
0351     trace("Splitting merged cluster across {} tracks", to_split.size());
0352 
0353     // loop over all hits from all clusters to merge
0354     std::vector<float> weights( to_split.size(), 1. );
0355     for (const auto& old_clust : to_merge) {
0356       for (const auto& hit : old_clust.getHits()) {
0357 
0358         // calculate hit's weight for each track
0359         for (std::size_t iProj = 0; const auto& proj : to_split) {
0360 
0361           // get track eta, phi
0362           const float etaProj = edm4hep::utils::eta(proj.position);
0363           const float phiProj = edm4hep::utils::angleAzimuthal(proj.position);
0364 
0365           // get hit eta, phi
0366           const float etaHit = edm4hep::utils::eta(hit.getPosition());
0367           const float phiHit = edm4hep::utils::angleAzimuthal(hit.getPosition());
0368 
0369           // get track momentum, distance to hit
0370           const float mom  = edm4hep::utils::magnitude(proj.momentum);
0371           const float dist = std::hypot(
0372             etaHit - etaProj,
0373             std::remainder(phiHit - phiProj, 2. * M_PI)
0374           );
0375 
0376           // set weight
0377           weights[iProj] = std::exp(-1. * dist / m_cfg.transverseEnergyProfileScale) * mom;
0378           ++iProj;
0379         }
0380 
0381         // normalize weights
0382         float wTotal = 0.;
0383         for (const float weight : weights) {
0384           wTotal += weight;
0385         }
0386         for (float& weight : weights) {
0387           weight /= wTotal;
0388         }
0389 
0390         // add hit to each split merged cluster w/ relevant weight
0391         for (std::size_t iProj = 0; auto& new_clust : new_clusters) {
0392           new_clust.addToHits( hit );
0393           new_clust.addToWeights( weights[iProj] );
0394         }
0395 
0396       }  // end hits to merge loop
0397     }  // end clusters to merge loop
0398 
0399   }  // end 'merge_and_split_clusters(VecClust&, VecProj&, edm4eic::MutableCluster&)'
0400 
0401 
0402 
0403   // --------------------------------------------------------------------------
0404   //! Grab current energy of protocluster
0405   // --------------------------------------------------------------------------
0406   float TrackClusterMergeSplitter::get_cluster_energy(const edm4eic::ProtoCluster& clust) const {
0407 
0408     float eClust = 0.;
0409     for (auto hit : clust.getHits()) {
0410       eClust += hit.getEnergy();
0411     }
0412     return eClust / m_cfg.sampFrac;
0413 
0414   }  // end 'get_cluster_energy(edm4eic::ProtoCluster&)'
0415 
0416 
0417 
0418   // --------------------------------------------------------------------------
0419   //! Get current center of protocluster
0420   // --------------------------------------------------------------------------
0421   edm4hep::Vector3f TrackClusterMergeSplitter::get_cluster_position(const edm4eic::ProtoCluster& clust) const {
0422 
0423     // grab total energy
0424     const float eClust = get_cluster_energy(clust) * m_cfg.sampFrac;
0425 
0426     // calculate energy-weighted center
0427     float wTotal = 0.;
0428     edm4hep::Vector3f position(0., 0., 0.);
0429     for (auto hit : clust.getHits()) {
0430 
0431       // calculate weight
0432       float weight = hit.getEnergy() / eClust;
0433       wTotal += weight;
0434 
0435       // update cluster position
0436       position = position + (hit.getPosition() * weight);
0437     }
0438 
0439     float norm = 1.;
0440     if (wTotal == 0.) {
0441       warning("Total weight of 0 in position calculation!");
0442     } else {
0443       norm = wTotal;
0444     }
0445     return position / norm;
0446 
0447   }  // end 'get_cluster_position(edm4eic::ProtoCluster&)'
0448 
0449 }  // end eicrecon namespace