Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-06-30 07:55:38

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 - 2024 Sylvester Joosten, Chao, Chao Peng, Whitney Armstrong, Dhevan Gangadharan, Derek Anderson
0003 
0004 /*
0005  *  Reconstruct the cluster with Center of Gravity method
0006  *  Logarithmic weighting is used for mimicking energy deposit in transverse direction
0007  *
0008  *  Author: Chao Peng (ANL), 09/27/2020
0009  */
0010 
0011 #include <Evaluator/DD4hepUnits.h>
0012 #include <boost/algorithm/string/join.hpp>
0013 #include <boost/range/adaptor/map.hpp>
0014 #include <edm4eic/CalorimeterHitCollection.h>
0015 #include <edm4hep/RawCalorimeterHit.h>
0016 #include <edm4hep/SimCalorimeterHitCollection.h>
0017 #include <edm4hep/Vector3f.h>
0018 #include <edm4hep/utils/vector_utils.h>
0019 #include <fmt/core.h>
0020 #include <podio/ObjectID.h>
0021 #include <podio/RelationRange.h>
0022 #include <cctype>
0023 #include <cstddef>
0024 #include <gsl/pointers>
0025 #include <limits>
0026 #include <map>
0027 #include <optional>
0028 #include <vector>
0029 
0030 #include "CalorimeterClusterRecoCoG.h"
0031 #include "algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h"
0032 
0033 namespace eicrecon {
0034 
0035 using namespace dd4hep;
0036 
0037 void CalorimeterClusterRecoCoG::init() {
0038   // select weighting method
0039   std::string ew = m_cfg.energyWeight;
0040   // make it case-insensitive
0041   std::transform(ew.begin(), ew.end(), ew.begin(), [](char s) { return std::tolower(s); });
0042   auto it = weightMethods.find(ew);
0043   if (it == weightMethods.end()) {
0044     error("Cannot find energy weighting method {}, choose one from [{}]", m_cfg.energyWeight,
0045           boost::algorithm::join(weightMethods | boost::adaptors::map_keys, ", "));
0046     return;
0047   }
0048   weightFunc = it->second;
0049 }
0050 
0051 void CalorimeterClusterRecoCoG::process(const CalorimeterClusterRecoCoG::Input& input,
0052                                         const CalorimeterClusterRecoCoG::Output& output) const {
0053 #if EDM4EIC_VERSION_MAJOR >= 7
0054   const auto [proto, mchitassociations] = input;
0055 #else
0056   const auto [proto, mchits] = input;
0057 #endif
0058   auto [clusters, associations] = output;
0059 
0060   for (const auto& pcl : *proto) {
0061     // skip protoclusters with no hits
0062     if (pcl.hits_size() == 0) {
0063       continue;
0064     }
0065 
0066     auto cl_opt = reconstruct(pcl);
0067     if (!cl_opt.has_value()) {
0068       continue;
0069     }
0070     auto cl = *std::move(cl_opt);
0071 
0072     debug("{} hits: {} GeV, ({}, {}, {})", cl.getNhits(), cl.getEnergy() / dd4hep::GeV,
0073           cl.getPosition().x / dd4hep::mm, cl.getPosition().y / dd4hep::mm,
0074           cl.getPosition().z / dd4hep::mm);
0075     clusters->push_back(cl);
0076 
0077     // If sim hits are available, associate cluster with MCParticle
0078 #if EDM4EIC_VERSION_MAJOR >= 7
0079     if (mchitassociations->empty()) {
0080       debug("Provided MCRecoCalorimeterHitAssociation collection is empty. No truth associations "
0081             "will be performed.");
0082       continue;
0083     }
0084     associate(cl, mchitassociations, associations);
0085 
0086 #else
0087     if (mchits->size() == 0) {
0088       debug(
0089           "Provided SimCalorimeterHitCollection is empty. No truth association will be performed.");
0090       continue;
0091     } else {
0092       associate(cl, mchits, associations);
0093     }
0094 #endif
0095   }
0096 }
0097 
0098 std::optional<edm4eic::MutableCluster>
0099 CalorimeterClusterRecoCoG::reconstruct(const edm4eic::ProtoCluster& pcl) const {
0100   edm4eic::MutableCluster cl;
0101   cl.setNhits(pcl.hits_size());
0102 
0103   debug("hit size = {}", pcl.hits_size());
0104 
0105   // no hits
0106   if (pcl.hits_size() == 0) {
0107     return {};
0108   }
0109 
0110   // calculate total energy, find the cell with the maximum energy deposit
0111   float totalE = 0.;
0112   // Used to optionally constrain the cluster eta to those of the contributing hits
0113   float minHitEta = std::numeric_limits<float>::max();
0114   float maxHitEta = std::numeric_limits<float>::min();
0115   auto time       = 0;
0116   auto timeError  = 0;
0117   for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0118     const auto& hit   = pcl.getHits()[i];
0119     const auto weight = pcl.getWeights()[i];
0120     debug("hit energy = {} hit weight: {}", hit.getEnergy(), weight);
0121     auto energy = hit.getEnergy() * weight;
0122     totalE += energy;
0123     time += (hit.getTime() - time) * energy / totalE;
0124     cl.addToHits(hit);
0125     cl.addToHitContributions(energy);
0126     const float eta = edm4hep::utils::eta(hit.getPosition());
0127     if (eta < minHitEta) {
0128       minHitEta = eta;
0129     }
0130     if (eta > maxHitEta) {
0131       maxHitEta = eta;
0132     }
0133   }
0134   cl.setEnergy(totalE / m_cfg.sampFrac);
0135   cl.setEnergyError(0.);
0136   cl.setTime(time);
0137   cl.setTimeError(timeError);
0138 
0139   // center of gravity with logarithmic weighting
0140   float tw = 0.;
0141   auto v   = cl.getPosition();
0142 
0143   double logWeightBase = m_cfg.logWeightBase;
0144   if (!m_cfg.logWeightBaseCoeffs.empty()) {
0145     double l      = std::log(cl.getEnergy() / m_cfg.logWeightBase_Eref);
0146     logWeightBase = 0;
0147     for (std::size_t i = 0; i < m_cfg.logWeightBaseCoeffs.size(); i++) {
0148       logWeightBase += m_cfg.logWeightBaseCoeffs[i] * pow(l, i);
0149     }
0150   }
0151 
0152   for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0153     const auto& hit   = pcl.getHits()[i];
0154     const auto weight = pcl.getWeights()[i];
0155     //      _DBG_<<" -- weight = " << weight << "  E=" << hit.getEnergy() << " totalE=" <<totalE << " log(E/totalE)=" << std::log(hit.getEnergy()/totalE) << std::endl;
0156     float w = weightFunc(hit.getEnergy() * weight, totalE, logWeightBase, 0);
0157     tw += w;
0158     v = v + (hit.getPosition() * w);
0159   }
0160   if (tw == 0.) {
0161     warning("zero total weights encountered, you may want to adjust your weighting parameter.");
0162     return {};
0163   }
0164   cl.setPosition(v / tw);
0165   cl.setPositionError({}); // @TODO: Covariance matrix
0166 
0167   // Optionally constrain the cluster to the hit eta values
0168   if (m_cfg.enableEtaBounds) {
0169     const bool overflow  = (edm4hep::utils::eta(cl.getPosition()) > maxHitEta);
0170     const bool underflow = (edm4hep::utils::eta(cl.getPosition()) < minHitEta);
0171     if (overflow || underflow) {
0172       const double newEta   = overflow ? maxHitEta : minHitEta;
0173       const double newTheta = edm4hep::utils::etaToAngle(newEta);
0174       const double newR     = edm4hep::utils::magnitude(cl.getPosition());
0175       const double newPhi   = edm4hep::utils::angleAzimuthal(cl.getPosition());
0176       cl.setPosition(edm4hep::utils::sphericalToVector(newR, newTheta, newPhi));
0177       debug("Bound cluster position to contributing hits due to {}",
0178             (overflow ? "overflow" : "underflow"));
0179     }
0180   }
0181   return cl;
0182 }
0183 
0184 void CalorimeterClusterRecoCoG::associate(
0185     const edm4eic::Cluster& cl,
0186 #if EDM4EIC_VERSION_MAJOR >= 7
0187     const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations,
0188 #else
0189     const edm4hep::SimCalorimeterHitCollection* mchits,
0190 #endif
0191     edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const {
0192   // --------------------------------------------------------------------------
0193   // Association Logic
0194   // --------------------------------------------------------------------------
0195   /*  1. identify all sim hits associated with a given protocluster, and sum
0196    *     the energy of the sim hits.
0197    *  2. for each sim hit
0198    *     - identify parents of each contributing particles; and
0199    *     - if parent is a primary particle, add to list of contributors
0200    *       and sum the energy contributed by the parent.
0201    *  3. create an association for each contributing primary with a weight
0202    *     of contributed energy over total sim hit energy.
0203    */
0204 
0205   // lambda to compare MCParticles
0206   auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) {
0207     if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) {
0208       return (lhs.getObjectID().index < rhs.getObjectID().index);
0209     }
0210     return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID);
0211   };
0212 
0213   // bookkeeping maps for associated primaries
0214   std::map<edm4hep::MCParticle, double, decltype(compare)> mapMCParToContrib(compare);
0215 
0216   // --------------------------------------------------------------------------
0217   // 1. get associated sim hits and sum energy
0218   // --------------------------------------------------------------------------
0219   double eSimHitSum = 0.;
0220   for (auto clhit : cl.getHits()) {
0221     // vector to hold associated sim hits
0222     std::vector<edm4hep::SimCalorimeterHit> vecAssocSimHits;
0223 
0224 #if EDM4EIC_VERSION_MAJOR >= 7
0225     for (const auto& hitAssoc : *mchitassociations) {
0226       // if found corresponding raw hit, add sim hit to vector
0227       // and increment energy sum
0228       if (clhit.getRawHit() == hitAssoc.getRawHit()) {
0229         vecAssocSimHits.push_back(hitAssoc.getSimHit());
0230         eSimHitSum += vecAssocSimHits.back().getEnergy();
0231       }
0232     }
0233 #else
0234     for (const auto& mchit : *mchits) {
0235       if (mchit.getCellID() == clhit.getCellID()) {
0236         vecAssocSimHits.push_back(mchit);
0237         break;
0238       }
0239     }
0240 
0241     // if no matching cell ID found, continue
0242     // otherwise increment sum
0243     if (vecAssocSimHits.empty()) {
0244       debug("No matching SimHit for hit {}", clhit.getCellID());
0245       continue;
0246     } else {
0247       eSimHitSum += vecAssocSimHits.back().getEnergy();
0248     }
0249 #endif
0250     debug("{} associated sim hits found for reco hit (cell ID = {})", vecAssocSimHits.size(),
0251           clhit.getCellID());
0252 
0253     // ------------------------------------------------------------------------
0254     // 2. loop through associated sim hits
0255     // ------------------------------------------------------------------------
0256     for (const auto& simHit : vecAssocSimHits) {
0257       for (const auto& contrib : simHit.getContributions()) {
0258         // --------------------------------------------------------------------
0259         // grab primary responsible for contribution & increment relevant sum
0260         // --------------------------------------------------------------------
0261         edm4hep::MCParticle primary = get_primary(contrib);
0262         mapMCParToContrib[primary] += contrib.getEnergy();
0263 
0264         trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}",
0265               primary.getObjectID().index, primary.getPDG(), primary.getEnergy(),
0266               mapMCParToContrib[primary]);
0267       }
0268     }
0269   }
0270   debug("Found {} primaries contributing a total of {} GeV", mapMCParToContrib.size(), eSimHitSum);
0271 
0272   // --------------------------------------------------------------------------
0273   // 3. create association for each contributing primary
0274   // --------------------------------------------------------------------------
0275   for (auto [part, contribution] : mapMCParToContrib) {
0276     // calculate weight
0277     const double weight = contribution / eSimHitSum;
0278 
0279     // set association
0280     auto assoc = assocs->create();
0281     assoc.setRecID(cl.getObjectID().index); // if not using collection, this is always set to -1
0282     assoc.setSimID(part.getObjectID().index);
0283     assoc.setWeight(weight);
0284     assoc.setRec(cl);
0285     assoc.setSim(part);
0286     debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with "
0287           "weight ({})",
0288           cl.getObjectID().index, part.getObjectID().index, part.getPDG(),
0289           part.getGeneratorStatus(), part.getEnergy(), weight);
0290   }
0291 }
0292 
0293 edm4hep::MCParticle
0294 CalorimeterClusterRecoCoG::get_primary(const edm4hep::CaloHitContribution& contrib) {
0295   // get contributing particle
0296   const auto contributor = contrib.getParticle();
0297 
0298   // walk back through parents to find primary
0299   //   - TODO finalize primary selection. This
0300   //     can be improved!!
0301   edm4hep::MCParticle primary = contributor;
0302   while (primary.parents_size() > 0) {
0303     if (primary.getGeneratorStatus() != 0) {
0304       break;
0305     }
0306     primary = primary.getParents(0);
0307   }
0308   return primary;
0309 }
0310 
0311 } // namespace eicrecon