Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-05 08:15:16

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 - 2024, Sylvester Joosten, Wouter Deconinck, Dmitry Romanov, Christopher Dilks, Dmitry Kalinkin
0003 
0004 #include "MatchToRICHPID.h"
0005 
0006 #include <edm4eic/TrackPoint.h>
0007 #include <edm4eic/TrackSegmentCollection.h>
0008 #include <edm4hep/Vector3f.h>
0009 #include <edm4hep/utils/vector_utils.h>
0010 #include <fmt/core.h>
0011 #include <podio/ObjectID.h>
0012 #include <podio/RelationRange.h>
0013 #include <algorithm>
0014 #include <cmath>
0015 #include <cstddef>
0016 #include <gsl/pointers>
0017 #include <map>
0018 #include <vector>
0019 
0020 #include "algorithms/pid/ConvertParticleID.h"
0021 #include "algorithms/pid/MatchToRICHPIDConfig.h"
0022 
0023 namespace eicrecon {
0024 
0025 void MatchToRICHPID::init() {}
0026 
0027 void MatchToRICHPID::process(const MatchToRICHPID::Input& input,
0028                              const MatchToRICHPID::Output& output) const {
0029   const auto [parts_in, assocs_in, drich_cherenkov_pid] = input;
0030   auto [parts_out, assocs_out, pids]                    = output;
0031 
0032   for (auto part_in : *parts_in) {
0033     auto part_out = part_in.clone();
0034 
0035     // link Cherenkov PID objects
0036     auto success = linkCherenkovPID(part_out, *drich_cherenkov_pid, *pids);
0037     if (success) {
0038       trace("Previous PDG vs. CherenkovPID PDG: {:>10} vs. {:<10}", part_in.getPDG(),
0039             part_out.getParticleIDUsed().isAvailable() ? part_out.getParticleIDUsed().getPDG() : 0);
0040     }
0041 
0042     for (auto assoc_in : *assocs_in) {
0043       if (assoc_in.getRec() == part_in) {
0044         auto assoc_out = assoc_in.clone();
0045         assoc_out.setRec(part_out);
0046         assocs_out->push_back(assoc_out);
0047       }
0048     }
0049 
0050     parts_out->push_back(part_out);
0051   }
0052 }
0053 
0054 /* link PID objects to input particle
0055      * - finds `CherenkovParticleID` object in `in_pids` associated to particle `in_part`
0056      *   by proximity matching to the associated track
0057      * - converts this `CherenkovParticleID` object's PID hypotheses to `ParticleID` objects,
0058      *   relates them to `in_part`, and adds them to the collection `out_pids` for persistency
0059      * - returns `true` iff PID objects were found and linked
0060      */
0061 bool MatchToRICHPID::linkCherenkovPID(edm4eic::MutableReconstructedParticle& in_part,
0062                                       const edm4eic::CherenkovParticleIDCollection& in_pids,
0063                                       edm4hep::ParticleIDCollection& out_pids) const {
0064 
0065   // skip this particle, if neutral
0066   if (std::abs(in_part.getCharge()) < 0.001) {
0067     return false;
0068   }
0069 
0070   // structure to store list of candidate matches
0071   struct ProxMatch {
0072     double match_dist;
0073     std::size_t pid_idx;
0074   };
0075   std::vector<ProxMatch> prox_match_list;
0076 
0077   // get input reconstructed particle momentum angles
0078   auto in_part_p   = in_part.getMomentum();
0079   auto in_part_eta = edm4hep::utils::eta(in_part_p);
0080   auto in_part_phi = edm4hep::utils::angleAzimuthal(in_part_p);
0081   trace("Input particle: (eta,phi) = ( {:>5.4}, {:>5.4} deg )", in_part_eta,
0082         in_part_phi * 180.0 / M_PI);
0083 
0084   // loop over input CherenkovParticleID objects
0085   for (std::size_t in_pid_idx = 0; in_pid_idx < in_pids.size(); in_pid_idx++) {
0086     auto in_pid = in_pids.at(in_pid_idx);
0087 
0088     // get charged particle track associated to this CherenkovParticleID object
0089     auto in_track = in_pid.getChargedParticle();
0090     if (!in_track.isAvailable()) {
0091       error("found CherenkovParticleID object with no chargedParticle");
0092       return false;
0093     }
0094     if (in_track.points_size() == 0) {
0095       error("found chargedParticle for CherenkovParticleID, but it has no TrackPoints");
0096       return false;
0097     }
0098 
0099     // get average momentum direction of the track's TrackPoints
0100     decltype(edm4eic::TrackPoint::momentum) in_track_p{0.0, 0.0, 0.0};
0101     for (const auto& in_track_point : in_track.getPoints()) {
0102       in_track_p = in_track_p + (in_track_point.momentum / in_track.points_size());
0103     }
0104     auto in_track_eta = edm4hep::utils::eta(in_track_p);
0105     auto in_track_phi = edm4hep::utils::angleAzimuthal(in_track_p);
0106 
0107     // calculate dist(eta,phi)
0108     auto match_dist = std::hypot(in_part_eta - in_track_eta, in_part_phi - in_track_phi);
0109 
0110     // check if the match is close enough: within user-specified tolerances
0111     auto match_is_close = std::abs(in_part_eta - in_track_eta) < m_cfg.etaTolerance &&
0112                           std::abs(in_part_phi - in_track_phi) < m_cfg.phiTolerance;
0113     if (match_is_close) {
0114       prox_match_list.push_back(ProxMatch{match_dist, in_pid_idx});
0115     }
0116 
0117     // logging
0118     trace("  - (eta,phi) = ( {:>5.4}, {:>5.4} deg ),  match_dist = {:<5.4}{}", in_track_eta,
0119           in_track_phi * 180.0 / M_PI, match_dist, match_is_close ? " => CLOSE!" : "");
0120 
0121   } // end loop over input CherenkovParticleID objects
0122 
0123   // check if at least one match was found
0124   if (prox_match_list.empty()) {
0125     trace("  => no matching CherenkovParticleID found for this particle");
0126     return false;
0127   }
0128 
0129   // choose the closest matching CherenkovParticleID object corresponding to this input reconstructed particle
0130   auto closest_prox_match =
0131       *std::min_element(prox_match_list.begin(), prox_match_list.end(),
0132                         [](ProxMatch a, ProxMatch b) { return a.match_dist < b.match_dist; });
0133   auto in_pid_matched = in_pids.at(closest_prox_match.pid_idx);
0134   trace("  => best match: match_dist = {:<5.4} at idx = {}", closest_prox_match.match_dist,
0135         closest_prox_match.pid_idx);
0136 
0137   // convert `CherenkovParticleID` object's hypotheses => set of `ParticleID` objects
0138   auto out_pid_index_map = ConvertParticleID::ConvertToParticleIDs(in_pid_matched, out_pids, true);
0139   if (out_pid_index_map.empty()) {
0140     error("found CherenkovParticleID object with no hypotheses");
0141     return false;
0142   }
0143 
0144   // relate matched ParticleID objects to output particle
0145   for (const auto& [out_pids_index, out_pids_id] : out_pid_index_map) {
0146     const auto& out_pid = out_pids->at(out_pids_index);
0147     if (out_pid.getObjectID().index != static_cast<int>(out_pids_id)) { // sanity check
0148       error("indexing error in `edm4eic::ParticleID` collection");
0149       return false;
0150     }
0151     in_part.addToParticleIDs(out_pid);
0152   }
0153   in_part.setParticleIDUsed(in_part.getParticleIDs().at(0)); // highest likelihood is the first
0154   in_part.setGoodnessOfPID(1); // FIXME: not used yet, aside from 0=noPID vs 1=hasPID
0155 
0156   // trace logging
0157   trace("    {:.^50}", " PID results ");
0158   trace("      Hypotheses (sorted):");
0159   for (auto hyp : in_part.getParticleIDs()) {
0160     float npe =
0161         hyp.parameters_size() > 0 ? hyp.getParameters(0) : -1; // assume NPE is the first parameter
0162     trace("{:{}}{:>6}  {:>10.8}  {:>10.8}", "", 8, hyp.getPDG(), hyp.getLikelihood(), npe);
0163   }
0164   trace("    {:'^50}", "");
0165 
0166   return true;
0167 }
0168 } // namespace eicrecon