Back to home page

EIC code displayed by LXR

 
 

    


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

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2023, Christopher Dilks
0003 
0004 #include "MergeParticleID.h"
0005 
0006 #include <edm4eic/CherenkovParticleIDHypothesis.h>
0007 #include <edm4eic/TrackSegmentCollection.h>
0008 #include <edm4hep/Vector2f.h>
0009 #include <fmt/core.h>
0010 #include <podio/ObjectID.h>
0011 #include <podio/RelationRange.h>
0012 #include <spdlog/common.h>
0013 #include <stddef.h>
0014 #include <stdexcept>
0015 #include <unordered_map>
0016 #include <utility>
0017 
0018 #include "algorithms/pid/MergeParticleIDConfig.h"
0019 #include "algorithms/pid/Tools.h"
0020 
0021 namespace eicrecon {
0022 
0023 void MergeParticleID::init(std::shared_ptr<spdlog::logger>& logger)
0024 {
0025   m_log = logger;
0026   m_cfg.Print(m_log, spdlog::level::debug);
0027 }
0028 
0029 void MergeParticleID::process(
0030     const MergeParticleID::Input& input,
0031     const MergeParticleID::Output& output) const {
0032 
0033   const auto [in_pid_collections_list] = input;
0034   auto [out_pids] = output;
0035 
0036   /* match input `CherenkovParticleIDCollection` elements from each list of
0037    * collections in `in_pid_collections_list`
0038    * - the matching is done by the ID of the charged particle object
0039    * - each unique charged particle object ID should correspond to a unique
0040    *   charged particle object, so any "merging" of the charged particle
0041    *   objects needs to be done upstream of this algorithm
0042    *
0043    * PROCEDURE:
0044    * - build data structure `particle_pids`, which maps a charged particle object
0045    *   ID to a list of index pairs referring to the `CherenkovParticleID`s
0046    *   associated to it
0047    *   - the 1st index of the pair is that of the input std::vector `in_pid_collections_list`
0048    *   - the 2nd index is that of the corresponding CherenkovParticleIDCollection
0049    *
0050    * EXAMPLE for merging dRICH aerogel and gas PID objects:
0051    *
0052    * - INPUT std::vector: `in_pid_collections_list`
0053    *   0. CherenkovParticleIDCollection: aerogel pids
0054    *      0. aerogel PID for charged particle A
0055    *      1. aerogel PID for charged particle B
0056    *   1. CherenkovParticleIDCollection: gas pids
0057    *      0. gas PID for charged particle A
0058    *      1. gas PID for charged particle B
0059    *      2. gas PID for charged particle C  // outside aerogel acceptance, but within gas acceptance
0060    *
0061    * - OUTPUT std::unordered_map: `particle_pids` (integer => std::vector<pair of integers>)
0062    *   - ID of charged particle A => { (0, 0), (1, 0) }
0063    *   - ID of charged particle B => { (0, 1), (1, 1) }
0064    *   - ID of charged particle C => { (1, 2) }
0065    */
0066 
0067   // fill `particle_pids`
0068   // -------------------------------------------------------------------------------
0069   std::unordered_map< decltype(podio::ObjectID::index), std::vector<std::pair<size_t,size_t>> > particle_pids;
0070   m_log->trace("{:-<70}","Build `particle_pids` indexing data structure ");
0071 
0072   // loop over list of PID collections
0073   for(size_t idx_coll = 0; idx_coll < in_pid_collections_list.size(); idx_coll++) {
0074     const auto& in_pid_collection = in_pid_collections_list.at(idx_coll);
0075     m_log->trace("idx_col={}", idx_coll);
0076 
0077     // loop over this PID collection
0078     for(size_t idx_pid = 0; idx_pid < in_pid_collection->size(); idx_pid++) {
0079       // make the index pair
0080       const auto& in_pid = in_pid_collection->at(idx_pid);
0081       auto& charged_particle_track_segment = in_pid.getChargedParticle();
0082       if(!charged_particle_track_segment.isAvailable()) {
0083         m_log->error("PID object found with no charged particle");
0084         continue;
0085       }
0086       auto id_particle = charged_particle_track_segment.getObjectID().index;
0087       auto idx_paired  = std::make_pair(idx_coll, idx_pid);
0088       m_log->trace("  idx_pid={}  id_particle={}", idx_pid, id_particle);
0089 
0090       // insert in `particle_pids`
0091       auto it = particle_pids.find(id_particle);
0092       if(it == particle_pids.end())
0093         particle_pids.insert({id_particle, {idx_paired}});
0094       else
0095         it->second.push_back(idx_paired);
0096     }
0097   }
0098 
0099   // trace logging
0100   if(m_log->level() <= spdlog::level::trace) {
0101     m_log->trace("{:-<70}","Resulting `particle_pids` ");
0102     for(auto& [id_particle, idx_paired_list] : particle_pids) {
0103       m_log->trace("id_particle={}", id_particle);
0104       for(auto& [idx_coll, idx_pid] : idx_paired_list)
0105         m_log->trace("  (idx_coll, idx_pid) = ({}, {})", idx_coll, idx_pid);
0106     }
0107   }
0108 
0109   // --------------------------------------------------------------------------------
0110 
0111 
0112   // loop over charged particles, combine weights from the associated `CherenkovParticleID` objects
0113   // and create a merged output `CherenkovParticleID` object
0114   m_log->trace("{:-<70}","Begin Merging PID Objects ");
0115   for(auto& [id_particle, idx_paired_list] : particle_pids) {
0116 
0117     // trace logging
0118     m_log->trace("Charged Particle:");
0119     m_log->trace("  id = {}", id_particle);
0120     m_log->trace("  PID Hypotheses:");
0121 
0122     // create mutable output `CherenkovParticleID` object `out_pid`
0123     auto out_pid = out_pids->create();
0124     decltype(edm4eic::CherenkovParticleIDData::npe)             out_npe             = 0.0;
0125     decltype(edm4eic::CherenkovParticleIDData::refractiveIndex) out_refractiveIndex = 0.0;
0126     decltype(edm4eic::CherenkovParticleIDData::photonEnergy)    out_photonEnergy    = 0.0;
0127 
0128     // define `pdg_2_out_hyp`: map of PDG => merged output hypothesis
0129     std::unordered_map< decltype(edm4eic::CherenkovParticleIDHypothesis::PDG), edm4eic::CherenkovParticleIDHypothesis > pdg_2_out_hyp;
0130 
0131     // merge each input `CherenkovParticleID` objects associated with this charged particle
0132     for(auto& [idx_coll, idx_pid] : idx_paired_list) {
0133       const auto& in_pid = in_pid_collections_list.at(idx_coll)->at(idx_pid);
0134 
0135       // logging
0136       m_log->trace("    Hypotheses for PID result (idx_coll, idx_pid) = ({}, {}):", idx_coll, idx_pid);
0137       Tools::PrintHypothesisTableHead(m_log,6);
0138 
0139       // merge scalar members
0140       out_npe             += in_pid.getNpe(); // sum
0141       out_refractiveIndex += in_pid.getNpe() * in_pid.getRefractiveIndex(); // NPE-weighted average
0142       out_photonEnergy    += in_pid.getNpe() * in_pid.getPhotonEnergy();    // NPE-weighted average
0143 
0144       // merge photon Cherenkov angles
0145       for(auto in_photon_vec : in_pid.getThetaPhiPhotons())
0146         out_pid.addToThetaPhiPhotons(in_photon_vec);
0147 
0148       // relate the charged particle
0149       if(!out_pid.getChargedParticle().isAvailable()) // only needs to be done once
0150         out_pid.setChargedParticle(in_pid.getChargedParticle());
0151 
0152       // merge PDG hypotheses, combining their weights and other members
0153       for(auto in_hyp : in_pid.getHypotheses()) {
0154         Tools::PrintHypothesisTableLine(m_log,in_hyp,6);
0155         auto out_hyp_it = pdg_2_out_hyp.find(in_hyp.PDG);
0156         if(out_hyp_it == pdg_2_out_hyp.end()) {
0157           edm4eic::CherenkovParticleIDHypothesis out_hyp;
0158           out_hyp.PDG    = in_hyp.PDG; // FIXME: no copy constructor?
0159           out_hyp.npe    = in_hyp.npe;
0160           out_hyp.weight = in_hyp.weight;
0161           pdg_2_out_hyp.insert({out_hyp.PDG,out_hyp});
0162         }
0163         else {
0164           auto& out_hyp = out_hyp_it->second;
0165           out_hyp.npe += in_hyp.npe;
0166           // combine hypotheses' weights
0167           switch(m_cfg.mergeMode) {
0168             case MergeParticleIDConfig::kAddWeights:
0169               out_hyp.weight += in_hyp.weight;
0170               break;
0171             case MergeParticleIDConfig::kMultiplyWeights:
0172               out_hyp.weight *= in_hyp.weight;
0173               break;
0174             default:
0175               throw std::runtime_error("unknown MergeParticleIDConfig::mergeMode setting; weights not combined");
0176           }
0177         }
0178       } // end `in_pid.getHypotheses()` loop, for this charged particle
0179 
0180     } // end `in_pid` loop, for this charged particle
0181 
0182     // finish computing averages of scalar members
0183     out_pid.setNpe(out_npe);
0184     if(out_npe > 0) {
0185       out_pid.setRefractiveIndex( out_refractiveIndex / out_npe );
0186       out_pid.setPhotonEnergy(    out_photonEnergy    / out_npe );
0187     }
0188 
0189     // append hypotheses
0190     for(auto [pdg,out_hyp] : pdg_2_out_hyp)
0191       out_pid.addToHypotheses(out_hyp);
0192 
0193     // logging: print merged hypothesis table
0194     m_log->trace("    => merged hypothesis weights:");
0195     Tools::PrintHypothesisTableHead(m_log,6);
0196     for(auto out_hyp : out_pid.getHypotheses())
0197       Tools::PrintHypothesisTableLine(m_log,out_hyp,6);
0198 
0199   } // end `particle_pids` loop over charged particles
0200 }
0201 
0202 } // namespace eicrecon