Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-07 08:28:00

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2026 Subhadip Pal
0003 
0004 #include <edm4eic/ClusterCollection.h>
0005 #include <edm4eic/ReconstructedParticleCollection.h>
0006 #include <edm4hep/Vector3f.h>
0007 #include <edm4hep/utils/vector_utils.h>
0008 #include <cmath>
0009 #include <set>
0010 #include <tuple>
0011 
0012 #include "CaloRemnantCombiner.h"
0013 #include "algorithms/particle_flow/CaloRemnantCombinerConfig.h"
0014 
0015 namespace eicrecon {
0016 
0017 /*! Construct a candidate neutral particle via the
0018  *  following algorithm.
0019  *    1. Repeat the following the steps until every Ecal
0020  *       cluster has been used:
0021  *       a. Identify seed Ecal cluster
0022  *       b. Identify all Ecal clusters and Hcal clusters which
0023  *          lie within a radius of ecalDeltaR and hcalDeltaR
0024  *          around seed Ecal cluster respectively
0025  *       c. Combine all identified clusters into a neutral particle
0026  *          candidate
0027  *    2. Repeat the following steps until every Hcal
0028  *       cluster has been used:
0029  *       a. Identify seed Hcal cluster
0030  *       b. Identify all Hcal clusters which lie within a
0031  *          radius of hcalDeltaR around seed Hcal
0032  *          cluster
0033  *       c. Combine all identified clusters into a neutral particle
0034  *          candidate
0035  */
0036 void CaloRemnantCombiner::process(const CaloRemnantCombiner::Input& input,
0037                                   const CaloRemnantCombiner::Output& output) const {
0038 
0039   const auto [ecal_clusters, hcal_clusters] = input;
0040   auto [out_neutral_candidates]             = output;
0041 
0042   // Skip event if both cluster collections are empty
0043   if ((ecal_clusters->size() == 0) && (hcal_clusters->size() == 0)) {
0044     debug("Both ECAL and HCAL inputs are empty; skipping event.");
0045     return;
0046   }
0047 
0048   auto ecal_cmp = [ecal_clusters](std::size_t a, std::size_t b) {
0049     float ea = (*ecal_clusters)[a].getEnergy();
0050     float eb = (*ecal_clusters)[b].getEnergy();
0051     if (ea != eb) {
0052       return ea > eb; // highest energy first
0053     }
0054     return a < b; // tie-break by index
0055   };
0056 
0057   auto hcal_cmp = [hcal_clusters](std::size_t a, std::size_t b) {
0058     float ea = (*hcal_clusters)[a].getEnergy();
0059     float eb = (*hcal_clusters)[b].getEnergy();
0060     if (ea != eb) {
0061       return ea > eb; // highest energy first
0062     }
0063     return a < b; // tie-break by index
0064   };
0065 
0066   std::set<std::size_t, decltype(ecal_cmp)> remaining_ecal(ecal_cmp);
0067   std::set<std::size_t, decltype(hcal_cmp)> remaining_hcal(hcal_cmp);
0068 
0069   for (std::size_t i = 0; i < ecal_clusters->size(); ++i) {
0070     remaining_ecal.insert(i);
0071   }
0072   for (std::size_t i = 0; i < hcal_clusters->size(); ++i) {
0073     remaining_hcal.insert(i);
0074   }
0075 
0076   // Phase 1: Ecal-seeded candidates
0077   while (!remaining_ecal.empty()) {
0078 
0079     auto neutral_candidate_eh = out_neutral_candidates->create();
0080 
0081     // Seed is the first element (highest energy)
0082     std::size_t seed_ecal_index = *remaining_ecal.begin();
0083 
0084     // Gather ecal clusters within ecalDeltaR of the seed
0085     std::vector<std::size_t> ecal_to_merge = move_cluster_indices_for_merging(
0086         *ecal_clusters, remaining_ecal, seed_ecal_index, m_cfg.ecalDeltaR, *ecal_clusters);
0087 
0088     for (const auto& idx : ecal_to_merge) {
0089       neutral_candidate_eh.addToClusters((*ecal_clusters)[idx]);
0090     }
0091 
0092     // Gather hcal clusters within hcalDeltaR of the ecal seed
0093     std::vector<std::size_t> hcal_to_merge = move_cluster_indices_for_merging(
0094         *hcal_clusters, remaining_hcal, seed_ecal_index, m_cfg.hcalDeltaR, *ecal_clusters);
0095 
0096     for (const auto& idx : hcal_to_merge) {
0097       neutral_candidate_eh.addToClusters((*hcal_clusters)[idx]);
0098     }
0099 
0100   } // end of ecal-seeded loop
0101 
0102   // Phase 2: Hcal-seeded candidates (remaining hcal clusters)
0103   while (!remaining_hcal.empty()) {
0104 
0105     auto neutral_candidate_h = out_neutral_candidates->create();
0106 
0107     // Seed is the first element (highest energy)
0108     std::size_t seed_hcal_index = *remaining_hcal.begin();
0109 
0110     std::vector<std::size_t> hcal_to_merge = move_cluster_indices_for_merging(
0111         *hcal_clusters, remaining_hcal, seed_hcal_index, m_cfg.hcalDeltaR, *hcal_clusters);
0112 
0113     for (const auto& idx : hcal_to_merge) {
0114       neutral_candidate_h.addToClusters((*hcal_clusters)[idx]);
0115     }
0116 
0117   } // end of hcal-seeded loop
0118 } // end of process
0119 
0120 /*! Collects indices of clusters within `delta_r_add` of the seed cluster,
0121  *  removes them from `remaining`, and returns the collected indices.
0122  */
0123 std::vector<std::size_t> CaloRemnantCombiner::move_cluster_indices_for_merging(
0124     const edm4eic::ClusterCollection& clusters, auto& remaining, std::size_t seed_cluster_index,
0125     double delta_r_add, const edm4eic::ClusterCollection& seed) const {
0126 
0127   std::vector<std::size_t> merged_indices;
0128 
0129   // get the position of the seed cluster to calculate distance to other clusters
0130   edm4hep::Vector3f seed_pos = seed[seed_cluster_index].getPosition();
0131   float eta_seed             = edm4hep::utils::eta(seed_pos);
0132   float phi_seed             = edm4hep::utils::angleAzimuthal(seed_pos);
0133 
0134   if (delta_r_add < 0.0)
0135     delta_r_add = 0.0;
0136 
0137   // Iterate over remaining indices; collect those within delta_r_add
0138   auto it = remaining.begin();
0139   while (it != remaining.end()) {
0140     std::size_t i = *it;
0141 
0142     edm4hep::Vector3f cluster_pos = clusters[i].getPosition();
0143     float eta_cluster             = edm4hep::utils::eta(cluster_pos);
0144     float phi_cluster             = edm4hep::utils::angleAzimuthal(cluster_pos);
0145 
0146     float dphi     = std::remainder(phi_cluster - phi_seed, 2 * M_PI);
0147     float deta     = eta_cluster - eta_seed;
0148     float distance = std::sqrt(deta * deta + dphi * dphi);
0149 
0150     if (distance <= delta_r_add) {
0151       merged_indices.push_back(i);
0152       it = remaining.erase(it);
0153     } else {
0154       ++it;
0155     }
0156   }
0157   return merged_indices;
0158 }
0159 } // namespace eicrecon