Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Simulation.hpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file Simulation.hpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2018-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 
9 #pragma once
10 
27 
28 #include <algorithm>
29 #include <cassert>
30 #include <iterator>
31 #include <memory>
32 #include <vector>
33 
34 namespace ActsFatras {
35 
42 template <typename propagator_t, typename interactions_t,
43  typename hit_surface_selector_t, typename decay_t>
48  decay_t decay;
50  interactions_t interactions;
52  hit_surface_selector_t selectHitSurface;
54  std::unique_ptr<const Acts::Logger> logger;
55 
58  std::unique_ptr<const Acts::Logger> _logger)
59  : propagator(propagator_), logger(std::move(_logger)) {}
60 
70  template <typename generator_t>
73  const Acts::MagneticFieldContext &magCtx, generator_t &generator,
74  const Particle &particle) const {
75  // propagator-related additional types
76  using Actor = detail::SimulationActor<generator_t, decay_t, interactions_t,
77  hit_surface_selector_t>;
78  using Aborter = typename Actor::ParticleNotAlive;
79  using Result = typename Actor::result_type;
80  using Actions = Acts::ActionList<Actor>;
81  using Abort = Acts::AbortList<Aborter, Acts::EndOfWorldReached>;
82  using PropagatorOptions = Acts::PropagatorOptions<Actions, Abort>;
83 
84  // Construct per-call options.
85  PropagatorOptions options(geoCtx, magCtx);
86  // setup the interactor as part of the propagator options
87  auto &actor = options.actionList.template get<Actor>();
88  actor.generator = &generator;
89  actor.decay = decay;
90  actor.interactions = interactions;
91  actor.selectHitSurface = selectHitSurface;
92  actor.initialParticle = particle;
93 
94  if (particle.hasReferenceSurface()) {
95  auto result = propagator.propagate(
96  particle.boundParameters(geoCtx).value(), options);
97  if (not result.ok()) {
98  return result.error();
99  }
100  auto &value = result.value().template get<Result>();
101  return std::move(value);
102  }
103 
104  auto result =
105  propagator.propagate(particle.curvilinearParameters(), options);
106  if (not result.ok()) {
107  return result.error();
108  }
109  auto &value = result.value().template get<Result>();
110  return std::move(value);
111  }
112 };
113 
123  std::error_code error;
124 };
125 
136 template <typename charged_selector_t, typename charged_simulator_t,
137  typename neutral_selector_t, typename neutral_simulator_t>
138 struct Simulation {
139  charged_selector_t selectCharged;
140  neutral_selector_t selectNeutral;
141  charged_simulator_t charged;
142  neutral_simulator_t neutral;
143 
145  Simulation(charged_simulator_t &&charged_, neutral_simulator_t &&neutral_)
146  : charged(std::move(charged_)), neutral(std::move(neutral_)) {}
147 
184  template <typename generator_t, typename input_particles_t,
185  typename output_particles_t, typename hits_t>
188  const Acts::MagneticFieldContext &magCtx, generator_t &generator,
189  const input_particles_t &inputParticles,
190  output_particles_t &simulatedParticlesInitial,
191  output_particles_t &simulatedParticlesFinal, hits_t &hits) const {
192  assert(
193  (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) and
194  "Inconsistent initial sizes of the simulated particle containers");
195 
196  using SingleParticleSimulationResult = Acts::Result<SimulationResult>;
197 
198  std::vector<FailedParticle> failedParticles;
199 
200  for (const Particle &inputParticle : inputParticles) {
201  // only consider simulatable particles
202  if (not selectParticle(inputParticle)) {
203  continue;
204  }
205  // required to allow correct particle id numbering for secondaries later
206  if ((inputParticle.particleId().generation() != 0u) or
207  (inputParticle.particleId().subParticle() != 0u)) {
208  return detail::SimulationError::eInvalidInputParticleId;
209  }
210 
211  // Do a *depth-first* simulation of the particle and its secondaries,
212  // i.e. we simulate all secondaries, tertiaries, ... before simulating
213  // the next primary particle. Use the end of the output container as
214  // a queue to store particles that should be simulated.
215  //
216  // WARNING the initial particle state output container will be modified
217  // during iteration. New secondaries are added to and failed
218  // particles might be removed. To avoid issues, access must always
219  // occur via indices.
220  auto iinitial = simulatedParticlesInitial.size();
221  simulatedParticlesInitial.push_back(inputParticle);
222  for (; iinitial < simulatedParticlesInitial.size(); ++iinitial) {
223  const auto &initialParticle = simulatedParticlesInitial[iinitial];
224 
225  // only simulatable particles are pushed to the container and here we
226  // only need to switch between charged/neutral.
227  SingleParticleSimulationResult result =
228  SingleParticleSimulationResult::success({});
229  if (initialParticle.charge() != Particle::Scalar(0)) {
230  result = charged.simulate(geoCtx, magCtx, generator, initialParticle);
231  } else {
232  result = neutral.simulate(geoCtx, magCtx, generator, initialParticle);
233  }
234 
235  if (not result.ok()) {
236  // remove particle from output container since it was not simulated.
237  simulatedParticlesInitial.erase(
238  std::next(simulatedParticlesInitial.begin(), iinitial));
239  // record the particle as failed
240  failedParticles.push_back({initialParticle, result.error()});
241  continue;
242  }
243 
244  copyOutputs(result.value(), simulatedParticlesInitial,
245  simulatedParticlesFinal, hits);
246  // since physics processes are independent, there can be particle id
247  // collisions within the generated secondaries. they can be resolved by
248  // renumbering within each sub-particle generation. this must happen
249  // before the particle is simulated since the particle id is used to
250  // associate generated hits back to the particle.
251  renumberTailParticleIds(simulatedParticlesInitial, iinitial);
252  }
253  }
254 
255  // the overall function call succeeded, i.e. no fatal errors occured.
256  // yet, there might have been some particle for which the propagation
257  // failed. thus, the successful result contains a list of failed particles.
258  // sounds a bit weird, but that is the way it is.
259  return failedParticles;
260  }
261 
262  private:
264  bool selectParticle(const Particle &particle) const {
265  if (particle.charge() != Particle::Scalar(0)) {
266  return selectCharged(particle);
267  } else {
268  return selectNeutral(particle);
269  }
270  }
271 
276  template <typename particles_t, typename hits_t>
277  void copyOutputs(const SimulationResult &result,
278  particles_t &particlesInitial, particles_t &particlesFinal,
279  hits_t &hits) const {
280  // initial particle state was already pushed to the container before
281  // store final particle state at the end of the simulation
282  particlesFinal.push_back(result.particle);
283  // move generated secondaries that should be simulated to the output
284  std::copy_if(
285  result.generatedParticles.begin(), result.generatedParticles.end(),
286  std::back_inserter(particlesInitial),
287  [this](const Particle &particle) { return selectParticle(particle); });
288  std::copy(result.hits.begin(), result.hits.end(), std::back_inserter(hits));
289  }
290 
305  template <typename particles_t>
306  static void renumberTailParticleIds(particles_t &particles,
307  std::size_t lastValid) {
308  // iterate over adjacent pairs; potentially modify the second element.
309  // assume e.g. a primary particle 2 with generation=subparticle=0 that
310  // generates two secondaries during simulation. we have the following
311  // ids (decoded as vertex|particle|generation|subparticle)
312  //
313  // v|2|0|0, v|2|1|0, v|2|1|0
314  //
315  // where v represents the vertex numbers. this will be renumbered to
316  //
317  // v|2|0|0, v|2|1|0, v|2|1|1
318  //
319  // if each secondary then generates two tertiaries we could have e.g.
320  //
321  // 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
322  //
323  // which would then be renumbered to
324  //
325  // 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
326  //
327  for (auto j = lastValid; (j + 1u) < particles.size(); ++j) {
328  const auto prevId = particles[j].particleId();
329  auto currId = particles[j + 1u].particleId();
330  // NOTE primary/secondary vertex and particle are assumed to be equal
331  // only renumber within one generation
332  if (prevId.generation() != currId.generation()) {
333  continue;
334  }
335  // ensure the sub-particle is strictly monotonic within a generation
336  if (prevId.subParticle() < currId.subParticle()) {
337  continue;
338  }
339  // sub-particle numbering must be non-zero
340  currId.setSubParticle(prevId.subParticle() + 1u);
341  particles[j + 1u] = particles[j + 1u].withParticleId(currId);
342  }
343  }
344 };
345 
346 } // namespace ActsFatras