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