File indexing completed on 2024-09-27 07:02:56
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/iterator/iterator_facade.hpp>
0014 #include <boost/range/adaptor/map.hpp>
0015 #include <edm4eic/CalorimeterHitCollection.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 <fmt/core.h>
0021 #include <podio/ObjectID.h>
0022 #include <podio/RelationRange.h>
0023 #include <Eigen/Core>
0024 #include <Eigen/Eigenvalues>
0025 #include <Eigen/Householder> // IWYU pragma: keep
0026 #include <cctype>
0027 #include <complex>
0028 #include <cstddef>
0029 #include <gsl/pointers>
0030 #include <iterator>
0031 #include <limits>
0032 #include <map>
0033 #include <optional>
0034 #include <vector>
0035
0036 #include "CalorimeterClusterRecoCoG.h"
0037 #include "algorithms/calorimetry/CalorimeterClusterRecoCoGConfig.h"
0038
0039 namespace eicrecon {
0040
0041 using namespace dd4hep;
0042
0043 void CalorimeterClusterRecoCoG::init() {
0044
0045 std::string ew = m_cfg.energyWeight;
0046
0047 std::transform(ew.begin(), ew.end(), ew.begin(), [](char s) { return std::tolower(s); });
0048 auto it = weightMethods.find(ew);
0049 if (it == weightMethods.end()) {
0050 error("Cannot find energy weighting method {}, choose one from [{}]", m_cfg.energyWeight, boost::algorithm::join(weightMethods | boost::adaptors::map_keys, ", "));
0051 return;
0052 }
0053 weightFunc = it->second;
0054 }
0055
0056 void CalorimeterClusterRecoCoG::process(
0057 const CalorimeterClusterRecoCoG::Input& input,
0058 const CalorimeterClusterRecoCoG::Output& output) const {
0059 #if EDM4EIC_VERSION_MAJOR >= 7
0060 const auto [proto, mchitassociations] = input;
0061 #else
0062 const auto [proto, mchits] = input;
0063 #endif
0064 auto [clusters, associations] = output;
0065
0066 for (const auto& pcl : *proto) {
0067
0068 if (pcl.hits_size() == 0) {
0069 continue;
0070 }
0071
0072 auto cl_opt = reconstruct(pcl);
0073 if (! cl_opt.has_value()) {
0074 continue;
0075 }
0076 auto cl = *std::move(cl_opt);
0077
0078 debug("{} hits: {} GeV, ({}, {}, {})", cl.getNhits(), cl.getEnergy() / dd4hep::GeV, cl.getPosition().x / dd4hep::mm, cl.getPosition().y / dd4hep::mm, cl.getPosition().z / dd4hep::mm);
0079 clusters->push_back(cl);
0080
0081
0082 #if EDM4EIC_VERSION_MAJOR >= 7
0083 if (mchitassociations->size() == 0) {
0084 debug("Provided MCRecoCalorimeterHitAssociation collection is empty. No truth associations will be performed.");
0085 continue;
0086 } else {
0087 associate(cl, mchitassociations, associations);
0088 }
0089 #else
0090 if (mchits->size() == 0) {
0091 debug("Provided SimCalorimeterHitCollection is empty. No truth association will be performed.");
0092 continue;
0093 } else {
0094 associate(cl, mchits, associations);
0095 }
0096 #endif
0097 }
0098 }
0099
0100 std::optional<edm4eic::MutableCluster> CalorimeterClusterRecoCoG::reconstruct(const edm4eic::ProtoCluster& pcl) const {
0101 edm4eic::MutableCluster cl;
0102 cl.setNhits(pcl.hits_size());
0103
0104 debug("hit size = {}", pcl.hits_size());
0105
0106
0107 if (pcl.hits_size() == 0) {
0108 return {};
0109 }
0110
0111
0112 float totalE = 0.;
0113
0114 float minHitEta = std::numeric_limits<float>::max();
0115 float maxHitEta = std::numeric_limits<float>::min();
0116 auto time = 0;
0117 auto timeError = 0;
0118 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0119 const auto& hit = pcl.getHits()[i];
0120 const auto weight = pcl.getWeights()[i];
0121 debug("hit energy = {} hit weight: {}", hit.getEnergy(), weight);
0122 auto energy = hit.getEnergy() * weight;
0123 totalE += energy;
0124 time += (hit.getTime() - time) * energy / totalE;
0125 cl.addToHits(hit);
0126 cl.addToHitContributions(energy);
0127 const float eta = edm4hep::utils::eta(hit.getPosition());
0128 if (eta < minHitEta) {
0129 minHitEta = eta;
0130 }
0131 if (eta > maxHitEta) {
0132 maxHitEta = eta;
0133 }
0134 }
0135 cl.setEnergy(totalE / m_cfg.sampFrac);
0136 cl.setEnergyError(0.);
0137 cl.setTime(time);
0138 cl.setTimeError(timeError);
0139
0140
0141 float tw = 0.;
0142 auto v = cl.getPosition();
0143
0144 double logWeightBase=m_cfg.logWeightBase;
0145 if (m_cfg.logWeightBaseCoeffs.size() != 0){
0146 double l=log(cl.getEnergy()/m_cfg.logWeightBase_Eref);
0147 logWeightBase=0;
0148 for(std::size_t i =0; i<m_cfg.logWeightBaseCoeffs.size(); i++){
0149 logWeightBase += m_cfg.logWeightBaseCoeffs[i]*pow(l,i);
0150 }
0151 }
0152
0153 for (unsigned i = 0; i < pcl.getHits().size(); ++i) {
0154 const auto& hit = pcl.getHits()[i];
0155 const auto weight = pcl.getWeights()[i];
0156
0157 float w = weightFunc(hit.getEnergy() * weight, totalE, logWeightBase, 0);
0158 tw += w;
0159 v = v + (hit.getPosition() * w);
0160 }
0161 if (tw == 0.) {
0162 warning("zero total weights encountered, you may want to adjust your weighting parameter.");
0163 return {};
0164 }
0165 cl.setPosition(v / tw);
0166 cl.setPositionError({});
0167
0168
0169 if (m_cfg.enableEtaBounds) {
0170 const bool overflow = (edm4hep::utils::eta(cl.getPosition()) > maxHitEta);
0171 const bool underflow = (edm4hep::utils::eta(cl.getPosition()) < minHitEta);
0172 if (overflow || underflow) {
0173 const double newEta = overflow ? maxHitEta : minHitEta;
0174 const double newTheta = edm4hep::utils::etaToAngle(newEta);
0175 const double newR = edm4hep::utils::magnitude(cl.getPosition());
0176 const double newPhi = edm4hep::utils::angleAzimuthal(cl.getPosition());
0177 cl.setPosition(edm4hep::utils::sphericalToVector(newR, newTheta, newPhi));
0178 debug("Bound cluster position to contributing hits due to {}", (overflow ? "overflow" : "underflow"));
0179 }
0180 }
0181
0182
0183
0184
0185
0186
0187
0188
0189
0190 float radius = 0, dispersion = 0, w_sum = 0;
0191
0192 Eigen::Matrix2f sum2_2D = Eigen::Matrix2f::Zero();
0193 Eigen::Matrix3f sum2_3D = Eigen::Matrix3f::Zero();
0194 Eigen::Vector2f sum1_2D = Eigen::Vector2f::Zero();
0195 Eigen::Vector3f sum1_3D = Eigen::Vector3f::Zero();
0196 Eigen::Vector2cf eigenValues_2D = Eigen::Vector2cf::Zero();
0197 Eigen::Vector3cf eigenValues_3D = Eigen::Vector3cf::Zero();
0198
0199 edm4hep::Vector3f axis;
0200
0201 if (cl.getNhits() > 1) {
0202 for (const auto& hit : pcl.getHits()) {
0203 float w = weightFunc(hit.getEnergy(), totalE, logWeightBase, 0);
0204
0205
0206 Eigen::Vector2f pos2D( edm4hep::utils::anglePolar( hit.getPosition() ), edm4hep::utils::angleAzimuthal( hit.getPosition() ) );
0207
0208 Eigen::Vector3f pos3D( hit.getPosition().x, hit.getPosition().y, hit.getPosition().z );
0209
0210 const auto delta = cl.getPosition() - hit.getPosition();
0211 radius += delta * delta;
0212 dispersion += delta * delta * w;
0213
0214
0215 sum2_2D += w * pos2D * pos2D.transpose();
0216 sum2_3D += w * pos3D * pos3D.transpose();
0217
0218
0219 sum1_2D += w * pos2D;
0220 sum1_3D += w * pos3D;
0221
0222 w_sum += w;
0223 }
0224
0225 radius = sqrt((1. / (cl.getNhits() - 1.)) * radius);
0226 if( w_sum > 0 ) {
0227 dispersion = sqrt( dispersion / w_sum );
0228
0229
0230 sum2_2D /= w_sum;
0231 sum2_3D /= w_sum;
0232 sum1_2D /= w_sum;
0233 sum1_3D /= w_sum;
0234
0235
0236 Eigen::Matrix2f cov2 = sum2_2D - sum1_2D * sum1_2D.transpose();
0237 Eigen::Matrix3f cov3 = sum2_3D - sum1_3D * sum1_3D.transpose();
0238
0239
0240 Eigen::EigenSolver<Eigen::Matrix2f> es_2D(cov2, false);
0241 Eigen::EigenSolver<Eigen::Matrix3f> es_3D(cov3, true);
0242
0243
0244 eigenValues_2D = es_2D.eigenvalues();
0245 eigenValues_3D = es_3D.eigenvalues();
0246
0247 auto eigenvectors= es_3D.eigenvectors();
0248 auto max_eigenvalue_it = std::max_element(
0249 eigenValues_3D.begin(),
0250 eigenValues_3D.end(),
0251 [](auto a, auto b) {
0252 return std::real(a) < std::real(b);
0253 }
0254 );
0255 auto axis_eigen = eigenvectors.col(std::distance(
0256 eigenValues_3D.begin(),
0257 max_eigenvalue_it
0258 ));
0259 axis = {
0260 axis_eigen(0,0).real(),
0261 axis_eigen(1,0).real(),
0262 axis_eigen(2,0).real(),
0263 };
0264 }
0265 }
0266
0267 cl.addToShapeParameters( radius );
0268 cl.addToShapeParameters( dispersion );
0269 cl.addToShapeParameters( eigenValues_2D[0].real() );
0270 cl.addToShapeParameters( eigenValues_2D[1].real() );
0271 cl.addToShapeParameters( eigenValues_3D[0].real() );
0272 cl.addToShapeParameters( eigenValues_3D[1].real() );
0273 cl.addToShapeParameters( eigenValues_3D[2].real() );
0274
0275
0276 double dot_product = cl.getPosition() * axis;
0277 if (dot_product < 0) {
0278 axis = -1 * axis;
0279 }
0280
0281 if (m_cfg.longitudinalShowerInfoAvailable) {
0282 cl.setIntrinsicTheta(edm4hep::utils::anglePolar(axis));
0283 cl.setIntrinsicPhi(edm4hep::utils::angleAzimuthal(axis));
0284
0285 } else {
0286 cl.setIntrinsicTheta(NAN);
0287 cl.setIntrinsicPhi(NAN);
0288 }
0289
0290 return std::move(cl);
0291 }
0292
0293 void CalorimeterClusterRecoCoG::associate(
0294 const edm4eic::Cluster& cl,
0295 #if EDM4EIC_VERSION_MAJOR >= 7
0296 const edm4eic::MCRecoCalorimeterHitAssociationCollection* mchitassociations,
0297 #else
0298 const edm4hep::SimCalorimeterHitCollection* mchits,
0299 #endif
0300 edm4eic::MCRecoClusterParticleAssociationCollection* assocs
0301 ) const {
0302
0303
0304
0305
0306
0307
0308
0309
0310
0311
0312
0313
0314
0315
0316 auto compare = [](const edm4hep::MCParticle& lhs, const edm4hep::MCParticle& rhs) {
0317 if (lhs.getObjectID().collectionID == rhs.getObjectID().collectionID) {
0318 return (lhs.getObjectID().index < rhs.getObjectID().index);
0319 } else {
0320 return (lhs.getObjectID().collectionID < rhs.getObjectID().collectionID);
0321 }
0322 };
0323
0324
0325 std::map<edm4hep::MCParticle, double, decltype(compare)> mapMCParToContrib(compare);
0326
0327
0328
0329
0330 double eSimHitSum = 0.;
0331 for (auto clhit : cl.getHits()) {
0332
0333 std::vector<edm4hep::SimCalorimeterHit> vecAssocSimHits;
0334
0335 #if EDM4EIC_VERSION_MAJOR >= 7
0336 for (const auto& hitAssoc : *mchitassociations) {
0337
0338
0339 if (clhit.getRawHit() == hitAssoc.getRawHit()) {
0340 vecAssocSimHits.push_back(hitAssoc.getSimHit());
0341 eSimHitSum += vecAssocSimHits.back().getEnergy();
0342 }
0343
0344 }
0345 #else
0346 for (const auto& mchit : *mchits) {
0347 if (mchit.getCellID() == clhit.getCellID()) {
0348 vecAssocSimHits.push_back(mchit);
0349 break;
0350 }
0351 }
0352
0353
0354
0355 if (vecAssocSimHits.empty()) {
0356 debug("No matching SimHit for hit {}", clhit.getCellID());
0357 continue;
0358 } else {
0359 eSimHitSum += vecAssocSimHits.back().getEnergy();
0360 }
0361 #endif
0362 debug("{} associated sim hits found for reco hit (cell ID = {})", vecAssocSimHits.size(), clhit.getCellID());
0363
0364
0365
0366
0367 for (const auto& simHit : vecAssocSimHits) {
0368 for (const auto& contrib : simHit.getContributions()) {
0369
0370
0371
0372 edm4hep::MCParticle primary = get_primary(contrib);
0373 mapMCParToContrib[primary] += contrib.getEnergy();
0374
0375 trace("Identified primary: id = {}, pid = {}, total energy = {}, contributed = {}",
0376 primary.getObjectID().index,
0377 primary.getPDG(),
0378 primary.getEnergy(),
0379 mapMCParToContrib[primary]
0380 );
0381 }
0382 }
0383 }
0384 debug("Found {} primaries contributing a total of {} GeV", mapMCParToContrib.size(), eSimHitSum);
0385
0386
0387
0388
0389 for (auto [part, contribution] : mapMCParToContrib) {
0390
0391 const double weight = contribution / eSimHitSum;
0392
0393
0394 auto assoc = assocs->create();
0395 assoc.setRecID(cl.getObjectID().index);
0396 assoc.setSimID(part.getObjectID().index);
0397 assoc.setWeight(weight);
0398 assoc.setRec(cl);
0399 assoc.setSim(part);
0400 debug("Associated cluster #{} to MC Particle #{} (pid = {}, status = {}, energy = {}) with weight ({})",
0401 cl.getObjectID().index,
0402 part.getObjectID().index,
0403 part.getPDG(),
0404 part.getGeneratorStatus(),
0405 part.getEnergy(),
0406 weight
0407 );
0408 }
0409 }
0410
0411 edm4hep::MCParticle CalorimeterClusterRecoCoG::get_primary(const edm4hep::CaloHitContribution& contrib) const {
0412
0413 const auto contributor = contrib.getParticle();
0414
0415
0416
0417
0418 edm4hep::MCParticle primary = contributor;
0419 while (primary.parents_size() > 0) {
0420 if (primary.getGeneratorStatus() != 0) break;
0421 primary = primary.getParents(0);
0422 }
0423 return primary;
0424 }
0425
0426 }