File indexing completed on 2026-08-24 08:21:06
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 "ActsExamples/Digitization/ModuleClusters.hpp"
0015 #include "ActsExamples/EventData/GeometryContainers.hpp"
0016 #include "ActsExamples/EventData/Index.hpp"
0017 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0018
0019 #include <algorithm>
0020 #include <array>
0021 #include <limits>
0022 #include <numeric>
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_outputMeasurementSubset.initialize(m_cfg.outputMeasurementSubset);
0073 m_outputClusters.initialize(m_cfg.outputClusters);
0074 m_outputMeasurementParticlesMap.initialize(
0075 m_cfg.outputMeasurementParticlesMap);
0076 m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap);
0077 m_outputParticleMeasurementsMap.initialize(
0078 m_cfg.outputParticleMeasurementsMap);
0079 m_outputSimHitMeasurementsMap.initialize(m_cfg.outputSimHitMeasurementsMap);
0080 }
0081
0082 if (m_cfg.doOutputCells) {
0083 if (m_cfg.outputCells.empty()) {
0084 throw std::invalid_argument("Missing cell output collection");
0085 }
0086
0087 m_outputCells.initialize(m_cfg.outputCells);
0088 }
0089
0090 m_inputHits.initialize(m_cfg.inputSimHits);
0091
0092
0093 std::vector<std::pair<Acts::GeometryIdentifier, Digitizer>> digitizerInput;
0094
0095 for (std::size_t i = 0; i < m_cfg.digitizationConfigs.size(); ++i) {
0096 GeometricConfig geoCfg;
0097 Acts::GeometryIdentifier geoId = m_cfg.digitizationConfigs.idAt(i);
0098
0099 const auto& digiCfg = m_cfg.digitizationConfigs.valueAt(i);
0100 geoCfg = digiCfg.geometricDigiConfig;
0101
0102 if (!geoCfg.indices.empty() && geoCfg.segmentation == nullptr) {
0103 throw std::invalid_argument(
0104 "Geometric digitization requires a segmentation");
0105 }
0106
0107 SmearingConfig smCfg = digiCfg.smearingDigiConfig;
0108
0109 std::vector<Acts::BoundIndices> indices;
0110 for (auto& gcf : smCfg.params) {
0111 indices.push_back(gcf.index);
0112 }
0113 indices.insert(indices.begin(), geoCfg.indices.begin(),
0114 geoCfg.indices.end());
0115
0116
0117 std::ranges::sort(indices);
0118
0119 auto dup = std::adjacent_find(indices.begin(), indices.end());
0120 if (dup != indices.end()) {
0121 throw std::invalid_argument(
0122 "Digitization configuration contains duplicate parameter indices");
0123 }
0124
0125 switch (smCfg.params.size()) {
0126 case 0u:
0127 digitizerInput.emplace_back(geoId, makeDigitizer<0u>(digiCfg));
0128 break;
0129 case 1u:
0130 digitizerInput.emplace_back(geoId, makeDigitizer<1u>(digiCfg));
0131 break;
0132 case 2u:
0133 digitizerInput.emplace_back(geoId, makeDigitizer<2u>(digiCfg));
0134 break;
0135 case 3u:
0136 digitizerInput.emplace_back(geoId, makeDigitizer<3u>(digiCfg));
0137 break;
0138 case 4u:
0139 digitizerInput.emplace_back(geoId, makeDigitizer<4u>(digiCfg));
0140 break;
0141 default:
0142 throw std::invalid_argument("Unsupported smearer size");
0143 }
0144 }
0145
0146 m_digitizers = Acts::GeometryHierarchyMap<Digitizer>(digitizerInput);
0147 }
0148
0149 ProcessCode DigitizationAlgorithm::execute(const AlgorithmContext& ctx) const {
0150
0151 const auto& simHits = m_inputHits(ctx);
0152 ACTS_DEBUG("Loaded " << simHits.size() << " sim hits");
0153
0154
0155
0156 MeasurementContainer measurements;
0157 ClusterContainer clusters;
0158
0159 MeasurementParticlesMap measurementParticlesMap;
0160 MeasurementSimHitsMap measurementSimHitsMap;
0161 measurements.reserve(simHits.size());
0162 measurementParticlesMap.reserve(simHits.size());
0163 measurementSimHitsMap.reserve(simHits.size());
0164
0165
0166 auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
0167
0168
0169 std::size_t skippedHits = 0;
0170
0171
0172
0173 CellsMap cellsMap;
0174
0175 ACTS_DEBUG("Starting loop over modules ...");
0176 for (const auto& simHitsGroup : groupByModule(simHits)) {
0177
0178
0179
0180
0181 Acts::GeometryIdentifier moduleGeoId = simHitsGroup.first;
0182 const auto& moduleSimHits = simHitsGroup.second;
0183
0184 auto surfaceItr = m_cfg.surfaceByIdentifier.find(moduleGeoId);
0185
0186 if (surfaceItr == m_cfg.surfaceByIdentifier.end()) {
0187
0188
0189 ACTS_ERROR("Could not find surface " << moduleGeoId
0190 << " for configured smearer");
0191 return ProcessCode::ABORT;
0192 }
0193
0194 const Acts::Surface* surfacePtr = surfaceItr->second;
0195
0196 auto digitizerItr = m_digitizers.find(moduleGeoId);
0197 if (digitizerItr == m_digitizers.end()) {
0198 ACTS_VERBOSE("No digitizer present for module " << moduleGeoId);
0199 continue;
0200 } else {
0201 ACTS_VERBOSE("Digitizer found for module " << moduleGeoId);
0202 }
0203
0204
0205
0206 std::visit(
0207 [&](const auto& digitizer) {
0208 ModuleClusters moduleClusters(
0209 digitizer.geometric.segmentation, digitizer.geometric.indices,
0210 m_cfg.doMerge, m_cfg.mergeNsigma, m_cfg.mergeCommonCorner);
0211
0212 for (auto h = moduleSimHits.begin(); h != moduleSimHits.end(); ++h) {
0213 const auto& simHit = *h;
0214 const auto simHitIdx = simHits.index_of(h);
0215
0216 DigitizedParameters dParameters;
0217
0218 if (simHit.depositedEnergy() < m_cfg.minEnergyDeposit) {
0219 ACTS_VERBOSE("Skip hit because energy deposit to small");
0220 continue;
0221 }
0222
0223
0224 if (!digitizer.geometric.indices.empty()) {
0225 ACTS_VERBOSE("Configured to geometric digitize "
0226 << digitizer.geometric.indices.size()
0227 << " parameters.");
0228 const auto& cfg = digitizer.geometric;
0229 Acts::Vector3 driftDir = cfg.drift(simHit.position(), rng);
0230 auto channelsRes = m_channelizer.channelize(
0231 simHit, *surfacePtr, ctx.geoContext, driftDir,
0232 *cfg.segmentation, cfg.thickness);
0233 if (!channelsRes.ok() || channelsRes->empty()) {
0234 ACTS_DEBUG(
0235 "Geometric channelization did not work, skipping this "
0236 "hit.");
0237 continue;
0238 }
0239 ACTS_VERBOSE("Activated " << channelsRes->size()
0240 << " channels for this hit.");
0241 dParameters =
0242 localParameters(digitizer.geometric, *channelsRes, rng);
0243 if (dParameters.cluster.channels.empty()) {
0244 ACTS_DEBUG("All channels below threshold, skipping this hit.");
0245 continue;
0246 }
0247 }
0248
0249
0250 if (!digitizer.smearing.indices.empty()) {
0251 ACTS_VERBOSE("Configured to smear "
0252 << digitizer.smearing.indices.size()
0253 << " parameters.");
0254 auto res =
0255 digitizer.smearing(rng, simHit, *surfacePtr, ctx.geoContext);
0256 if (!res.ok()) {
0257 ++skippedHits;
0258 ACTS_DEBUG("Problem in hit smearing, skip hit ("
0259 << res.error().message() << ")");
0260 continue;
0261 }
0262 const auto& [par, cov] = res.value();
0263 for (Eigen::Index ip = 0; ip < par.rows(); ++ip) {
0264 dParameters.indices.push_back(digitizer.smearing.indices[ip]);
0265 dParameters.values.push_back(par[ip]);
0266 dParameters.variances.push_back(cov(ip, ip));
0267 }
0268 }
0269
0270
0271 if (dParameters.values.empty()) {
0272 ACTS_VERBOSE(
0273 "Parameter digitization did not yield a measurement.");
0274 continue;
0275 }
0276
0277 moduleClusters.add(std::move(dParameters), simHitIdx);
0278 }
0279
0280 auto digitizeParametersResult = moduleClusters.digitizedParameters();
0281
0282
0283 if (m_cfg.doOutputCells) {
0284 std::vector<Cluster::Cell> cells;
0285 for (const auto& [dParameters, simHitsIdxs] :
0286 digitizeParametersResult) {
0287 for (const auto& cell : dParameters.cluster.channels) {
0288 cells.push_back(cell);
0289 }
0290 }
0291 cellsMap.insert({moduleGeoId, std::move(cells)});
0292 }
0293
0294 if (m_cfg.doClusterization) {
0295 for (auto& [dParameters, simHitsIdxs] : digitizeParametersResult) {
0296 auto measurement =
0297 createMeasurement(measurements, moduleGeoId, dParameters);
0298
0299 dParameters.cluster.globalPosition = measurementGlobalPosition(
0300 dParameters, *surfacePtr, ctx.geoContext);
0301 clusters.emplace_back(std::move(dParameters.cluster));
0302
0303 for (auto simHitIdx : simHitsIdxs) {
0304 measurementParticlesMap.emplace_hint(
0305 measurementParticlesMap.end(), measurement.index(),
0306 simHits.nth(simHitIdx)->particleId());
0307 measurementSimHitsMap.emplace_hint(measurementSimHitsMap.end(),
0308 measurement.index(),
0309 simHitIdx);
0310 }
0311 }
0312 }
0313 },
0314 *digitizerItr);
0315 }
0316
0317 if (skippedHits > 0) {
0318 ACTS_WARNING(
0319 skippedHits
0320 << " skipped in Digitization. Enable DEBUG mode to see more details.");
0321 }
0322
0323 if (m_cfg.doClusterization) {
0324 ACTS_DEBUG("Created " << measurements.size() << " measurements, "
0325 << clusters.size() << " clusters" << " from "
0326 << simHits.size() << " sim hits.");
0327
0328 const auto& storedMeasurements =
0329 m_outputMeasurements(ctx, std::move(measurements));
0330
0331
0332 std::vector<MeasurementContainer::Index> allIndices(
0333 storedMeasurements.size());
0334 std::iota(allIndices.begin(), allIndices.end(), Index{0});
0335 m_outputMeasurementSubset(
0336 ctx, MeasurementSubset(storedMeasurements, std::move(allIndices)));
0337
0338 m_outputClusters(ctx, std::move(clusters));
0339
0340
0341 m_outputParticleMeasurementsMap(
0342 ctx, invertIndexMultimap(measurementParticlesMap));
0343 m_outputSimHitMeasurementsMap(ctx,
0344 invertIndexMultimap(measurementSimHitsMap));
0345
0346 m_outputMeasurementParticlesMap(ctx, std::move(measurementParticlesMap));
0347 m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap));
0348 }
0349
0350 if (m_cfg.doOutputCells) {
0351 m_outputCells(ctx, std::move(cellsMap));
0352 }
0353
0354 return ProcessCode::SUCCESS;
0355 }
0356
0357 DigitizedParameters DigitizationAlgorithm::localParameters(
0358 const GeometricConfig& geoCfg,
0359 const std::vector<ActsFatras::Segmentizer::ChannelSegment>& channels,
0360 RandomEngine& rng) const {
0361 DigitizedParameters dParameters;
0362
0363
0364 std::array<double, 2u> pos = {0., 0.};
0365 std::array<double, 2u> totalWeight = {0., 0.};
0366 std::array<std::size_t, 2u> bmin = {std::numeric_limits<std::size_t>::max(),
0367 std::numeric_limits<std::size_t>::max()};
0368 std::array<std::size_t, 2u> bmax = {0, 0};
0369
0370
0371 std::array<std::set<std::size_t>, 2u> componentChannels;
0372
0373
0374 for (const auto& ch : channels) {
0375 auto bin = ch.bin;
0376 double charge = geoCfg.charge(ch.activation, rng);
0377
0378 if (charge > geoCfg.threshold) {
0379 double weight = geoCfg.digital ? 1. : charge;
0380 for (std::size_t ib = 0; ib < 2; ++ib) {
0381
0382 const double binCenter =
0383 geoCfg.segmentation->getAxis(ib).getBinCenter(bin[ib] + 1);
0384 if (geoCfg.digital && geoCfg.componentDigital) {
0385
0386 if (!componentChannels[ib].contains(bin[ib])) {
0387 totalWeight[ib] += weight;
0388 pos[ib] += weight * binCenter;
0389 componentChannels[ib].insert(bin[ib]);
0390 }
0391 } else {
0392 totalWeight[ib] += weight;
0393 pos[ib] += weight * binCenter;
0394 }
0395
0396 bmin[ib] = std::min(bmin[ib], static_cast<std::size_t>(bin[ib]));
0397 bmax[ib] = std::max(bmax[ib], static_cast<std::size_t>(bin[ib]));
0398 }
0399
0400 auto chdig = ch;
0401 chdig.bin = ch.bin;
0402 chdig.activation = charge;
0403 dParameters.cluster.channels.push_back(chdig);
0404 }
0405 }
0406 if (totalWeight[0] > 0. && totalWeight[1] > 0.) {
0407 pos[0] /= totalWeight[0];
0408 pos[1] /= totalWeight[1];
0409 dParameters.indices = geoCfg.indices;
0410 for (auto idx : dParameters.indices) {
0411 dParameters.values.push_back(pos[idx]);
0412 }
0413 std::size_t size0 = (bmax[0] - bmin[0] + 1);
0414 std::size_t size1 = (bmax[1] - bmin[1] + 1);
0415
0416 dParameters.variances = geoCfg.variances({size0, size1}, bmin);
0417 dParameters.cluster.sizeLoc0 = size0;
0418 dParameters.cluster.sizeLoc1 = size1;
0419 }
0420
0421 return dParameters;
0422 }
0423
0424 }