ECCE @ EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Simulator.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file Simulator.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2018-2020 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 
9 #pragma once
10 
11 #include <algorithm>
12 #include <cassert>
13 #include <iterator>
14 #include <memory>
15 #include <vector>
16 
32 
33 namespace ActsFatras {
34 
40 template <typename propagator_t, typename physics_list_t,
41  typename hit_surface_selector_t>
46  physics_list_t physics;
48  hit_surface_selector_t selectHitSurface;
50  std::shared_ptr<const Acts::Logger> localLogger = nullptr;
51 
54  : propagator(propagator_),
55  localLogger(Acts::getDefaultLogger("Simulator", lvl)) {}
56 
58  const Acts::Logger &logger() const { return *localLogger; }
59 
69  template <typename generator_t>
71  const Acts::GeometryContext &geoCtx,
72  const Acts::MagneticFieldContext &magCtx, generator_t &generator,
73  const Particle &particle) const {
74  assert(localLogger and "Missing local logger");
75 
76  // propagator-related additional types
77  using Interact =
80  using Abort = Acts::AbortList<typename Interact::ParticleNotAlive,
82  using PropagatorOptions = Acts::PropagatorOptions<Actions, Abort>;
83 
84  // Construct per-call options.
85  PropagatorOptions options(geoCtx, magCtx);
86  options.absPdgCode = particle.pdg();
87  options.mass = particle.mass();
88  options.debug = localLogger->doPrint(Acts::Logging::Level::DEBUG);
89  // setup the interactor as part of the propagator options
90  auto &interactor = options.actionList.template get<Interact>();
91  interactor.generator = &generator;
92  interactor.physics = physics;
93  interactor.selectHitSurface = selectHitSurface;
94  interactor.particle = particle;
95 
96  // run with a start parameter type depending on the particle charge.
97  // TODO make track parameters consistently constructible regardless
98  // of the charge policy and template the class on the parameter.
99  if (particle.charge() != 0) {
101  std::nullopt, particle.position(),
102  particle.absMomentum() * particle.unitDirection(), particle.charge(),
103  particle.time());
104  auto result = propagator.propagate(start, options);
105  if (result.ok()) {
106  return result.value().template get<InteractorResult>();
107  } else {
108  return result.error();
109  }
110  } else {
112  std::nullopt, particle.position(),
113  particle.absMomentum() * particle.unitDirection(), particle.time());
114  auto result = propagator.propagate(start, options);
115  if (result.ok()) {
116  return result.value().template get<InteractorResult>();
117  } else {
118  return result.error();
119  }
120  }
121  }
122 };
123 
130 template <typename charged_selector_t, typename charged_simulator_t,
131  typename neutral_selector_t, typename neutral_simulator_t>
132 struct Simulator {
134  struct FailedParticle {
142  std::error_code error;
143  };
144 
145  charged_selector_t selectCharged;
146  neutral_selector_t selectNeutral;
147  charged_simulator_t charged;
148  neutral_simulator_t neutral;
149 
151  Simulator(charged_simulator_t &&charged_, neutral_simulator_t &&neutral_)
152  : charged(std::move(charged_)), neutral(std::move(neutral_)) {}
153 
189  template <typename generator_t, typename input_particles_t,
190  typename output_particles_t, typename hits_t>
192  const Acts::GeometryContext &geoCtx,
193  const Acts::MagneticFieldContext &magCtx, generator_t &generator,
194  const input_particles_t &inputParticles,
195  output_particles_t &simulatedParticlesInitial,
196  output_particles_t &simulatedParticlesFinal, hits_t &hits) const {
197  assert(
198  (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) and
199  "Inconsistent initial sizes of the simulated particle containers");
200 
201  using ParticleSimulatorResult = Acts::Result<InteractorResult>;
202 
203  std::vector<FailedParticle> failedParticles;
204 
205  for (const Particle &inputParticle : inputParticles) {
206  // only consider simulatable particles
207  if (not selectParticle(inputParticle)) {
208  continue;
209  }
210  // required to allow correct particle id numbering for secondaries later
211  if ((inputParticle.particleId().generation() != 0u) or
212  (inputParticle.particleId().subParticle() != 0u)) {
213  return detail::SimulatorError::eInvalidInputParticleId;
214  }
215 
216  // Do a *depth-first* simulation of the particle and its secondaries,
217  // i.e. we simulate all secondaries, tertiaries, ... before simulating
218  // the next primary particle. Use the end of the output container as
219  // a queue to store particles that should be simulated.
220  //
221  // WARNING the initial particle state output container will be modified
222  // during iteration. New secondaries are added to and failed
223  // particles might be removed. to avoid issues, access must always
224  // occur via indices.
225  auto iinitial = simulatedParticlesInitial.size();
226  simulatedParticlesInitial.push_back(inputParticle);
227  for (; iinitial < simulatedParticlesInitial.size(); ++iinitial) {
228  const auto &initialParticle = simulatedParticlesInitial[iinitial];
229 
230  // only simulatable particles are pushed to the container.
231  // they must therefore be either charged or neutral.
232  ParticleSimulatorResult result = ParticleSimulatorResult::success({});
233  if (selectCharged(initialParticle)) {
234  result = charged.simulate(geoCtx, magCtx, generator, initialParticle);
235  } else {
236  result = neutral.simulate(geoCtx, magCtx, generator, initialParticle);
237  }
238 
239  if (not result.ok()) {
240  // remove particle from output container since it was not simulated.
241  simulatedParticlesInitial.erase(
242  std::next(simulatedParticlesInitial.begin(), iinitial));
243  // record the particle as failed
244  failedParticles.push_back({initialParticle, result.error()});
245  continue;
246  }
247 
248  copyOutputs(result.value(), simulatedParticlesInitial,
249  simulatedParticlesFinal, hits);
250  // since physics processes are independent, there can be particle id
251  // collisions within the generated secondaries. they can be resolved by
252  // renumbering within each sub-particle generation. this must happen
253  // before the particle is simulated since the particle id is used to
254  // associate generated hits back to the particle.
255  renumberTailParticleIds(simulatedParticlesInitial, iinitial);
256  }
257  }
258 
259  // the overall function call succeeded, i.e. no fatal errors occured.
260  // yet, there might have been some particle for which the propagation
261  // failed. thus, the successful result contains a list of failed particles.
262  // sounds a bit weird, but that is the way it is.
263  return failedParticles;
264  }
265 
266  private:
272  bool selectParticle(const Particle &particle) const {
273  const bool isValidCharged = selectCharged(particle);
274  const bool isValidNeutral = selectNeutral(particle);
275  assert(not(isValidCharged and isValidNeutral) and
276  "Charge selectors are not mutually exclusive");
277  return isValidCharged xor isValidNeutral;
278  }
279 
284  template <typename particles_t, typename hits_t>
285  void copyOutputs(const InteractorResult &result,
286  particles_t &particlesInitial, particles_t &particlesFinal,
287  hits_t &hits) const {
288  // initial particle state was already pushed to the container before
289  // store final particle state at the end of the simulation
290  particlesFinal.push_back(result.particle);
291  // move generated secondaries that should be simulated to the output
292  std::copy_if(
293  result.generatedParticles.begin(), result.generatedParticles.end(),
294  std::back_inserter(particlesInitial),
295  [this](const Particle &particle) { return selectParticle(particle); });
296  std::copy(result.hits.begin(), result.hits.end(), std::back_inserter(hits));
297  }
298 
313  template <typename particles_t>
314  static void renumberTailParticleIds(particles_t &particles,
315  std::size_t lastValid) {
316  // iterate over adjacent pairs; potentially modify the second element.
317  // assume e.g. a primary particle 2 with generation=subparticle=0 that
318  // generates two secondaries during simulation. we have the following
319  // ids (decoded as vertex|particle|generation|subparticle)
320  //
321  // v|2|0|0, v|2|1|0, v|2|1|0
322  //
323  // where v represents the vertex numbers. this will be renumbered to
324  //
325  // v|2|0|0, v|2|1|0, v|2|1|1
326  //
327  // if each secondary then generates two tertiaries we could have e.g.
328  //
329  // v|2|0|0, v|2|1|0, v|2|1|1, v|2|2|0, v|2|2|1, v|2|2|0, v|2|2|1
330  //
331  // which would then be renumbered to
332  //
333  // v|2|0|0, v|2|1|0, v|2|1|1, v|2|2|0, v|2|2|1, v|2|2|2, v|2|2|3
334  //
335  for (auto j = lastValid; (j + 1u) < particles.size(); ++j) {
336  const auto prevId = particles[j].particleId();
337  auto currId = particles[j + 1u].particleId();
338  // NOTE primary/secondary vertex and particle are assumed to be equal
339  // only renumber within one generation
340  if (prevId.generation() != currId.generation()) {
341  continue;
342  }
343  // ensure the sub-particle is strictly monotonic within a generation
344  if (prevId.subParticle() < currId.subParticle()) {
345  continue;
346  }
347  // sub-particle numbering must be non-zero
348  currId.setSubParticle(prevId.subParticle() + 1u);
349  particles[j + 1u] = particles[j + 1u].withParticleId(currId);
350  }
351  }
352 };
353 
354 } // namespace ActsFatras