File indexing completed on 2026-09-07 08:27:54
0001
0002
0003
0004 #include "CalorimeterClusterShape.h"
0005
0006 #include <boost/algorithm/string/join.hpp>
0007 #include <boost/range/adaptor/map.hpp>
0008 #include <edm4eic/CalorimeterHitCollection.h>
0009 #include <edm4eic/EDM4eicVersion.h>
0010 #include <edm4hep/MCParticle.h>
0011 #include <edm4hep/Vector3f.h>
0012 #include <edm4hep/utils/vector_utils.h>
0013 #include <podio/LinkNavigator.h>
0014 #include <podio/RelationRange.h>
0015 #include <podio/detail/LinkCollectionImpl.h>
0016 #include <Eigen/Core>
0017 #include <Eigen/Eigenvalues>
0018 #include <Eigen/Householder> // IWYU pragma: keep
0019 #include <Eigen/Jacobi>
0020 #include <algorithm>
0021 #include <cctype>
0022 #include <cmath>
0023 #include <cstddef>
0024 #include <gsl/pointers>
0025 #include <tuple>
0026 #include <utility>
0027 #include <vector>
0028
0029 #include "algorithms/calorimetry/CalorimeterClusterShapeConfig.h"
0030 #include "algorithms/interfaces/LinkTruthUtils.h"
0031
0032 namespace eicrecon {
0033
0034 void CalorimeterClusterShape::init() {
0035
0036
0037 std::string ew = m_cfg.energyWeight;
0038
0039
0040 std::ranges::transform(ew, ew.begin(), [](char s) { return std::tolower(s); });
0041 auto it = m_weightMethods.find(ew);
0042 if (it == m_weightMethods.end()) {
0043 error("Cannot find energy weighting method {}, choose one from [{}]", m_cfg.energyWeight,
0044 boost::algorithm::join(m_weightMethods | boost::adaptors::map_keys, ", "));
0045 } else {
0046 m_weightFunc = it->second;
0047 }
0048
0049 }
0050
0051
0052
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062 void CalorimeterClusterShape::process(const CalorimeterClusterShape::Input& input,
0063 const CalorimeterClusterShape::Output& output) const {
0064
0065
0066 const auto [in_clusters, in_links] = input;
0067 auto [out_clusters, out_links, out_associations] = output;
0068
0069 const truth::EventLinkNavigator<edm4eic::MCRecoClusterParticleLinkCollection> link_nav(in_links);
0070
0071
0072 if (in_clusters->empty()) {
0073 debug("No clusters in input collection.");
0074 return;
0075 }
0076
0077
0078 for (const auto& in_clust : *in_clusters) {
0079
0080
0081 edm4eic::MutableCluster out_clust = in_clust.clone();
0082
0083
0084 double logWeightBase = m_cfg.logWeightBase;
0085 if (!m_cfg.logWeightBaseCoeffs.empty()) {
0086 double l = std::log(out_clust.getEnergy() / m_cfg.logWeightBase_Eref);
0087 logWeightBase = 0;
0088 for (std::size_t i = 0; i < m_cfg.logWeightBaseCoeffs.size(); i++) {
0089 logWeightBase += m_cfg.logWeightBaseCoeffs[i] * pow(l, i);
0090 }
0091 }
0092
0093
0094
0095
0096 {
0097
0098
0099 double radius = 0;
0100 double dispersion = 0;
0101 double w_sum = 0;
0102
0103 Eigen::Matrix2d sum2_2D = Eigen::Matrix2d::Zero();
0104 Eigen::Matrix3d sum2_3D = Eigen::Matrix3d::Zero();
0105 Eigen::Vector2d sum1_2D = Eigen::Vector2d::Zero();
0106 Eigen::Vector3d sum1_3D = Eigen::Vector3d::Zero();
0107 Eigen::Vector2d eigenValues_2D = Eigen::Vector2d::Zero();
0108 Eigen::Vector3d eigenValues_3D = Eigen::Vector3d::Zero();
0109
0110
0111 edm4hep::Vector3f axis;
0112 if (out_clust.getNhits() > 1) {
0113 for (const auto& hit : out_clust.getHits()) {
0114
0115
0116 const double eTotal = out_clust.getEnergy() * m_cfg.sampFrac;
0117 const double w = m_weightFunc(hit.getEnergy(), eTotal, logWeightBase, 0);
0118
0119
0120 Eigen::Vector2d pos2D(edm4hep::utils::anglePolar(hit.getPosition()),
0121 edm4hep::utils::angleAzimuthal(hit.getPosition()));
0122
0123 Eigen::Vector3d pos3D(hit.getPosition().x, hit.getPosition().y, hit.getPosition().z);
0124 const auto delta = out_clust.getPosition() - hit.getPosition();
0125 radius += delta * delta;
0126 dispersion += delta * delta * w;
0127
0128
0129 sum2_2D += w * pos2D * pos2D.transpose();
0130 sum2_3D += w * pos3D * pos3D.transpose();
0131
0132
0133 sum1_2D += w * pos2D;
0134 sum1_3D += w * pos3D;
0135
0136 w_sum += w;
0137 }
0138
0139 radius = sqrt((1. / (out_clust.getNhits() - 1.)) * radius);
0140 if (w_sum > 0) {
0141 dispersion = sqrt(dispersion / w_sum);
0142
0143
0144 sum2_2D /= w_sum;
0145 sum2_3D /= w_sum;
0146 sum1_2D /= w_sum;
0147 sum1_3D /= w_sum;
0148
0149
0150 Eigen::Matrix2d cov2 = sum2_2D - sum1_2D * sum1_2D.transpose();
0151 Eigen::Matrix3d cov3 = sum2_3D - sum1_3D * sum1_3D.transpose();
0152
0153
0154
0155
0156 Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> es_2D(cov2);
0157 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es_3D(cov3);
0158
0159
0160
0161 auto ev2 = es_2D.eigenvalues();
0162 eigenValues_2D[0] = ev2[1];
0163 eigenValues_2D[1] = ev2[0];
0164
0165 auto ev3 = es_3D.eigenvalues();
0166 eigenValues_3D[0] = ev3[2];
0167 eigenValues_3D[1] = ev3[1];
0168 eigenValues_3D[2] = ev3[0];
0169
0170
0171 auto axis_eigen = es_3D.eigenvectors().col(2);
0172 axis = {
0173 static_cast<float>(axis_eigen(0)),
0174 static_cast<float>(axis_eigen(1)),
0175 static_cast<float>(axis_eigen(2)),
0176 };
0177 }
0178 }
0179
0180
0181
0182
0183 out_clust.addToShapeParameters(radius);
0184 out_clust.addToShapeParameters(dispersion);
0185 out_clust.addToShapeParameters(eigenValues_2D[0]);
0186 out_clust.addToShapeParameters(eigenValues_2D[1]);
0187 out_clust.addToShapeParameters(eigenValues_3D[0]);
0188 out_clust.addToShapeParameters(eigenValues_3D[1]);
0189 out_clust.addToShapeParameters(eigenValues_3D[2]);
0190
0191 #if EDM4EIC_BUILD_VERSION >= EDM4EIC_VERSION(8, 10, 0)
0192
0193 out_clust.setRadius(static_cast<float>(radius));
0194 out_clust.setDispersion(static_cast<float>(dispersion));
0195 out_clust.setPrincipalAxesLengthsXYZ({
0196 static_cast<float>(std::sqrt(std::abs(eigenValues_3D[0]))),
0197 static_cast<float>(std::sqrt(std::abs(eigenValues_3D[1]))),
0198 static_cast<float>(std::sqrt(std::abs(eigenValues_3D[2]))),
0199 });
0200 out_clust.setPrincipalAxesLengthsThetaPhi({
0201 static_cast<float>(std::sqrt(std::abs(eigenValues_2D[0]))),
0202 static_cast<float>(std::sqrt(std::abs(eigenValues_2D[1]))),
0203 });
0204 #endif
0205
0206
0207 double dot_product = out_clust.getPosition() * axis;
0208 if (dot_product < 0) {
0209 axis = -1 * axis;
0210 }
0211
0212
0213 float intrinsicTheta = edm4hep::utils::anglePolar(axis);
0214 float intrinsicPhi = edm4hep::utils::angleAzimuthal(axis);
0215 out_clust.setIntrinsicTheta(intrinsicTheta);
0216 out_clust.setIntrinsicPhi(intrinsicPhi);
0217
0218
0219 trace("ClusterShape: radius={:.3f} [mm] dispersion={:.3f} [mm] "
0220 "2D w1={:.4f} w2={:.4f} [rad] "
0221 "3D w1={:.3f} w2={:.3f} w3={:.3f} [mm] "
0222 "intrinsicTheta={:.4f} Phi={:.4f} [rad]",
0223 radius, dispersion, std::sqrt(std::abs(eigenValues_2D[0])),
0224 std::sqrt(std::abs(eigenValues_2D[1])), std::sqrt(std::abs(eigenValues_3D[0])),
0225 std::sqrt(std::abs(eigenValues_3D[1])), std::sqrt(std::abs(eigenValues_3D[2])),
0226 intrinsicTheta, intrinsicPhi);
0227 }
0228
0229 out_clusters->push_back(out_clust);
0230
0231
0232
0233
0234 if (link_nav.enabled()) {
0235 for (const auto& [mc_par, weight] : link_nav.linked(in_clust)) {
0236 truth::addWeightedRelation(
0237 out_clust, mc_par, weight,
0238 gsl::not_null<edm4eic::MCRecoClusterParticleLinkCollection*>{out_links},
0239 gsl::not_null<edm4eic::MCRecoClusterParticleAssociationCollection*>{out_associations});
0240 }
0241 }
0242 }
0243 debug("Completed processing input clusters");
0244
0245 }
0246
0247 }