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