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 
26 
27 #include <algorithm>
28 #include <cassert>
29 #include <iterator>
30 #include <memory>
31 #include <vector>
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>
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 Interactor =
79  using InteractorResult = typename Interactor::result_type;
80  using Actions = Acts::ActionList<Interactor>;
81  using Abort = Acts::AbortList<typename Interactor::ParticleNotAlive,
83  using PropagatorOptions = Acts::PropagatorOptions<Actions, Abort>;
84 
85  // Construct per-call options.
86  PropagatorOptions options(geoCtx, magCtx,
88  options.absPdgCode = particle.pdg();
89  options.mass = particle.mass();
90  // setup the interactor as part of the propagator options
91  auto &interactor = options.actionList.template get<Interactor>();
92  interactor.generator = &generator;
93  interactor.physics = physics;
94  interactor.selectHitSurface = selectHitSurface;
95  interactor.particle = particle;
96  // use AnyCharge to be able to handle neutral and charged parameters
98  particle.position4(), particle.unitDirection(), particle.absMomentum(),
99  particle.charge());
100  auto result = propagator.propagate(start, options);
101  if (result.ok()) {
102  return result.value().template get<InteractorResult>();
103  } else {
104  return result.error();
105  }
106  }
107 };
108 
115 template <typename charged_selector_t, typename charged_simulator_t,
116  typename neutral_selector_t, typename neutral_simulator_t>
117 struct Simulator {
119  struct FailedParticle {
127  std::error_code error;
128  };
129 
130  charged_selector_t selectCharged;
131  neutral_selector_t selectNeutral;
132  charged_simulator_t charged;
133  neutral_simulator_t neutral;
134 
136  Simulator(charged_simulator_t &&charged_, neutral_simulator_t &&neutral_)
137  : charged(std::move(charged_)), neutral(std::move(neutral_)) {}
138 
174  template <typename generator_t, typename input_particles_t,
175  typename output_particles_t, typename hits_t>
178  const Acts::MagneticFieldContext &magCtx, generator_t &generator,
179  const input_particles_t &inputParticles,
180  output_particles_t &simulatedParticlesInitial,
181  output_particles_t &simulatedParticlesFinal, hits_t &hits) const {
182  assert(
183  (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) and
184  "Inconsistent initial sizes of the simulated particle containers");
185 
186  using ParticleSimulatorResult = Acts::Result<SimulationResult>;
187 
188  std::vector<FailedParticle> failedParticles;
189 
190  for (const Particle &inputParticle : inputParticles) {
191  // only consider simulatable particles
192  if (not selectParticle(inputParticle)) {
193  continue;
194  }
195  // required to allow correct particle id numbering for secondaries later
196  if ((inputParticle.particleId().generation() != 0u) or
197  (inputParticle.particleId().subParticle() != 0u)) {
198  return detail::SimulatorError::eInvalidInputParticleId;
199  }
200 
201  // Do a *depth-first* simulation of the particle and its secondaries,
202  // i.e. we simulate all secondaries, tertiaries, ... before simulating
203  // the next primary particle. Use the end of the output container as
204  // a queue to store particles that should be simulated.
205  //
206  // WARNING the initial particle state output container will be modified
207  // during iteration. New secondaries are added to and failed
208  // particles might be removed. to avoid issues, access must always
209  // occur via indices.
210  auto iinitial = simulatedParticlesInitial.size();
211  simulatedParticlesInitial.push_back(inputParticle);
212  for (; iinitial < simulatedParticlesInitial.size(); ++iinitial) {
213  const auto &initialParticle = simulatedParticlesInitial[iinitial];
214 
215  // only simulatable particles are pushed to the container.
216  // they must therefore be either charged or neutral.
217  ParticleSimulatorResult result = ParticleSimulatorResult::success({});
218  if (selectCharged(initialParticle)) {
219  result = charged.simulate(geoCtx, magCtx, generator, initialParticle);
220  } else {
221  result = neutral.simulate(geoCtx, magCtx, generator, initialParticle);
222  }
223 
224  if (not result.ok()) {
225  // remove particle from output container since it was not simulated.
226  simulatedParticlesInitial.erase(
227  std::next(simulatedParticlesInitial.begin(), iinitial));
228  // record the particle as failed
229  failedParticles.push_back({initialParticle, result.error()});
230  continue;
231  }
232 
233  copyOutputs(result.value(), simulatedParticlesInitial,
234  simulatedParticlesFinal, hits);
235  // since physics processes are independent, there can be particle id
236  // collisions within the generated secondaries. they can be resolved by
237  // renumbering within each sub-particle generation. this must happen
238  // before the particle is simulated since the particle id is used to
239  // associate generated hits back to the particle.
240  renumberTailParticleIds(simulatedParticlesInitial, iinitial);
241  }
242  }
243 
244  // the overall function call succeeded, i.e. no fatal errors occured.
245  // yet, there might have been some particle for which the propagation
246  // failed. thus, the successful result contains a list of failed particles.
247  // sounds a bit weird, but that is the way it is.
248  return failedParticles;
249  }
250 
251  private:
257  bool selectParticle(const Particle &particle) const {
258  const bool isValidCharged = selectCharged(particle);
259  const bool isValidNeutral = selectNeutral(particle);
260  assert(not(isValidCharged and isValidNeutral) and
261  "Charge selectors are not mutually exclusive");
262  return isValidCharged xor isValidNeutral;
263  }
264 
269  template <typename particles_t, typename hits_t>
270  void copyOutputs(const SimulationResult &result,
271  particles_t &particlesInitial, particles_t &particlesFinal,
272  hits_t &hits) const {
273  // initial particle state was already pushed to the container before
274  // store final particle state at the end of the simulation
275  particlesFinal.push_back(result.particle);
276  // move generated secondaries that should be simulated to the output
277  std::copy_if(
278  result.generatedParticles.begin(), result.generatedParticles.end(),
279  std::back_inserter(particlesInitial),
280  [this](const Particle &particle) { return selectParticle(particle); });
281  std::copy(result.hits.begin(), result.hits.end(), std::back_inserter(hits));
282  }
283 
298  template <typename particles_t>
299  static void renumberTailParticleIds(particles_t &particles,
300  std::size_t lastValid) {
301  // iterate over adjacent pairs; potentially modify the second element.
302  // assume e.g. a primary particle 2 with generation=subparticle=0 that
303  // generates two secondaries during simulation. we have the following
304  // ids (decoded as vertex|particle|generation|subparticle)
305  //
306  // v|2|0|0, v|2|1|0, v|2|1|0
307  //
308  // where v represents the vertex numbers. this will be renumbered to
309  //
310  // v|2|0|0, v|2|1|0, v|2|1|1
311  //
312  // if each secondary then generates two tertiaries we could have e.g.
313  //
314  // 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
315  //
316  // which would then be renumbered to
317  //
318  // 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
319  //
320  for (auto j = lastValid; (j + 1u) < particles.size(); ++j) {
321  const auto prevId = particles[j].particleId();
322  auto currId = particles[j + 1u].particleId();
323  // NOTE primary/secondary vertex and particle are assumed to be equal
324  // only renumber within one generation
325  if (prevId.generation() != currId.generation()) {
326  continue;
327  }
328  // ensure the sub-particle is strictly monotonic within a generation
329  if (prevId.subParticle() < currId.subParticle()) {
330  continue;
331  }
332  // sub-particle numbering must be non-zero
333  currId.setSubParticle(prevId.subParticle() + 1u);
334  particles[j + 1u] = particles[j + 1u].withParticleId(currId);
335  }
336  }
337 };
338 
339 } // namespace ActsFatras