File indexing completed on 2025-07-14 08:14:33
0001
0002
0003
0004
0005
0006
0007
0008 #include <DD4hep/Handle.h>
0009 #include <DD4hep/Readout.h>
0010 #include <Evaluator/DD4hepUnits.h>
0011 #include <algorithms/service.h>
0012 #include <edm4hep/Vector2f.h>
0013 #include <edm4hep/Vector3f.h>
0014 #include <edm4hep/utils/vector_utils.h>
0015 #include <fmt/core.h>
0016 #include <fmt/format.h>
0017 #include <algorithm>
0018 #include <cmath>
0019 #include <gsl/pointers>
0020 #include <map>
0021 #include <set>
0022 #include <stdexcept>
0023 #include <string>
0024 #include <tuple>
0025 #include <unordered_map>
0026 #include <utility>
0027 #include <variant>
0028 #include <vector>
0029
0030 #include "CalorimeterIslandCluster.h"
0031 #include "algorithms/calorimetry/CalorimeterIslandClusterConfig.h"
0032 #include "services/evaluator/EvaluatorSvc.h"
0033
0034 using namespace edm4eic;
0035
0036 namespace eicrecon {
0037 template <typename... L> struct multilambda : L... {
0038 using L::operator()...;
0039 constexpr multilambda(L... lambda) : L(std::move(lambda))... {}
0040 };
0041
0042 static double Phi_mpi_pi(double phi) { return std::remainder(phi, 2 * M_PI); }
0043
0044 static edm4hep::Vector2f localDistXY(const CaloHit& h1, const CaloHit& h2) {
0045 const auto delta = h1.getLocal() - h2.getLocal();
0046 return {delta.x, delta.y};
0047 }
0048 static edm4hep::Vector2f localDistXZ(const CaloHit& h1, const CaloHit& h2) {
0049 const auto delta = h1.getLocal() - h2.getLocal();
0050 return {delta.x, delta.z};
0051 }
0052 static edm4hep::Vector2f localDistYZ(const CaloHit& h1, const CaloHit& h2) {
0053 const auto delta = h1.getLocal() - h2.getLocal();
0054 return {delta.y, delta.z};
0055 }
0056 static edm4hep::Vector2f dimScaledLocalDistXY(const CaloHit& h1, const CaloHit& h2) {
0057 const auto delta = h1.getLocal() - h2.getLocal();
0058
0059 const auto dimsum = h1.getDimension() + h2.getDimension();
0060
0061 return {2 * delta.x / dimsum.x, 2 * delta.y / dimsum.y};
0062 }
0063 static edm4hep::Vector2f globalDistRPhi(const CaloHit& h1, const CaloHit& h2) {
0064 using vector_type = decltype(edm4hep::Vector2f::a);
0065 return {static_cast<vector_type>(edm4hep::utils::magnitude(h1.getPosition()) -
0066 edm4hep::utils::magnitude(h2.getPosition())),
0067 static_cast<vector_type>(Phi_mpi_pi(edm4hep::utils::angleAzimuthal(h1.getPosition()) -
0068 edm4hep::utils::angleAzimuthal(h2.getPosition())))};
0069 }
0070 static edm4hep::Vector2f globalDistEtaPhi(const CaloHit& h1, const CaloHit& h2) {
0071 using vector_type = decltype(edm4hep::Vector2f::a);
0072 return {static_cast<vector_type>(edm4hep::utils::eta(h1.getPosition()) -
0073 edm4hep::utils::eta(h2.getPosition())),
0074 static_cast<vector_type>(Phi_mpi_pi(edm4hep::utils::angleAzimuthal(h1.getPosition()) -
0075 edm4hep::utils::angleAzimuthal(h2.getPosition())))};
0076 }
0077
0078
0079
0080
0081 void CalorimeterIslandCluster::init() {
0082
0083 multilambda _toDouble = {
0084 [](const std::string& v) { return dd4hep::_toDouble(v); },
0085 [](const double& v) { return v; },
0086 };
0087
0088 if (m_cfg.localDistXY.size() == 2) {
0089 m_localDistXY.push_back(std::visit(_toDouble, m_cfg.localDistXY[0]));
0090 m_localDistXY.push_back(std::visit(_toDouble, m_cfg.localDistXY[1]));
0091 }
0092
0093 static std::map<std::string,
0094 std::tuple<std::function<edm4hep::Vector2f(const CaloHit&, const CaloHit&)>,
0095 std::vector<double>>>
0096 distMethods{{"localDistXY", {localDistXY, {dd4hep::mm, dd4hep::mm}}},
0097 {"localDistXZ", {localDistXZ, {dd4hep::mm, dd4hep::mm}}},
0098 {"localDistYZ", {localDistYZ, {dd4hep::mm, dd4hep::mm}}},
0099 {"dimScaledLocalDistXY", {dimScaledLocalDistXY, {1., 1.}}},
0100 {"globalDistRPhi", {globalDistRPhi, {dd4hep::mm, dd4hep::rad}}},
0101 {"globalDistEtaPhi", {globalDistEtaPhi, {1., dd4hep::rad}}}};
0102
0103
0104 auto set_dist_method = [this](std::pair<std::string, std::vector<double>> uprop) {
0105 if (uprop.second.empty()) {
0106 return false;
0107 }
0108 auto& [method, units] = distMethods[uprop.first];
0109 if (uprop.second.size() != units.size()) {
0110 warning("Expect {} values from {}, received {}. ignored it.", units.size(), uprop.first,
0111 uprop.second.size());
0112 return false;
0113 }
0114 for (std::size_t i = 0; i < units.size(); ++i) {
0115
0116 neighbourDist[i] = uprop.second[i] / units[i];
0117 }
0118 hitsDist = method;
0119 info("Clustering uses {} with distances <= [{}]", uprop.first, fmt::join(neighbourDist, ","));
0120
0121 return true;
0122 };
0123
0124 std::vector<std::pair<std::string, std::vector<double>>> uprops{
0125 {"localDistXY", m_localDistXY},
0126 {"localDistXZ", m_cfg.localDistXZ},
0127 {"localDistYZ", m_cfg.localDistYZ},
0128 {"globalDistRPhi", m_cfg.globalDistRPhi},
0129 {"globalDistEtaPhi", m_cfg.globalDistEtaPhi},
0130
0131 {"dimScaledLocalDistXY", m_cfg.dimScaledLocalDistXY}};
0132
0133 auto& serviceSvc = algorithms::ServiceSvc::instance();
0134
0135 std::function hit_pair_to_map = [this](const edm4eic::CalorimeterHit& h1,
0136 const edm4eic::CalorimeterHit& h2) {
0137 std::unordered_map<std::string, double> params;
0138 for (const auto& p : m_idSpec.fields()) {
0139 const std::string& name = p.first;
0140 const dd4hep::IDDescriptor::Field* field = p.second;
0141 params.emplace(name + "_1", field->value(h1.getCellID()));
0142 params.emplace(name + "_2", field->value(h2.getCellID()));
0143 trace("{}_1 = {}", name, field->value(h1.getCellID()));
0144 trace("{}_2 = {}", name, field->value(h2.getCellID()));
0145 }
0146 return params;
0147 };
0148
0149 if (m_cfg.readout.empty()) {
0150 if ((!m_cfg.adjacencyMatrix.empty()) || (!m_cfg.peakNeighbourhoodMatrix.empty())) {
0151 throw std::runtime_error(
0152 "'readout' is not provided, it is needed to know the fields in readout ids");
0153 }
0154 } else {
0155 m_idSpec = m_detector->readout(m_cfg.readout).idSpec();
0156 }
0157
0158 bool method_found = false;
0159
0160
0161 if (!m_cfg.adjacencyMatrix.empty()) {
0162 is_neighbour = serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")
0163 ->compile(m_cfg.adjacencyMatrix, hit_pair_to_map);
0164 method_found = true;
0165 }
0166
0167
0168 if (not method_found) {
0169 for (auto& uprop : uprops) {
0170 if (set_dist_method(uprop)) {
0171 method_found = true;
0172
0173 is_neighbour = [this](const CaloHit& h1, const CaloHit& h2) {
0174
0175 if (h1.getSector() == h2.getSector()) {
0176 auto dist = hitsDist(h1, h2);
0177 return (std::abs(dist.a) <= neighbourDist[0]) && (std::abs(dist.b) <= neighbourDist[1]);
0178
0179 }
0180
0181 return (edm4hep::utils::magnitude(h1.getPosition() - h2.getPosition()) <=
0182 m_cfg.sectorDist / dd4hep::mm);
0183 };
0184
0185 break;
0186 }
0187 }
0188 }
0189
0190 if (not method_found) {
0191 throw std::runtime_error("Cannot determine the clustering coordinates");
0192 }
0193
0194 if (m_cfg.splitCluster) {
0195 if (!m_cfg.peakNeighbourhoodMatrix.empty()) {
0196 is_maximum_neighbourhood = serviceSvc.service<EvaluatorSvc>("EvaluatorSvc")
0197 ->compile(m_cfg.peakNeighbourhoodMatrix, hit_pair_to_map);
0198 } else {
0199 is_maximum_neighbourhood = is_neighbour;
0200 }
0201
0202 auto transverseEnergyProfileMetric_it =
0203 std::find_if(distMethods.begin(), distMethods.end(),
0204 [&](auto& p) { return m_cfg.transverseEnergyProfileMetric == p.first; });
0205 if (transverseEnergyProfileMetric_it == distMethods.end()) {
0206 throw std::runtime_error(
0207 fmt::format(R"(Unsupported value "{}" for "transverseEnergyProfileMetric")",
0208 m_cfg.transverseEnergyProfileMetric));
0209 }
0210 transverseEnergyProfileMetric = std::get<0>(transverseEnergyProfileMetric_it->second);
0211 std::vector<double>& units = std::get<1>(transverseEnergyProfileMetric_it->second);
0212 for (auto unit : units) {
0213 if (unit != units[0]) {
0214 throw std::runtime_error(fmt::format("Metric {} has incompatible dimension units",
0215 m_cfg.transverseEnergyProfileMetric));
0216 }
0217 }
0218 transverseEnergyProfileScaleUnits = units[0];
0219 }
0220 }
0221
0222 void CalorimeterIslandCluster::process(const CalorimeterIslandCluster::Input& input,
0223 const CalorimeterIslandCluster::Output& output) const {
0224
0225 const auto [hits] = input;
0226 auto [proto_clusters] = output;
0227
0228
0229 std::vector<std::set<std::size_t>> groups;
0230
0231 std::vector<bool> visits(hits->size(), false);
0232 for (std::size_t i = 0; i < hits->size(); ++i) {
0233
0234 {
0235 const auto& hit = (*hits)[i];
0236 debug("hit {:d}: energy = {:.4f} MeV, local = ({:.4f}, {:.4f}) mm, global=({:.4f}, {:.4f}, "
0237 "{:.4f}) mm",
0238 i, hit.getEnergy() * 1000., hit.getLocal().x, hit.getLocal().y, hit.getPosition().x,
0239 hit.getPosition().y, hit.getPosition().z);
0240 }
0241
0242 if (visits[i]) {
0243 continue;
0244 }
0245 groups.emplace_back();
0246
0247 bfs_group(*hits, groups.back(), i, visits);
0248 }
0249
0250 for (auto& group : groups) {
0251 if (group.empty()) {
0252 continue;
0253 }
0254 auto maxima = find_maxima(*hits, group, !m_cfg.splitCluster);
0255 split_group(*hits, group, maxima, proto_clusters);
0256
0257 debug("hits in a group: {}, local maxima: {}", group.size(), maxima.size());
0258 }
0259 }
0260
0261
0262 void CalorimeterIslandCluster::bfs_group(const edm4eic::CalorimeterHitCollection& hits,
0263 std::set<std::size_t>& group, std::size_t idx,
0264 std::vector<bool>& visits) const {
0265 visits[idx] = true;
0266
0267
0268 if (hits[idx].getEnergy() < m_cfg.minClusterHitEdep) {
0269 return;
0270 }
0271
0272 group.insert(idx);
0273 std::size_t prev_size = 0;
0274
0275 while (prev_size != group.size()) {
0276 prev_size = group.size();
0277 for (std::size_t idx1 : group) {
0278
0279 for (std::size_t idx2 = 0; idx2 < hits.size(); ++idx2) {
0280
0281 if (hits[idx2].getEnergy() < m_cfg.minClusterHitEdep) {
0282 continue;
0283 }
0284 if ((!visits[idx2]) && is_neighbour(hits[idx1], hits[idx2])) {
0285 group.insert(idx2);
0286 visits[idx2] = true;
0287 }
0288 }
0289 }
0290 }
0291 }
0292
0293
0294 std::vector<std::size_t>
0295 CalorimeterIslandCluster::find_maxima(const edm4eic::CalorimeterHitCollection& hits,
0296 const std::set<std::size_t>& group, bool global) const {
0297 std::vector<std::size_t> maxima;
0298 if (group.empty()) {
0299 return maxima;
0300 }
0301
0302 if (global) {
0303 std::size_t mpos = *group.begin();
0304 for (auto idx : group) {
0305 if (hits[mpos].getEnergy() < hits[idx].getEnergy()) {
0306 mpos = idx;
0307 }
0308 }
0309 if (hits[mpos].getEnergy() >= m_cfg.minClusterCenterEdep) {
0310 maxima.push_back(mpos);
0311 }
0312 return maxima;
0313 }
0314
0315 for (std::size_t idx1 : group) {
0316
0317 if (hits[idx1].getEnergy() < m_cfg.minClusterCenterEdep) {
0318 continue;
0319 }
0320
0321 bool maximum = true;
0322 for (std::size_t idx2 : group) {
0323 if (idx1 == idx2) {
0324 continue;
0325 }
0326
0327 if (is_maximum_neighbourhood(hits[idx1], hits[idx2]) &&
0328 (hits[idx2].getEnergy() > hits[idx1].getEnergy())) {
0329 maximum = false;
0330 break;
0331 }
0332 }
0333
0334 if (maximum) {
0335 maxima.push_back(idx1);
0336 }
0337 }
0338
0339 return maxima;
0340 }
0341
0342
0343
0344 void CalorimeterIslandCluster::split_group(const edm4eic::CalorimeterHitCollection& hits,
0345 std::set<std::size_t>& group,
0346 const std::vector<std::size_t>& maxima,
0347 edm4eic::ProtoClusterCollection* protoClusters) const {
0348
0349 if (maxima.empty()) {
0350 debug("No maxima found, not building any clusters");
0351 return;
0352 } else if (maxima.size() == 1) {
0353 edm4eic::MutableProtoCluster pcl = protoClusters->create();
0354 for (std::size_t idx : group) {
0355 pcl.addToHits(hits[idx]);
0356 pcl.addToWeights(1.);
0357 }
0358
0359 debug("A single maximum found, added one ProtoCluster");
0360
0361 return;
0362 }
0363
0364
0365
0366 std::vector<double> weights(maxima.size(), 1.);
0367 std::vector<edm4eic::MutableProtoCluster> pcls;
0368 for (std::size_t k = 0; k < maxima.size(); ++k) {
0369 pcls.push_back(protoClusters->create());
0370 }
0371
0372 for (std::size_t idx : group) {
0373 std::size_t j = 0;
0374
0375 for (std::size_t cidx : maxima) {
0376 double energy = hits[cidx].getEnergy();
0377 double dist = edm4hep::utils::magnitude(transverseEnergyProfileMetric(hits[cidx], hits[idx]));
0378 weights[j] =
0379 std::exp(-dist * transverseEnergyProfileScaleUnits / m_cfg.transverseEnergyProfileScale) *
0380 energy;
0381 j += 1;
0382 }
0383
0384
0385 vec_normalize(weights);
0386
0387
0388 for (auto& w : weights) {
0389 if (w < 0.02) {
0390 w = 0;
0391 }
0392 }
0393 vec_normalize(weights);
0394
0395
0396 for (std::size_t k = 0; k < maxima.size(); ++k) {
0397 double weight = weights[k];
0398 if (weight <= 1e-6) {
0399 continue;
0400 }
0401 pcls[k].addToHits(hits[idx]);
0402 pcls[k].addToWeights(weight);
0403 }
0404 }
0405 debug("Multiple ({}) maxima found, added a ProtoClusters for each maximum", maxima.size());
0406 }
0407
0408 }