Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
DigitizationAlgorithm.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file DigitizationAlgorithm.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2021 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
10 
28 
29 #include <algorithm>
30 #include <array>
31 #include <cmath>
32 #include <cstdint>
33 #include <ostream>
34 #include <set>
35 #include <stdexcept>
36 #include <string>
37 #include <utility>
38 
41  : ActsExamples::IAlgorithm("DigitizationAlgorithm", level),
42  m_cfg(std::move(config)) {
43  if (m_cfg.inputSimHits.empty()) {
44  throw std::invalid_argument("Missing simulated hits input collection");
45  }
46  if (m_cfg.outputMeasurements.empty()) {
47  throw std::invalid_argument("Missing measurements output collection");
48  }
49  if (m_cfg.outputSourceLinks.empty()) {
50  throw std::invalid_argument("Missing source links output collection");
51  }
53  throw std::invalid_argument(
54  "Missing hit-to-particles map output collection");
55  }
56  if (m_cfg.outputMeasurementSimHitsMap.empty()) {
57  throw std::invalid_argument(
58  "Missing hit-to-simulated-hits map output collection");
59  }
60  if (not m_cfg.trackingGeometry) {
61  throw std::invalid_argument("Missing tracking geometry");
62  }
63  if (not m_cfg.randomNumbers) {
64  throw std::invalid_argument("Missing random numbers tool");
65  }
66 
68  throw std::invalid_argument("Missing digitization configuration");
69  }
70 
79 
80  // Create the digitizers from the configuration
81  std::vector<std::pair<Acts::GeometryIdentifier, Digitizer>> digitizerInput;
82 
83  for (size_t i = 0; i < m_cfg.digitizationConfigs.size(); ++i) {
84  GeometricConfig geoCfg;
86 
87  const auto& digiCfg = m_cfg.digitizationConfigs.valueAt(i);
88  geoCfg = digiCfg.geometricDigiConfig;
89  // Copy so we can sort in-place
90  SmearingConfig smCfg = digiCfg.smearingDigiConfig;
91 
92  std::vector<Acts::BoundIndices> indices;
93  for (auto& gcf : smCfg) {
94  indices.push_back(gcf.index);
95  }
96  indices.insert(indices.begin(), geoCfg.indices.begin(),
97  geoCfg.indices.end());
98 
99  // Make sure the configured input parameter indices are sorted and unique
100  std::sort(indices.begin(), indices.end());
101 
102  auto dup = std::adjacent_find(indices.begin(), indices.end());
103  if (dup != indices.end()) {
104  std::invalid_argument(
105  "Digitization configuration contains duplicate parameter indices");
106  }
107 
108  switch (smCfg.size()) {
109  case 0u:
110  digitizerInput.emplace_back(geoId, makeDigitizer<0u>(digiCfg));
111  break;
112  case 1u:
113  digitizerInput.emplace_back(geoId, makeDigitizer<1u>(digiCfg));
114  break;
115  case 2u:
116  digitizerInput.emplace_back(geoId, makeDigitizer<2u>(digiCfg));
117  break;
118  case 3u:
119  digitizerInput.emplace_back(geoId, makeDigitizer<3u>(digiCfg));
120  break;
121  case 4u:
122  digitizerInput.emplace_back(geoId, makeDigitizer<4u>(digiCfg));
123  break;
124  default:
125  throw std::invalid_argument("Unsupported smearer size");
126  }
127  }
128 
130 }
131 
133  const AlgorithmContext& ctx) const {
134  // Retrieve input
135  const auto& simHits = m_simContainerReadHandle(ctx);
136  ACTS_DEBUG("Loaded " << simHits.size() << " sim hits");
137 
138  // Prepare output containers
139  // need list here for stable addresses
140  IndexSourceLinkContainer sourceLinks;
141  MeasurementContainer measurements;
143  IndexMultimap<ActsFatras::Barcode> measurementParticlesMap;
144  IndexMultimap<Index> measurementSimHitsMap;
145  sourceLinks.reserve(simHits.size());
146  measurements.reserve(simHits.size());
147  measurementParticlesMap.reserve(simHits.size());
148  measurementSimHitsMap.reserve(simHits.size());
149 
150  // Setup random number generator
151  auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
152 
153  // Some statistics
154  std::size_t skippedHits = 0;
155 
156  ACTS_DEBUG("Starting loop over modules ...");
157  for (const auto& simHitsGroup : groupByModule(simHits)) {
158  // Manual pair unpacking instead of using
159  // auto [moduleGeoId, moduleSimHits] : ...
160  // otherwise clang on macos complains that it is unable to capture the local
161  // binding in the lambda used for visiting the smearer below.
162  Acts::GeometryIdentifier moduleGeoId = simHitsGroup.first;
163  const auto& moduleSimHits = simHitsGroup.second;
164 
165  const Acts::Surface* surfacePtr =
166  m_cfg.trackingGeometry->findSurface(moduleGeoId);
167 
168  if (surfacePtr == nullptr) {
169  // this is either an invalid geometry id or a misconfigured smearer
170  // setup; both cases can not be handled and should be fatal.
171  ACTS_ERROR("Could not find surface " << moduleGeoId
172  << " for configured smearer");
173  return ProcessCode::ABORT;
174  }
175 
176  auto digitizerItr = m_digitizers.find(moduleGeoId);
177  if (digitizerItr == m_digitizers.end()) {
178  ACTS_VERBOSE("No digitizer present for module " << moduleGeoId);
179  continue;
180  } else {
181  ACTS_VERBOSE("Digitizer found for module " << moduleGeoId);
182  }
183 
184  // Run the digitizer. Iterate over the hits for this surface inside the
185  // visitor so we do not need to lookup the variant object per-hit.
186  std::visit(
187  [&](const auto& digitizer) {
188  ModuleClusters moduleClusters(
189  digitizer.geometric.segmentation, digitizer.geometric.indices,
190  m_cfg.doMerge, m_cfg.mergeNsigma, m_cfg.mergeCommonCorner);
191 
192  for (auto h = moduleSimHits.begin(); h != moduleSimHits.end(); ++h) {
193  const auto& simHit = *h;
194  const auto simHitIdx = simHits.index_of(h);
195 
196  DigitizedParameters dParameters;
197 
198  if (simHit.depositedEnergy() < m_cfg.minEnergyDeposit) {
199  ACTS_VERBOSE("Skip hit because energy deposit to small")
200  continue;
201  }
202 
203  // Geometric part - 0, 1, 2 local parameters are possible
204  if (not digitizer.geometric.indices.empty()) {
205  ACTS_VERBOSE("Configured to geometric digitize "
206  << digitizer.geometric.indices.size()
207  << " parameters.");
208  auto channels = channelizing(digitizer.geometric, simHit,
209  *surfacePtr, ctx.geoContext, rng);
210  if (channels.empty()) {
211  ACTS_DEBUG(
212  "Geometric channelization did not work, skipping this hit.")
213  continue;
214  }
215  ACTS_VERBOSE("Activated " << channels.size()
216  << " channels for this hit.");
217  dParameters = localParameters(digitizer.geometric, channels, rng);
218  }
219 
220  // Smearing part - (optionally) rest
221  if (not digitizer.smearing.indices.empty()) {
222  ACTS_VERBOSE("Configured to smear "
223  << digitizer.smearing.indices.size()
224  << " parameters.");
225  auto res =
226  digitizer.smearing(rng, simHit, *surfacePtr, ctx.geoContext);
227  if (not res.ok()) {
228  ++skippedHits;
229  ACTS_DEBUG("Problem in hit smearing, skip hit ("
230  << res.error().message() << ")");
231  continue;
232  }
233  const auto& [par, cov] = res.value();
234  for (Eigen::Index ip = 0; ip < par.rows(); ++ip) {
235  dParameters.indices.push_back(digitizer.smearing.indices[ip]);
236  dParameters.values.push_back(par[ip]);
237  dParameters.variances.push_back(cov(ip, ip));
238  }
239  }
240 
241  // Check on success - threshold could have eliminated all channels
242  if (dParameters.values.empty()) {
243  ACTS_VERBOSE(
244  "Parameter digitization did not yield a measurement.")
245  continue;
246  }
247 
248  moduleClusters.add(std::move(dParameters), simHitIdx);
249  }
250 
251  for (auto& [dParameters, simhits] :
252  moduleClusters.digitizedParameters()) {
253  // The measurement container is unordered and the index under which
254  // the measurement will be stored is known before adding it.
255  Index measurementIdx = measurements.size();
256  IndexSourceLink sourceLink{moduleGeoId, measurementIdx};
257 
258  // Add to output containers:
259  // index map and source link container are geometry-ordered.
260  // since the input is also geometry-ordered, new items can
261  // be added at the end.
262  sourceLinks.insert(sourceLinks.end(), sourceLink);
263 
264  measurements.emplace_back(
265  createMeasurement(dParameters, sourceLink));
266  clusters.emplace_back(std::move(dParameters.cluster));
267  // this digitization does hit merging so there can be more than one
268  // mapping entry for each digitized hit.
269  for (auto simHitIdx : simhits) {
270  measurementParticlesMap.emplace_hint(
271  measurementParticlesMap.end(), measurementIdx,
272  simHits.nth(simHitIdx)->particleId());
273  measurementSimHitsMap.emplace_hint(measurementSimHitsMap.end(),
274  measurementIdx, simHitIdx);
275  }
276  }
277  },
278  *digitizerItr);
279  }
280 
281  if (skippedHits > 0) {
282  ACTS_WARNING(
283  skippedHits
284  << " skipped in Digitization. Enable DEBUG mode to see more details.");
285  }
286 
287  m_sourceLinkWriteHandle(ctx, std::move(sourceLinks));
288  m_measurementWriteHandle(ctx, std::move(measurements));
289  m_clusterWriteHandle(ctx, std::move(clusters));
290  m_measurementParticlesMapWriteHandle(ctx, std::move(measurementParticlesMap));
291  m_measurementSimHitsMapWriteHandle(ctx, std::move(measurementSimHitsMap));
292  return ProcessCode::SUCCESS;
293 }
294 
295 std::vector<ActsFatras::Channelizer::ChannelSegment>
297  const GeometricConfig& geoCfg, const SimHit& hit,
299  RandomEngine& rng) const {
300  Acts::Vector3 driftDir = geoCfg.drift(hit.position(), rng);
301 
302  auto driftedSegment =
303  m_surfaceDrift.toReadout(gctx, surface, geoCfg.thickness, hit.position(),
304  hit.direction(), driftDir);
305  auto maskedSegmentRes = m_surfaceMask.apply(surface, driftedSegment);
306  if (maskedSegmentRes.ok()) {
307  auto maskedSegment = maskedSegmentRes.value();
308  // Now Channelize
309  return m_channelizer.segments(gctx, surface, geoCfg.segmentation,
310  maskedSegment);
311  }
312  return {};
313 }
314 
317  const GeometricConfig& geoCfg,
318  const std::vector<ActsFatras::Channelizer::ChannelSegment>& channels,
319  RandomEngine& rng) const {
320  DigitizedParameters dParameters;
321 
322  const auto& binningData = geoCfg.segmentation.binningData();
323 
324  Acts::ActsScalar totalWeight = 0.;
325  Acts::Vector2 m(0., 0.);
326  size_t b0min = SIZE_MAX;
327  size_t b0max = 0;
328  size_t b1min = SIZE_MAX;
329  size_t b1max = 0;
330  // Combine the channels
331  for (const auto& ch : channels) {
332  auto bin = ch.bin;
334  geoCfg.digital ? 1. : geoCfg.charge(ch.activation, rng);
335  if (geoCfg.digital or charge > geoCfg.threshold) {
336  totalWeight += charge;
337  size_t b0 = bin[0];
338  size_t b1 = bin[1];
339  m += Acts::Vector2(charge * binningData[0].center(b0),
340  charge * binningData[1].center(b1));
341  b0min = std::min(b0min, b0);
342  b0max = std::max(b0max, b0);
343  b1min = std::min(b1min, b1);
344  b1max = std::max(b1max, b1);
345  // Create a copy of the channel, as activation may change
346  auto chdig = ch;
347  chdig.bin = ch.bin;
348  chdig.activation = charge;
349  dParameters.cluster.channels.push_back(chdig);
350  }
351  }
352  if (totalWeight > 0.) {
353  m *= 1. / totalWeight;
354  dParameters.indices = geoCfg.indices;
355  for (auto idx : dParameters.indices) {
356  dParameters.values.push_back(m[idx]);
357  }
358  size_t size0 = static_cast<size_t>(b0max - b0min + 1);
359  size_t size1 = static_cast<size_t>(b1max - b1min + 1);
360  auto variances = geoCfg.variances(size0, size1, rng);
361  if (variances.size() == dParameters.indices.size()) {
362  dParameters.variances = variances;
363  } else {
364  dParameters.variances =
365  std::vector<Acts::ActsScalar>(dParameters.indices.size(), -1.);
366  }
367 
368  if (dParameters.variances[0] == -1) {
369  size_t ictr = b0min + size0 / 2;
370  dParameters.variances[0] = std::pow(binningData[0].width(ictr), 2) / 12.0;
371  }
372  if (dParameters.variances[1] == -1) {
373  size_t ictr = b1min + size1 / 2;
374  dParameters.variances[1] = std::pow(binningData[1].width(ictr), 2) / 12.0;
375  }
376 
377  dParameters.cluster.sizeLoc0 = size0;
378  dParameters.cluster.sizeLoc1 = size1;
379  }
380 
381  return dParameters;
382 }