File indexing completed on 2025-06-30 07:55:38
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 <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
0039 std::string ew = m_cfg.energyWeight;
0040
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
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
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
0106 if (pcl.hits_size() == 0) {
0107 return {};
0108 }
0109
0110
0111 float totalE = 0.;
0112
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
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
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({});
0166
0167
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
0194
0195
0196
0197
0198
0199
0200
0201
0202
0203
0204
0205
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
0214 std::map<edm4hep::MCParticle, double, decltype(compare)> mapMCParToContrib(compare);
0215
0216
0217
0218
0219 double eSimHitSum = 0.;
0220 for (auto clhit : cl.getHits()) {
0221
0222 std::vector<edm4hep::SimCalorimeterHit> vecAssocSimHits;
0223
0224 #if EDM4EIC_VERSION_MAJOR >= 7
0225 for (const auto& hitAssoc : *mchitassociations) {
0226
0227
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
0242
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
0255
0256 for (const auto& simHit : vecAssocSimHits) {
0257 for (const auto& contrib : simHit.getContributions()) {
0258
0259
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
0274
0275 for (auto [part, contribution] : mapMCParToContrib) {
0276
0277 const double weight = contribution / eSimHitSum;
0278
0279
0280 auto assoc = assocs->create();
0281 assoc.setRecID(cl.getObjectID().index);
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
0296 const auto contributor = contrib.getParticle();
0297
0298
0299
0300
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 }