File indexing completed on 2026-04-27 07:26:54
0001
0002
0003
0004
0005
0006
0007
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
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
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
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
0145 const auto& simHits = m_inputHits(ctx);
0146 ACTS_DEBUG("Loaded " << simHits.size() << " sim hits");
0147
0148
0149
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
0160 auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
0161
0162
0163 std::size_t skippedHits = 0;
0164
0165
0166
0167 CellsMap cellsMap;
0168
0169 ACTS_DEBUG("Starting loop over modules ...");
0170 for (const auto& simHitsGroup : groupByModule(simHits)) {
0171
0172
0173
0174
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
0182
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
0199
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
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
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
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
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
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
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
0361 std::array<std::set<std::size_t>, 2u> componentChannels;
0362
0363
0364 for (const auto& ch : channels) {
0365 auto bin = ch.bin;
0366 double charge = geoCfg.charge(ch.activation, rng);
0367
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
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
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
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 }