File indexing completed on 2026-08-26 08:25:48
0001
0002
0003
0004
0005
0006
0007
0008
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 <edm4eic/Cov3f.h>
0016 #include <edm4hep/RawCalorimeterHit.h>
0017 #include <edm4hep/SimCalorimeterHitCollection.h>
0018 #include <edm4hep/Vector3f.h>
0019 #include <edm4hep/utils/vector_utils.h>
0020 #include <podio/LinkNavigator.h>
0021 #include <podio/ObjectID.h>
0022 #include <podio/RelationRange.h>
0023 #include <podio/detail/Link.h>
0024 #include <algorithm>
0025 #include <cctype>
0026 #include <cstddef>
0027 #include <limits>
0028 #include <map>
0029 #include <optional>
0030 #include <tuple>
0031 #include <vector>
0032
0033 #include "CalorimeterClusterRecoCoG.h"
0034 #include "algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h"
0035
0036 namespace eicrecon {
0037
0038 using namespace dd4hep;
0039
0040 void CalorimeterClusterRecoCoG::init() {
0041
0042 std::string ew = m_cfg.energyWeight;
0043
0044 std::ranges::transform(ew, ew.begin(), [](char s) { return std::tolower(s); });
0045 auto it = weightMethods.find(ew);
0046 if (it == weightMethods.end()) {
0047 error("Cannot find energy weighting method {}, choose one from [{}]", m_cfg.energyWeight,
0048 boost::algorithm::join(weightMethods | boost::adaptors::map_keys, ", "));
0049 return;
0050 }
0051 weightFunc = it->second;
0052 }
0053
0054 void CalorimeterClusterRecoCoG::process(const CalorimeterClusterRecoCoG::Input& input,
0055 const CalorimeterClusterRecoCoG::Output& output) const {
0056 const auto [proto, mchitlinks, mchitassociations] = input;
0057 auto [clusters, links, associations] = output;
0058
0059
0060 const bool do_assoc = mchitlinks != nullptr && !mchitlinks->empty();
0061 if (!do_assoc) {
0062 debug("Provided MCRecoCalorimeterHitLink collection is empty. No truth associations "
0063 "will be performed.");
0064 }
0065
0066 std::optional<podio::LinkNavigator<edm4eic::MCRecoCalorimeterHitLinkCollection>> link_nav;
0067 if (do_assoc) {
0068 link_nav.emplace(*mchitlinks);
0069 }
0070
0071 for (const auto& pcl : *proto) {
0072
0073 if (pcl.hits_size() == 0) {
0074 continue;
0075 }
0076
0077 auto cl_opt = reconstruct(pcl);
0078 if (!cl_opt.has_value()) {
0079 continue;
0080 }
0081 auto cl = *std::move(cl_opt);
0082
0083 debug("{} hits: {} GeV, ({}, {}, {})", cl.getNhits(), cl.getEnergy() / dd4hep::GeV,
0084 cl.getPosition().x / dd4hep::mm, cl.getPosition().y / dd4hep::mm,
0085 cl.getPosition().z / dd4hep::mm);
0086 clusters->push_back(cl);
0087
0088
0089 if (do_assoc) {
0090 associate(cl, mchitassociations, *link_nav, links, associations);
0091 }
0092 }
0093 }
0094
0095 std::optional<edm4eic::MutableCluster>
0096 CalorimeterClusterRecoCoG::reconstruct(const edm4eic::ProtoCluster& pcl) const {
0097 edm4eic::MutableCluster cl;
0098 cl.setNhits(pcl.hits_size());
0099
0100 debug("hit size = {}", pcl.hits_size());
0101
0102
0103 if (pcl.hits_size() == 0) {
0104 return {};
0105 }
0106
0107
0108 float totalE = 0.;
0109
0110 float minHitEta = std::numeric_limits<float>::max();
0111 float maxHitEta = std::numeric_limits<float>::min();
0112 auto time = 0;
0113 auto timeError = 0;
0114 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0115 const auto& hit = pcl.getHits()[i];
0116 const auto weight = pcl.getWeights()[i];
0117 debug("hit energy = {} hit weight: {}", hit.getEnergy(), weight);
0118 auto energy = hit.getEnergy() * weight;
0119 totalE += energy;
0120 time += (hit.getTime() - time) * energy / totalE;
0121 cl.addToHits(hit);
0122 cl.addToHitContributions(energy);
0123 const float eta = edm4hep::utils::eta(hit.getPosition());
0124 minHitEta = std::min(eta, minHitEta);
0125 maxHitEta = std::max(eta, maxHitEta);
0126 }
0127 cl.setEnergy(totalE / m_cfg.sampFrac);
0128 cl.setEnergyError(0.);
0129 cl.setTime(time);
0130 cl.setTimeError(timeError);
0131
0132
0133 float tw = 0.;
0134 auto v = cl.getPosition();
0135
0136 double logWeightBase = m_cfg.logWeightBase;
0137 if (!m_cfg.logWeightBaseCoeffs.empty()) {
0138 double l = std::log(cl.getEnergy() / m_cfg.logWeightBase_Eref);
0139 logWeightBase = 0;
0140 for (std::size_t i = 0; i < m_cfg.logWeightBaseCoeffs.size(); i++) {
0141 logWeightBase += m_cfg.logWeightBaseCoeffs[i] * pow(l, i);
0142 }
0143 }
0144
0145 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0146 const auto& hit = pcl.getHits()[i];
0147 const auto weight = pcl.getWeights()[i];
0148
0149 float w = weightFunc(hit.getEnergy() * weight, totalE, logWeightBase, 0);
0150 tw += w;
0151 v = v + (hit.getPosition() * w);
0152 }
0153 if (tw == 0.) {
0154 warning("zero total weights encountered, you may want to adjust your weighting parameter.");
0155 return {};
0156 }
0157 cl.setPosition(v / tw);
0158 cl.setPositionError({});
0159
0160
0161 if (m_cfg.enableEtaBounds) {
0162 const bool overflow = (edm4hep::utils::eta(cl.getPosition()) > maxHitEta);
0163 const bool underflow = (edm4hep::utils::eta(cl.getPosition()) < minHitEta);
0164 if (overflow || underflow) {
0165 const double newEta = overflow ? maxHitEta : minHitEta;
0166 const double newTheta = edm4hep::utils::etaToAngle(newEta);
0167 const double newR = edm4hep::utils::magnitude(cl.getPosition());
0168 const double newPhi = edm4hep::utils::angleAzimuthal(cl.getPosition());
0169 cl.setPosition(edm4hep::utils::sphericalToVector(newR, newTheta, newPhi));
0170 debug("Bound cluster position to contributing hits due to {}",
0171 (overflow ? "overflow" : "underflow"));
0172 }
0173 }
0174 return cl;
0175 }
0176
0177 void CalorimeterClusterRecoCoG::associate(
0178 const edm4eic::Cluster& cl,
0179 [[maybe_unused]] const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations,
0180 const podio::LinkNavigator<edm4eic::MCRecoCalorimeterHitLinkCollection>& link_nav,
0181 edm4eic::MCRecoClusterParticleLinkCollection* links,
0182 edm4eic::MCRecoClusterParticleAssociationCollection* assocs) const {
0183
0184
0185
0186
0187
0188
0189
0190
0191
0192
0193
0194
0195
0196
0197 auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) {
0198 if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) {
0199 return (lhs.getObjectID().index < rhs.getObjectID().index);
0200 }
0201 return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID);
0202 };
0203
0204
0205 std::map<edm4hep::MCParticle, double, decltype(compare)> mapMCParToContrib(compare);
0206
0207
0208
0209
0210 double eSimHitSum = 0.;
0211 for (auto clhit : cl.getHits()) {
0212
0213
0214 const auto vecAssocSimHits = link_nav.getLinked(clhit.getRawHit());
0215
0216 for (const auto& [simHit, weight] : vecAssocSimHits) {
0217 eSimHitSum += simHit.getEnergy();
0218 }
0219
0220 debug("{} associated sim hits found for reco hit (cell ID = {})", vecAssocSimHits.size(),
0221 clhit.getCellID());
0222
0223
0224
0225
0226 for (const auto& [simHit, weight] : vecAssocSimHits) {
0227 for (const auto& contrib : simHit.getContributions()) {
0228
0229
0230
0231 edm4hep::MCParticle primary = get_primary(contrib);
0232 mapMCParToContrib[primary] += contrib.getEnergy();
0233
0234 trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}",
0235 primary.getObjectID().index, primary.getPDG(), primary.getEnergy(),
0236 mapMCParToContrib[primary]);
0237 }
0238 }
0239 }
0240 debug("Found {} primaries contributing a total of {} GeV", mapMCParToContrib.size(), eSimHitSum);
0241
0242
0243
0244
0245 for (auto [part, contribution] : mapMCParToContrib) {
0246
0247 const double weight = contribution / eSimHitSum;
0248
0249
0250 auto link = links->create();
0251 link.setWeight(weight);
0252 link.setFrom(cl);
0253 link.setTo(part);
0254
0255
0256 auto assoc = assocs->create();
0257 assoc.setWeight(weight);
0258 assoc.setRec(cl);
0259 assoc.setSim(part);
0260
0261 debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with "
0262 "weight ({})",
0263 cl.getObjectID().index, part.getObjectID().index, part.getPDG(),
0264 part.getGeneratorStatus(), part.getEnergy(), weight);
0265 }
0266 }
0267
0268 edm4hep::MCParticle
0269 CalorimeterClusterRecoCoG::get_primary(const edm4hep::CaloHitContribution& contrib) {
0270
0271 const auto contributor = contrib.getParticle();
0272
0273
0274
0275
0276 edm4hep::MCParticle primary = contributor;
0277 while (primary.parents_size() > 0) {
0278 if (primary.getGeneratorStatus() != 0) {
0279 break;
0280 }
0281 primary = primary.getParents(0);
0282 }
0283 return primary;
0284 }
0285
0286 }