Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-06-27 07:42:30

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include "ActsExamples/Digitization/ModuleClusters.hpp"
0010 
0011 #include "Acts/Clusterization/Clusterization.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/Utilities/Helpers.hpp"
0014 #include "ActsExamples/Digitization/MeasurementCreation.hpp"
0015 #include "ActsExamples/EventData/SimHit.hpp"
0016 
0017 #include <array>
0018 #include <cmath>
0019 #include <cstdlib>
0020 #include <limits>
0021 #include <map>
0022 #include <stdexcept>
0023 
0024 namespace ActsExamples {
0025 
0026 void ModuleClusters::add(DigitizedParameters params, SimHitIndex simhit) {
0027   ModuleValue mval;
0028   mval.paramIndices = std::move(params.indices);
0029   mval.paramValues = std::move(params.values);
0030   mval.paramVariances = std::move(params.variances);
0031   mval.sources = {simhit};
0032 
0033   if (m_merge && !params.cluster.channels.empty()) {
0034     // Break-up the cluster
0035     for (const auto& cell : params.cluster.channels) {
0036       ModuleValue mval_cell = mval;
0037       mval_cell.value = cell;
0038       m_moduleValues.push_back(std::move(mval_cell));
0039     }
0040   } else {
0041     // pass-through mode or smeared indices only
0042     mval.value = std::move(params.cluster);
0043     m_moduleValues.push_back(std::move(mval));
0044   }
0045 }
0046 
0047 std::vector<std::pair<DigitizedParameters, std::set<SimHitIndex>>>
0048 ModuleClusters::digitizedParameters() {
0049   if (m_merge) {  // (re-)build the clusters
0050     merge();
0051   }
0052   std::vector<std::pair<DigitizedParameters, std::set<SimHitIndex>>> retv;
0053   for (ModuleValue& mval : m_moduleValues) {
0054     if (std::holds_alternative<Cluster::Cell>(mval.value)) {
0055       // Should never happen! Either the cluster should have
0056       // passed-through or the cells merged into clusters in the
0057       // merge() step. Treat this as a bug.
0058       throw std::runtime_error("Invalid cluster!");
0059     }
0060     DigitizedParameters dpars;
0061     dpars.indices = mval.paramIndices;
0062     dpars.values = mval.paramValues;
0063     dpars.variances = mval.paramVariances;
0064     dpars.cluster = std::get<Cluster>(mval.value);
0065     retv.emplace_back(std::move(dpars), mval.sources);
0066   }
0067   return retv;
0068 }
0069 
0070 // Needed for clusterization
0071 int getCellRow(const ModuleValue& mval) {
0072   if (std::holds_alternative<Cluster::Cell>(mval.value)) {
0073     return std::get<Cluster::Cell>(mval.value).bin[0];
0074   }
0075   throw std::domain_error("ModuleValue does not contain cell!");
0076 }
0077 
0078 int getCellColumn(const ModuleValue& mval) {
0079   if (std::holds_alternative<Cluster::Cell>(mval.value)) {
0080     return std::get<Cluster::Cell>(mval.value).bin[1];
0081   }
0082   throw std::domain_error("ModuleValue does not contain cell!");
0083 }
0084 
0085 void clusterAddCell(std::vector<ModuleValue>& cl, const ModuleValue& ce) {
0086   cl.push_back(ce);
0087 }
0088 
0089 std::vector<ModuleValue> ModuleClusters::createCellCollection() const {
0090   std::map<ActsFatras::Segmentizer::Bin2D, ModuleValue> uniqueCells;
0091   for (const ModuleValue& mval : m_moduleValues) {
0092     if (!std::holds_alternative<Cluster::Cell>(mval.value)) {
0093       continue;
0094     }
0095     const auto& cell = std::get<Cluster::Cell>(mval.value).bin;
0096 
0097     if (const auto it = uniqueCells.find(cell); it != uniqueCells.end()) {
0098       // Cell already exists, so merge the hit sources
0099       std::get<Cluster::Cell>(it->second.value).activation +=
0100           std::get<Cluster::Cell>(mval.value).activation;
0101     } else {
0102       // New cell
0103       uniqueCells[cell] = mval;
0104     }
0105   }
0106   std::vector<ModuleValue> cells;
0107   cells.reserve(uniqueCells.size());
0108   for (const auto& [_, mval] : uniqueCells) {
0109     cells.push_back(mval);
0110   }
0111   return cells;
0112 }
0113 
0114 void ModuleClusters::merge() {
0115   std::vector<ModuleValue> cells = createCellCollection();
0116 
0117   std::vector<ModuleValue> newVals;
0118 
0119   if (!cells.empty()) {
0120     // Case where we actually have geometric clusters
0121     Acts::Ccl::ClusteringData data;
0122     std::vector<std::vector<ModuleValue>> merged;
0123     Acts::Ccl::createClusters<std::vector<ModuleValue>,
0124                               std::vector<std::vector<ModuleValue>>>(
0125         data, cells, merged,
0126         Acts::Ccl::DefaultConnect<ModuleValue>(m_commonCorner));
0127 
0128     for (std::vector<ModuleValue>& cellv : merged) {
0129       // At this stage, the cellv vector contains cells that form a
0130       // consistent cluster based on a connected component analysis
0131       // only. Still have to check if they match based on the other
0132       // indices (a good example of this would a for a timing
0133       // detector).
0134 
0135       for (std::vector<ModuleValue>& remerged : mergeParameters(cellv)) {
0136         newVals.push_back(squash(remerged));
0137       }
0138     }
0139     m_moduleValues = std::move(newVals);
0140   } else {
0141     // no geo clusters
0142     for (std::vector<ModuleValue>& merged : mergeParameters(m_moduleValues)) {
0143       newVals.push_back(squash(merged));
0144     }
0145     m_moduleValues = std::move(newVals);
0146   }
0147 }
0148 
0149 // ATTN: returns vector of index into `indices'
0150 std::vector<std::size_t> ModuleClusters::nonGeoEntries(
0151     std::vector<Acts::BoundIndices>& indices) const {
0152   std::vector<std::size_t> retv;
0153   for (std::size_t i = 0; i < indices.size(); i++) {
0154     auto idx = indices.at(i);
0155     if (!rangeContainsValue(m_geoIndices, idx)) {
0156       retv.push_back(i);
0157     }
0158   }
0159   return retv;
0160 }
0161 
0162 // Merging based on parameters
0163 std::vector<std::vector<ModuleValue>> ModuleClusters::mergeParameters(
0164     std::vector<ModuleValue> values) const {
0165   std::vector<std::vector<ModuleValue>> retv;
0166 
0167   std::vector<bool> used(values.size(), false);
0168   for (std::size_t i = 0; i < values.size(); i++) {
0169     if (used.at(i)) {
0170       continue;
0171     }
0172 
0173     retv.emplace_back();
0174     std::vector<ModuleValue>& thisvec = retv.back();
0175 
0176     // Value has not yet been claimed, so claim it
0177     thisvec.push_back(std::move(values.at(i)));
0178     used.at(i) = true;
0179 
0180     // Values previously visited by index `i' have already been added
0181     // to a cluster or used to seed a new cluster, so start at the
0182     // next unseen one
0183     for (std::size_t j = i + 1; j < values.size(); j++) {
0184       // Still may have already been used, so check it
0185       if (used.at(j)) {
0186         continue;
0187       }
0188 
0189       // Now look for a match between current cluster and value `j' Consider
0190       // them matched until we find evidence to the contrary. This
0191       // way, merging still works when digitization is done by
0192       // clusters only
0193       bool matched = true;
0194 
0195       // The cluster to be merged into can have more than one
0196       // associated value at this point, so we have to consider them
0197       // all
0198       for (ModuleValue& thisval : thisvec) {
0199         // Loop over non-geometric dimensions
0200         for (auto k : nonGeoEntries(thisval.paramIndices)) {
0201           double p_i = thisval.paramValues.at(k);
0202           double p_j = values.at(j).paramValues.at(k);
0203           double v_i = thisval.paramVariances.at(k);
0204           double v_j = values.at(j).paramVariances.at(k);
0205 
0206           double left = 0, right = 0;
0207           if (p_i < p_j) {
0208             left = p_i + m_nsigma * std::sqrt(v_i);
0209             right = p_j - m_nsigma * std::sqrt(v_j);
0210           } else {
0211             left = p_j + m_nsigma * std::sqrt(v_j);
0212             right = p_i - m_nsigma * std::sqrt(v_i);
0213           }
0214           if (left < right) {
0215             // We know these two don't match, so break out of the
0216             // dimension loop
0217             matched = false;
0218             break;
0219           }
0220         }  // Loop over `k' (non-geo dimensions)
0221         if (matched) {
0222           // The value under consideration matched at least one
0223           // associated to the current cluster so no need to keep
0224           // checking others in current cluster
0225           break;
0226         }
0227       }  // Loop on current cluster
0228       if (matched) {
0229         // Claim value `j'
0230         used.at(j) = true;
0231         thisvec.push_back(std::move(values.at(j)));
0232       }
0233     }  // Loop on `j'
0234   }  // Loop on `i'
0235   return retv;
0236 }
0237 
0238 ModuleValue ModuleClusters::squash(std::vector<ModuleValue>& values) const {
0239   if (values.empty()) {
0240     throw std::runtime_error("Cannot squash empty cluster!");
0241   }
0242 
0243   ModuleValue result;
0244 
0245   double tot = 0;
0246   double tot2 = 0;
0247   std::vector<double> weights;
0248 
0249   // First, start by computing cell weights
0250   for (const ModuleValue& other : values) {
0251     if (std::holds_alternative<Cluster::Cell>(other.value)) {
0252       weights.push_back(std::get<Cluster::Cell>(other.value).activation);
0253     } else {
0254       weights.push_back(1);
0255     }
0256     tot += weights.back();
0257     tot2 += weights.back() * weights.back();
0258   }
0259 
0260   // Do the geometric indices first: they precede the smeared ones in
0261   // the parameter vectors filled by the digitization algorithm, so
0262   // seeding `result' with them keeps the slot positions used in the
0263   // non-geometric accumulation below aligned with the source layout.
0264   Cluster cluster;
0265 
0266   const auto& binningData = m_segmentation.binningData();
0267   Acts::Vector2 pos(0., 0.);
0268   Acts::Vector2 var(0., 0.);
0269 
0270   std::size_t b0min = std::numeric_limits<std::size_t>::max();
0271   std::size_t b0max = 0;
0272   std::size_t b1min = std::numeric_limits<std::size_t>::max();
0273   std::size_t b1max = 0;
0274 
0275   for (std::size_t i = 0; i < values.size(); i++) {
0276     const ModuleValue& other = values.at(i);
0277     if (!std::holds_alternative<Cluster::Cell>(other.value)) {
0278       continue;
0279     }
0280 
0281     Cluster::Cell ch = std::get<Cluster::Cell>(other.value);
0282     const auto bin = ch.bin;
0283 
0284     const std::size_t b0 = bin[0];
0285     const std::size_t b1 = bin[1];
0286 
0287     b0min = std::min(b0min, b0);
0288     b0max = std::max(b0max, b0);
0289     b1min = std::min(b1min, b1);
0290     b1max = std::max(b1max, b1);
0291 
0292     const float p0 = binningData[0].center(b0);
0293     const float w0 = binningData[0].width(b0);
0294     const float p1 = binningData[1].center(b1);
0295     const float w1 = binningData[1].width(b1);
0296 
0297     pos += Acts::Vector2(weights.at(i) * p0, weights.at(i) * p1);
0298     // Assume uniform distribution to compute error
0299     // N.B. This will overestimate the variance
0300     // but it's better than nothing for now
0301     var += Acts::Vector2(weights.at(i) * weights.at(i) * w0 * w0 / 12,
0302                          weights.at(i) * weights.at(i) * w1 * w1 / 12);
0303 
0304     cluster.channels.push_back(std::move(ch));
0305 
0306     // Will have the right value at last iteration Do it here to
0307     // avoid having bogus values when there are no clusters
0308     cluster.sizeLoc0 = b0max - b0min + 1;
0309     cluster.sizeLoc1 = b1max - b1min + 1;
0310   }
0311 
0312   if (!m_geoIndices.empty() && cluster.channels.empty()) {
0313     throw std::runtime_error(
0314         "Expected to have at least one cell for a cluster with geo indices!");
0315   }
0316 
0317   if (tot > 0) {
0318     pos /= tot;
0319     var /= tot * tot;
0320   }
0321 
0322   for (const Acts::BoundIndices idx : m_geoIndices) {
0323     result.paramIndices.push_back(idx);
0324     result.paramValues.push_back(pos[idx]);
0325     result.paramVariances.push_back(var[idx]);
0326   }
0327 
0328   // Now, go over the non-geometric indices. Each source value carries
0329   // the geometric entries (if any) first, in `m_geoIndices' order, so
0330   // after seeding `result' with the geometric entries above the source
0331   // position `j' is also the correct slot in `result'.
0332   for (std::size_t i = 0; i < values.size(); i++) {
0333     const ModuleValue& other = values.at(i);
0334     for (std::size_t j = 0; j < other.paramIndices.size(); j++) {
0335       const Acts::BoundIndices idx = other.paramIndices.at(j);
0336       if (!rangeContainsValue(m_geoIndices, idx)) {
0337         if (!rangeContainsValue(result.paramIndices, idx)) {
0338           result.paramIndices.push_back(idx);
0339         }
0340         if (result.paramValues.size() < j + 1) {
0341           result.paramValues.push_back(0);
0342           result.paramVariances.push_back(0);
0343         }
0344         double f = weights.at(i) / (tot > 0 ? tot : 1);
0345         double f2 = weights.at(i) * weights.at(i) / (tot2 > 0 ? tot2 : 1);
0346         result.paramValues.at(j) += f * other.paramValues.at(j);
0347         result.paramVariances.at(j) += f2 * other.paramVariances.at(j);
0348       }
0349     }
0350   }
0351 
0352   result.value = std::move(cluster);
0353 
0354   // Finally do the hit association
0355   for (ModuleValue& other : values) {
0356     result.sources.merge(other.sources);
0357   }
0358 
0359   return result;
0360 }
0361 
0362 }  // namespace ActsExamples