Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-27 07:26:54

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/DigitizationAlgorithm.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/TrackParametrization.hpp"
0013 #include "Acts/Geometry/GeometryIdentifier.hpp"
0014 #include "Acts/Utilities/BinUtility.hpp"
0015 #include "ActsExamples/Digitization/ModuleClusters.hpp"
0016 #include "ActsExamples/EventData/GeometryContainers.hpp"
0017 #include "ActsExamples/EventData/Index.hpp"
0018 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0019 
0020 #include <algorithm>
0021 #include <array>
0022 #include <limits>
0023 #include <ostream>
0024 #include <stdexcept>
0025 #include <string>
0026 #include <utility>
0027 
0028 namespace ActsExamples {
0029 
0030 DigitizationAlgorithm::DigitizationAlgorithm(
0031     Config config, std::unique_ptr<const Acts::Logger> logger)
0032     : IAlgorithm("DigitizationAlgorithm", std::move(logger)),
0033       m_cfg(std::move(config)) {
0034   if (m_cfg.inputSimHits.empty()) {
0035     throw std::invalid_argument("Missing simulated hits input collection");
0036   }
0037   if (m_cfg.surfaceByIdentifier.empty()) {
0038     throw std::invalid_argument("Missing Surface-GeometryID association map");
0039   }
0040   if (!m_cfg.randomNumbers) {
0041     throw std::invalid_argument("Missing random numbers tool");
0042   }
0043   if (m_cfg.digitizationConfigs.empty()) {
0044     throw std::invalid_argument("Missing digitization configuration");
0045   }
0046 
0047   if (m_cfg.doClusterization) {
0048     if (m_cfg.outputMeasurements.empty()) {
0049       throw std::invalid_argument("Missing measurements output collection");
0050     }
0051     if (m_cfg.outputClusters.empty()) {
0052       throw std::invalid_argument("Missing cluster output collection");
0053     }
0054     if (m_cfg.outputMeasurementParticlesMap.empty()) {
0055       throw std::invalid_argument(
0056           "Missing hit-to-particles map output collection");
0057     }
0058     if (m_cfg.outputMeasurementSimHitsMap.empty()) {
0059       throw std::invalid_argument(
0060           "Missing hit-to-simulated-hits map output collection");
0061     }
0062     if (m_cfg.outputParticleMeasurementsMap.empty()) {
0063       throw std::invalid_argument(
0064           "Missing particle-to-measurements map output collection");
0065     }
0066     if (m_cfg.outputSimHitMeasurementsMap.empty()) {
0067       throw std::invalid_argument(
0068           "Missing particle-to-simulated-hits map output collection");
0069     }
0070 
0071     m_outputMeasurements.initialize(m_cfg.outputMeasurements);
0072     m_outputClusters.initialize(m_cfg.outputClusters);
0073     m_outputMeasurementParticlesMap.initialize(
0074         m_cfg.outputMeasurementParticlesMap);
0075     m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap);
0076     m_outputParticleMeasurementsMap.initialize(
0077         m_cfg.outputParticleMeasurementsMap);
0078     m_outputSimHitMeasurementsMap.initialize(m_cfg.outputSimHitMeasurementsMap);
0079   }
0080 
0081   if (m_cfg.doOutputCells) {
0082     if (m_cfg.outputCells.empty()) {
0083       throw std::invalid_argument("Missing cell output collection");
0084     }
0085 
0086     m_outputCells.initialize(m_cfg.outputCells);
0087   }
0088 
0089   m_inputHits.initialize(m_cfg.inputSimHits);
0090 
0091   // Create the digitizers from the configuration
0092   std::vector<std::pair<Acts::GeometryIdentifier, Digitizer>> digitizerInput;
0093 
0094   for (std::size_t i = 0; i < m_cfg.digitizationConfigs.size(); ++i) {
0095     GeometricConfig geoCfg;
0096     Acts::GeometryIdentifier geoId = m_cfg.digitizationConfigs.idAt(i);
0097 
0098     const auto& digiCfg = m_cfg.digitizationConfigs.valueAt(i);
0099     geoCfg = digiCfg.geometricDigiConfig;
0100     // Copy so we can sort in-place
0101     SmearingConfig smCfg = digiCfg.smearingDigiConfig;
0102 
0103     std::vector<Acts::BoundIndices> indices;
0104     for (auto& gcf : smCfg.params) {
0105       indices.push_back(gcf.index);
0106     }
0107     indices.insert(indices.begin(), geoCfg.indices.begin(),
0108                    geoCfg.indices.end());
0109 
0110     // Make sure the configured input parameter indices are sorted and unique
0111     std::ranges::sort(indices);
0112 
0113     auto dup = std::adjacent_find(indices.begin(), indices.end());
0114     if (dup != indices.end()) {
0115       throw std::invalid_argument(
0116           "Digitization configuration contains duplicate parameter indices");
0117     }
0118 
0119     switch (smCfg.params.size()) {
0120       case 0u:
0121         digitizerInput.emplace_back(geoId, makeDigitizer<0u>(digiCfg));
0122         break;
0123       case 1u:
0124         digitizerInput.emplace_back(geoId, makeDigitizer<1u>(digiCfg));
0125         break;
0126       case 2u:
0127         digitizerInput.emplace_back(geoId, makeDigitizer<2u>(digiCfg));
0128         break;
0129       case 3u:
0130         digitizerInput.emplace_back(geoId, makeDigitizer<3u>(digiCfg));
0131         break;
0132       case 4u:
0133         digitizerInput.emplace_back(geoId, makeDigitizer<4u>(digiCfg));
0134         break;
0135       default:
0136         throw std::invalid_argument("Unsupported smearer size");
0137     }
0138   }
0139 
0140   m_digitizers = Acts::GeometryHierarchyMap<Digitizer>(digitizerInput);
0141 }
0142 
0143 ProcessCode DigitizationAlgorithm::execute(const AlgorithmContext& ctx) const {
0144   // Retrieve input
0145   const auto& simHits = m_inputHits(ctx);
0146   ACTS_DEBUG("Loaded " << simHits.size() << " sim hits");
0147 
0148   // Prepare output containers
0149   // need list here for stable addresses
0150   MeasurementContainer measurements;
0151   ClusterContainer clusters;
0152 
0153   MeasurementParticlesMap measurementParticlesMap;
0154   MeasurementSimHitsMap measurementSimHitsMap;
0155   measurements.reserve(simHits.size());
0156   measurementParticlesMap.reserve(simHits.size());
0157   measurementSimHitsMap.reserve(simHits.size());
0158 
0159   // Setup random number generator
0160   auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
0161 
0162   // Some statistics
0163   std::size_t skippedHits = 0;
0164 
0165   // Some algorithms do the clusterization themselves such as the traccc chain.
0166   // Thus we need to store the cell data from the simulation.
0167   CellsMap cellsMap;
0168 
0169   ACTS_DEBUG("Starting loop over modules ...");
0170   for (const auto& simHitsGroup : groupByModule(simHits)) {
0171     // Manual pair unpacking instead of using
0172     //   auto [moduleGeoId, moduleSimHits] : ...
0173     // otherwise clang on macos complains that it is unable to capture the local
0174     // binding in the lambda used for visiting the smearer below.
0175     Acts::GeometryIdentifier moduleGeoId = simHitsGroup.first;
0176     const auto& moduleSimHits = simHitsGroup.second;
0177 
0178     auto surfaceItr = m_cfg.surfaceByIdentifier.find(moduleGeoId);
0179 
0180     if (surfaceItr == m_cfg.surfaceByIdentifier.end()) {
0181       // this is either an invalid geometry id or a misconfigured smearer
0182       // setup; both cases can not be handled and should be fatal.
0183       ACTS_ERROR("Could not find surface " << moduleGeoId
0184                                            << " for configured smearer");
0185       return ProcessCode::ABORT;
0186     }
0187 
0188     const Acts::Surface* surfacePtr = surfaceItr->second;
0189 
0190     auto digitizerItr = m_digitizers.find(moduleGeoId);
0191     if (digitizerItr == m_digitizers.end()) {
0192       ACTS_VERBOSE("No digitizer present for module " << moduleGeoId);
0193       continue;
0194     } else {
0195       ACTS_VERBOSE("Digitizer found for module " << moduleGeoId);
0196     }
0197 
0198     // Run the digitizer. Iterate over the hits for this surface inside the
0199     // visitor so we do not need to lookup the variant object per-hit.
0200     std::visit(
0201         [&](const auto& digitizer) {
0202           ModuleClusters moduleClusters(
0203               digitizer.geometric.segmentation, digitizer.geometric.indices,
0204               m_cfg.doMerge, m_cfg.mergeNsigma, m_cfg.mergeCommonCorner);
0205 
0206           for (auto h = moduleSimHits.begin(); h != moduleSimHits.end(); ++h) {
0207             const auto& simHit = *h;
0208             const auto simHitIdx = simHits.index_of(h);
0209 
0210             DigitizedParameters dParameters;
0211 
0212             if (simHit.depositedEnergy() < m_cfg.minEnergyDeposit) {
0213               ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::VERBOSE,
0214                                    "Skip hit because energy deposit to small");
0215               continue;
0216             }
0217 
0218             // Geometric part - 0, 1, 2 local parameters are possible
0219             if (!digitizer.geometric.indices.empty()) {
0220               ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::VERBOSE,
0221                                    "Configured to geometric digitize "
0222                                        << digitizer.geometric.indices.size()
0223                                        << " parameters.");
0224               const auto& cfg = digitizer.geometric;
0225               Acts::Vector3 driftDir = cfg.drift(simHit.position(), rng);
0226               auto channelsRes = m_channelizer.channelize(
0227                   simHit, *surfacePtr, ctx.geoContext, driftDir,
0228                   cfg.segmentation, cfg.thickness);
0229               if (!channelsRes.ok() || channelsRes->empty()) {
0230                 ACTS_LOG_WITH_LOGGER(
0231                     this->logger(), Acts::Logging::DEBUG,
0232                     "Geometric channelization did not work, skipping this "
0233                     "hit.");
0234                 continue;
0235               }
0236               ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::VERBOSE,
0237                                    "Activated " << channelsRes->size()
0238                                                 << " channels for this hit.");
0239               dParameters =
0240                   localParameters(digitizer.geometric, *channelsRes, rng);
0241             }
0242 
0243             // Smearing part - (optionally) rest
0244             if (!digitizer.smearing.indices.empty()) {
0245               ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::VERBOSE,
0246                                    "Configured to smear "
0247                                        << digitizer.smearing.indices.size()
0248                                        << " parameters.");
0249               auto res =
0250                   digitizer.smearing(rng, simHit, *surfacePtr, ctx.geoContext);
0251               if (!res.ok()) {
0252                 ++skippedHits;
0253                 ACTS_LOG_WITH_LOGGER(this->logger(), Acts::Logging::DEBUG,
0254                                      "Problem in hit smearing, skip hit ("
0255                                          << res.error().message() << ")");
0256                 continue;
0257               }
0258               const auto& [par, cov] = res.value();
0259               for (Eigen::Index ip = 0; ip < par.rows(); ++ip) {
0260                 dParameters.indices.push_back(digitizer.smearing.indices[ip]);
0261                 dParameters.values.push_back(par[ip]);
0262                 dParameters.variances.push_back(cov(ip, ip));
0263               }
0264             }
0265 
0266             // Check on success - threshold could have eliminated all channels
0267             if (dParameters.values.empty()) {
0268               ACTS_LOG_WITH_LOGGER(
0269                   this->logger(), Acts::Logging::VERBOSE,
0270                   "Parameter digitization did not yield a measurement.");
0271               continue;
0272             }
0273 
0274             moduleClusters.add(std::move(dParameters), simHitIdx);
0275           }
0276 
0277           auto digitizeParametersResult = moduleClusters.digitizedParameters();
0278 
0279           // Store the cell data into a map.
0280           if (m_cfg.doOutputCells) {
0281             std::vector<Cluster::Cell> cells;
0282             for (const auto& [dParameters, simHitsIdxs] :
0283                  digitizeParametersResult) {
0284               for (const auto& cell : dParameters.cluster.channels) {
0285                 cells.push_back(cell);
0286               }
0287             }
0288             cellsMap.insert({moduleGeoId, std::move(cells)});
0289           }
0290 
0291           if (m_cfg.doClusterization) {
0292             for (auto& [dParameters, simHitsIdxs] : digitizeParametersResult) {
0293               auto measurement =
0294                   createMeasurement(measurements, moduleGeoId, dParameters);
0295 
0296               dParameters.cluster.globalPosition = measurementGlobalPosition(
0297                   dParameters, *surfacePtr, ctx.geoContext);
0298               clusters.emplace_back(std::move(dParameters.cluster));
0299 
0300               for (auto simHitIdx : simHitsIdxs) {
0301                 measurementParticlesMap.emplace_hint(
0302                     measurementParticlesMap.end(), measurement.index(),
0303                     simHits.nth(simHitIdx)->particleId());
0304                 measurementSimHitsMap.emplace_hint(measurementSimHitsMap.end(),
0305                                                    measurement.index(),
0306                                                    simHitIdx);
0307               }
0308             }
0309           }
0310         },
0311         *digitizerItr);
0312   }
0313 
0314   if (skippedHits > 0) {
0315     ACTS_WARNING(
0316         skippedHits
0317         << " skipped in Digitization. Enable DEBUG mode to see more details.");
0318   }
0319 
0320   if (m_cfg.doClusterization) {
0321     ACTS_DEBUG("Created " << measurements.size() << " measurements, "
0322                           << clusters.size() << " clusters" << " from "
0323                           << simHits.size() << " sim hits.");
0324 
0325     m_outputMeasurements(ctx, std::move(measurements));
0326     m_outputClusters(ctx, std::move(clusters));
0327 
0328     // invert them before they are moved
0329     m_outputParticleMeasurementsMap(
0330         ctx, invertIndexMultimap(measurementParticlesMap));
0331     m_outputSimHitMeasurementsMap(ctx,
0332                                   invertIndexMultimap(measurementSimHitsMap));
0333 
0334     m_outputMeasurementParticlesMap(ctx, std::move(measurementParticlesMap));
0335     m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap));
0336   }
0337 
0338   if (m_cfg.doOutputCells) {
0339     m_outputCells(ctx, std::move(cellsMap));
0340   }
0341 
0342   return ProcessCode::SUCCESS;
0343 }
0344 
0345 DigitizedParameters DigitizationAlgorithm::localParameters(
0346     const GeometricConfig& geoCfg,
0347     const std::vector<ActsFatras::Segmentizer::ChannelSegment>& channels,
0348     RandomEngine& rng) const {
0349   DigitizedParameters dParameters;
0350 
0351   const auto& binningData = geoCfg.segmentation.binningData();
0352 
0353   // For digital readout, the weight needs to be split in x and y
0354   std::array<double, 2u> pos = {0., 0.};
0355   std::array<double, 2u> totalWeight = {0., 0.};
0356   std::array<std::size_t, 2u> bmin = {std::numeric_limits<std::size_t>::max(),
0357                                       std::numeric_limits<std::size_t>::max()};
0358   std::array<std::size_t, 2u> bmax = {0, 0};
0359 
0360   // The component digital store
0361   std::array<std::set<std::size_t>, 2u> componentChannels;
0362 
0363   // Combine the channels
0364   for (const auto& ch : channels) {
0365     auto bin = ch.bin;
0366     double charge = geoCfg.charge(ch.activation, rng);
0367     // Loop and check
0368     if (charge > geoCfg.threshold) {
0369       double weight = geoCfg.digital ? 1. : charge;
0370       for (std::size_t ib = 0; ib < 2; ++ib) {
0371         if (geoCfg.digital && geoCfg.componentDigital) {
0372           // only fill component of this row/column if not yet filled
0373           if (!componentChannels[ib].contains(bin[ib])) {
0374             totalWeight[ib] += weight;
0375             pos[ib] += weight * binningData[ib].center(bin[ib]);
0376             componentChannels[ib].insert(bin[ib]);
0377           }
0378         } else {
0379           totalWeight[ib] += weight;
0380           pos[ib] += weight * binningData[ib].center(bin[ib]);
0381         }
0382         // min max channels
0383         bmin[ib] = std::min(bmin[ib], static_cast<std::size_t>(bin[ib]));
0384         bmax[ib] = std::max(bmax[ib], static_cast<std::size_t>(bin[ib]));
0385       }
0386       // Create a copy of the channel, as activation may change
0387       auto chdig = ch;
0388       chdig.bin = ch.bin;
0389       chdig.activation = charge;
0390       dParameters.cluster.channels.push_back(chdig);
0391     }
0392   }
0393   if (totalWeight[0] > 0. && totalWeight[1] > 0.) {
0394     pos[0] /= totalWeight[0];
0395     pos[1] /= totalWeight[1];
0396     dParameters.indices = geoCfg.indices;
0397     for (auto idx : dParameters.indices) {
0398       dParameters.values.push_back(pos[idx]);
0399     }
0400     std::size_t size0 = (bmax[0] - bmin[0] + 1);
0401     std::size_t size1 = (bmax[1] - bmin[1] + 1);
0402 
0403     dParameters.variances = geoCfg.variances({size0, size1}, bmin);
0404     dParameters.cluster.sizeLoc0 = size0;
0405     dParameters.cluster.sizeLoc1 = size1;
0406   }
0407 
0408   return dParameters;
0409 }
0410 
0411 }  // namespace ActsExamples