Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
FatrasSimulation.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file FatrasSimulation.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2017-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 
37 
38 #include <algorithm>
39 #include <array>
40 #include <map>
41 #include <ostream>
42 #include <stdexcept>
43 #include <system_error>
44 #include <utility>
45 #include <vector>
46 
47 #include <boost/version.hpp>
48 
49 namespace {
50 
52 struct HitSurfaceSelector {
53  bool sensitive = false;
54  bool material = false;
55  bool passive = false;
56 
58  bool operator()(const Acts::Surface &surface) const {
59  // sensitive/material are not mutually exclusive
60  bool isSensitive = surface.associatedDetectorElement() != nullptr;
61  bool isMaterial = surface.surfaceMaterial() != nullptr;
62  // passive should be an orthogonal category
63  bool isPassive = not(isSensitive or isMaterial);
64  return (isSensitive and sensitive) or (isMaterial and material) or
65  (isPassive and passive);
66  }
67 };
68 
69 } // namespace
70 
71 // Same interface as `ActsFatras::Simulation` but with concrete types.
73  virtual ~FatrasSimulation() = default;
77  ActsExamples::SimParticleContainer::sequence_type &,
78  ActsExamples::SimParticleContainer::sequence_type &,
79  ActsExamples::SimHitContainer::sequence_type &) const = 0;
80 };
81 
82 namespace {
83 
84 // Magnetic-field specific PIMPL implementation.
85 //
86 // This always uses the EigenStepper with default extensions for charged
87 // particle propagation and is thus limited to propagation in vacuum at the
88 // moment.
89 // @TODO: Remove this, unneeded after #675
90 struct FatrasSimulationT final : ActsExamples::detail::FatrasSimulation {
92 
93  // typedefs for charge particle simulation
94  // propagate charged particles numerically in the given magnetic field
95  using ChargedStepper = Acts::EigenStepper<>;
96  using ChargedPropagator = Acts::Propagator<ChargedStepper, Acts::Navigator>;
97  // charged particles w/ standard em physics list and selectable hits
98  using ChargedSelector = CutPMin;
99  using ChargedSimulation = ActsFatras::SingleParticleSimulation<
101  HitSurfaceSelector, ActsFatras::NoDecay>;
102 
103  // typedefs for neutral particle simulation
104  // propagate neutral particles with just straight lines
105  using NeutralStepper = Acts::StraightLineStepper;
106  using NeutralPropagator = Acts::Propagator<NeutralStepper, Acts::Navigator>;
107  // neutral particles w/ photon conversion and no hits
108  using NeutralSelector = CutPMin;
109  using NeutralInteractions =
111  using NeutralSimulation = ActsFatras::SingleParticleSimulation<
112  NeutralPropagator, NeutralInteractions, ActsFatras::NoSurface,
114 
115  // combined simulation type
116  using Simulation = ActsFatras::Simulation<ChargedSelector, ChargedSimulation,
117  NeutralSelector, NeutralSimulation>;
118 
119  Simulation simulation;
120 
121  FatrasSimulationT(const ActsExamples::FatrasSimulation::Config &cfg,
123  : simulation(
124  ChargedSimulation(
125  ChargedPropagator(ChargedStepper(cfg.magneticField),
126  Acts::Navigator{{cfg.trackingGeometry}}),
127  Acts::getDefaultLogger("Simulation", lvl)),
128  NeutralSimulation(
129  NeutralPropagator(NeutralStepper(),
130  Acts::Navigator{{cfg.trackingGeometry}}),
131  Acts::getDefaultLogger("Simulation", lvl))) {
132  using namespace ActsFatras;
133  using namespace ActsFatras::detail;
134  // apply the configuration
135 
136  // minimal p cut on input particles and as is-alive check for interactions
137  simulation.selectCharged.valMin = cfg.pMin;
138  simulation.selectNeutral.valMin = cfg.pMin;
139  simulation.charged.interactions =
141 
142  // processes are enabled by default
143  if (not cfg.emScattering) {
144  simulation.charged.interactions.template disable<StandardScattering>();
145  }
146  if (not cfg.emEnergyLossIonisation) {
147  simulation.charged.interactions.template disable<StandardBetheBloch>();
148  }
149  if (not cfg.emEnergyLossRadiation) {
150  simulation.charged.interactions.template disable<StandardBetheHeitler>();
151  }
152  if (not cfg.emPhotonConversion) {
153  simulation.neutral.interactions.template disable<PhotonConversion>();
154  }
155 
156  // configure hit surfaces for charged particles
157  simulation.charged.selectHitSurface.sensitive = cfg.generateHitsOnSensitive;
158  simulation.charged.selectHitSurface.material = cfg.generateHitsOnMaterial;
159  simulation.charged.selectHitSurface.passive = cfg.generateHitsOnPassive;
160  }
161  ~FatrasSimulationT() final = default;
162 
163  Acts::Result<std::vector<ActsFatras::FailedParticle>> simulate(
164  const Acts::GeometryContext &geoCtx,
165  const Acts::MagneticFieldContext &magCtx, ActsExamples::RandomEngine &rng,
166  const ActsExamples::SimParticleContainer &inputParticles,
167  ActsExamples::SimParticleContainer::sequence_type
168  &simulatedParticlesInitial,
169  ActsExamples::SimParticleContainer::sequence_type
170  &simulatedParticlesFinal,
171  ActsExamples::SimHitContainer::sequence_type &simHits) const final {
172  return simulation.simulate(geoCtx, magCtx, rng, inputParticles,
173  simulatedParticlesInitial,
174  simulatedParticlesFinal, simHits);
175  }
176 };
177 
178 } // namespace
179 
182  : ActsExamples::IAlgorithm("FatrasSimulation", lvl), m_cfg(std::move(cfg)) {
183  ACTS_DEBUG("hits on sensitive surfaces: " << m_cfg.generateHitsOnSensitive);
184  ACTS_DEBUG("hits on material surfaces: " << m_cfg.generateHitsOnMaterial);
185  ACTS_DEBUG("hits on passive surfaces: " << m_cfg.generateHitsOnPassive);
186 
189  ACTS_WARNING("FatrasSimulation not configured to generate any hits!");
190  }
191 
192  if (!m_cfg.trackingGeometry) {
193  throw std::invalid_argument{"Missing tracking geometry"};
194  }
195  if (!m_cfg.magneticField) {
196  throw std::invalid_argument{"Missing magnetic field"};
197  }
198  if (!m_cfg.randomNumbers) {
199  throw std::invalid_argument("Missing random numbers tool");
200  }
201 
202  // construct the simulation for the specific magnetic field
203  m_sim = std::make_unique<FatrasSimulationT>(m_cfg, lvl);
204 
205  m_inputParticles.initialize(m_cfg.inputParticles);
206  m_outputParticlesInitial.initialize(m_cfg.outputParticlesInitial);
207  m_outputParticlesFinal.initialize(m_cfg.outputParticlesFinal);
208  m_outputSimHits.initialize(m_cfg.outputSimHits);
209 }
210 
211 // explicit destructor needed for the PIMPL implementation to work
213 
215  const AlgorithmContext &ctx) const {
216  // read input containers
217  const auto &inputParticles = m_inputParticles(ctx);
218 
219  ACTS_DEBUG(inputParticles.size() << " input particles");
220 
221  // prepare output containers
222  SimParticleContainer::sequence_type particlesInitialUnordered;
223  SimParticleContainer::sequence_type particlesFinalUnordered;
224  SimHitContainer::sequence_type simHitsUnordered;
225  // reserve appropriate resources
226  particlesInitialUnordered.reserve(inputParticles.size());
227  particlesFinalUnordered.reserve(inputParticles.size());
228  simHitsUnordered.reserve(inputParticles.size() *
229  m_cfg.averageHitsPerParticle);
230 
231  // run the simulation w/ a local random generator
232  auto rng = m_cfg.randomNumbers->spawnGenerator(ctx);
233  auto ret = m_sim->simulate(ctx.geoContext, ctx.magFieldContext, rng,
234  inputParticles, particlesInitialUnordered,
235  particlesFinalUnordered, simHitsUnordered);
236  // fatal error leads to panic
237  if (not ret.ok()) {
238  ACTS_FATAL("event " << ctx.eventNumber << " simulation failed with error "
239  << ret.error());
240  return ProcessCode::ABORT;
241  }
242  // failed particles are just logged. assumes that failed particles are due
243  // to edge-cases representing a tiny fraction of the event; not due to a
244  // fundamental issue.
245  for (const auto &failed : ret.value()) {
246  ACTS_ERROR("event " << ctx.eventNumber << " particle " << failed.particle
247  << " failed to simulate with error " << failed.error
248  << ": " << failed.error.message());
249  }
250 
251  ACTS_DEBUG(particlesInitialUnordered.size()
252  << " simulated particles (initial state)");
253  ACTS_DEBUG(particlesFinalUnordered.size()
254  << " simulated particles (final state)");
255  ACTS_DEBUG(simHitsUnordered.size() << " simulated hits");
256 
257  // order output containers
258 #if BOOST_VERSION >= 107800
259  SimParticleContainer particlesInitial(particlesInitialUnordered.begin(),
260  particlesInitialUnordered.end());
261  SimParticleContainer particlesFinal(particlesFinalUnordered.begin(),
262  particlesFinalUnordered.end());
263  SimHitContainer simHits(simHitsUnordered.begin(), simHitsUnordered.end());
264 #else
265  // working around a nasty boost bug
266  // https://github.com/boostorg/container/issues/244
267 
268  SimParticleContainer particlesInitial;
269  SimParticleContainer particlesFinal;
270  SimHitContainer simHits;
271 
272  particlesInitial.reserve(particlesInitialUnordered.size());
273  particlesFinal.reserve(particlesFinalUnordered.size());
274  simHits.reserve(simHitsUnordered.size());
275 
276  for (const auto &p : particlesInitialUnordered) {
277  particlesInitial.insert(p);
278  }
279  for (const auto &p : particlesFinalUnordered) {
280  particlesFinal.insert(p);
281  }
282  for (const auto &h : simHitsUnordered) {
283  simHits.insert(h);
284  }
285 #endif
286 
287  // store ordered output containers
288  m_outputParticlesInitial(ctx, std::move(particlesInitial));
289  m_outputParticlesFinal(ctx, std::move(particlesFinal));
290  m_outputSimHits(ctx, std::move(simHits));
291 
293 }