|
||||
File indexing completed on 2025-01-18 09:12:13
0001 // This file is part of the ACTS project. 0002 // 0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project 0004 // 0005 // This Source Code Form is subject to the terms of the Mozilla Public 0006 // License, v. 2.0. If a copy of the MPL was not distributed with this 0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/. 0008 0009 #pragma once 0010 0011 #include "Acts/Geometry/GeometryContext.hpp" 0012 #include "Acts/MagneticField/MagneticFieldContext.hpp" 0013 #include "Acts/Propagator/ActorList.hpp" 0014 #include "Acts/Utilities/Logger.hpp" 0015 #include "Acts/Utilities/Result.hpp" 0016 #include "ActsFatras/EventData/Particle.hpp" 0017 #include "ActsFatras/Kernel/SimulationResult.hpp" 0018 #include "ActsFatras/Kernel/detail/SimulationActor.hpp" 0019 #include "ActsFatras/Kernel/detail/SimulationError.hpp" 0020 0021 #include <algorithm> 0022 #include <cassert> 0023 #include <iterator> 0024 #include <memory> 0025 #include <vector> 0026 0027 namespace ActsFatras { 0028 0029 /// Single particle simulation with fixed propagator, interactions, and decay. 0030 /// 0031 /// @tparam generator_t random number generator 0032 /// @tparam interactions_t interaction list 0033 /// @tparam hit_surface_selector_t selector for hit surfaces 0034 /// @tparam decay_t decay module 0035 template <typename propagator_t, typename interactions_t, 0036 typename hit_surface_selector_t, typename decay_t> 0037 struct SingleParticleSimulation { 0038 /// How and within which geometry to propagate the particle. 0039 propagator_t propagator; 0040 /// Absolute maximum step size 0041 double maxStepSize = std::numeric_limits<double>::max(); 0042 /// Absolute maximum path length 0043 double pathLimit = std::numeric_limits<double>::max(); 0044 /// Decay module. 0045 decay_t decay; 0046 /// Interaction list containing the simulated interactions. 0047 interactions_t interactions; 0048 /// Selector for surfaces that should generate hits. 0049 hit_surface_selector_t selectHitSurface; 0050 /// Logger for debug output. 0051 std::unique_ptr<const Acts::Logger> logger; 0052 0053 /// Alternatively construct the simulator with an external logger. 0054 SingleParticleSimulation(propagator_t &&propagator_, 0055 std::unique_ptr<const Acts::Logger> _logger) 0056 : propagator(propagator_), logger(std::move(_logger)) {} 0057 0058 /// Simulate a single particle without secondaries. 0059 /// 0060 /// @tparam generator_t is the type of the random number generator 0061 /// 0062 /// @param geoCtx is the geometry context to access surface geometries 0063 /// @param magCtx is the magnetic field context to access field values 0064 /// @param generator is the random number generator 0065 /// @param particle is the initial particle state 0066 /// @returns Simulated particle state, hits, and generated particles. 0067 template <typename generator_t> 0068 Acts::Result<SimulationResult> simulate( 0069 const Acts::GeometryContext &geoCtx, 0070 const Acts::MagneticFieldContext &magCtx, generator_t &generator, 0071 const Particle &particle) const { 0072 // propagator-related additional types 0073 using Actor = detail::SimulationActor<generator_t, decay_t, interactions_t, 0074 hit_surface_selector_t>; 0075 using Result = typename Actor::result_type; 0076 using ActorList = Acts::ActorList<Actor>; 0077 using PropagatorOptions = 0078 typename propagator_t::template Options<ActorList>; 0079 0080 // Construct per-call options. 0081 PropagatorOptions options(geoCtx, magCtx); 0082 options.stepping.maxStepSize = maxStepSize; 0083 options.pathLimit = pathLimit; 0084 // setup the interactor as part of the propagator options 0085 auto &actor = options.actorList.template get<Actor>(); 0086 actor.generator = &generator; 0087 actor.decay = decay; 0088 actor.interactions = interactions; 0089 actor.selectHitSurface = selectHitSurface; 0090 actor.initialParticle = particle; 0091 0092 if (particle.hasReferenceSurface()) { 0093 auto result = propagator.propagate( 0094 particle.boundParameters(geoCtx).value(), options); 0095 if (!result.ok()) { 0096 return result.error(); 0097 } 0098 auto &value = result.value().template get<Result>(); 0099 return std::move(value); 0100 } 0101 0102 auto result = 0103 propagator.propagate(particle.curvilinearParameters(), options); 0104 if (!result.ok()) { 0105 return result.error(); 0106 } 0107 auto &value = result.value().template get<Result>(); 0108 return std::move(value); 0109 } 0110 }; 0111 0112 /// A particle that failed to simulate. 0113 struct FailedParticle { 0114 /// Initial particle state of the failed particle. 0115 /// 0116 /// This must store the full particle state to be able to handle secondaries 0117 /// that are not in the input particle list. Otherwise they could not be 0118 /// referenced. 0119 Particle particle; 0120 /// The associated error code for this particular failure case. 0121 std::error_code error; 0122 }; 0123 0124 /// Multi-particle/event simulation. 0125 /// 0126 /// @tparam charged_selector_t Callable selector type for charged particles 0127 /// @tparam charged_simulator_t Single particle simulator for charged particles 0128 /// @tparam neutral_selector_t Callable selector type for neutral particles 0129 /// @tparam neutral_simulator_t Single particle simulator for neutral particles 0130 /// 0131 /// The selector types for charged and neutral particles **do not** need to 0132 /// check for the particle charge. This is done automatically by the simulator 0133 /// to ensure consistency. 0134 template <typename charged_selector_t, typename charged_simulator_t, 0135 typename neutral_selector_t, typename neutral_simulator_t> 0136 struct Simulation { 0137 charged_selector_t selectCharged; 0138 neutral_selector_t selectNeutral; 0139 charged_simulator_t charged; 0140 neutral_simulator_t neutral; 0141 0142 /// Construct from the single charged/neutral particle simulators. 0143 Simulation(charged_simulator_t &&charged_, neutral_simulator_t &&neutral_) 0144 : charged(std::move(charged_)), neutral(std::move(neutral_)) {} 0145 0146 /// Simulate multiple particles and generated secondaries. 0147 /// 0148 /// @param geoCtx is the geometry context to access surface geometries 0149 /// @param magCtx is the magnetic field context to access field values 0150 /// @param generator is the random number generator 0151 /// @param inputParticles contains all particles that should be simulated 0152 /// @param simulatedParticlesInitial contains initial particle states 0153 /// @param simulatedParticlesFinal contains final particle states 0154 /// @param hits contains all generated hits 0155 /// @retval Acts::Result::Error if there is a fundamental issue 0156 /// @retval Acts::Result::Success with all particles that failed to simulate 0157 /// 0158 /// @warning Particle-hit association is based on particle ids generated 0159 /// during the simulation. This requires that all input particles 0160 /// **must** have generation and sub-particle number set to zero. 0161 /// @note Parameter edge-cases can lead to errors in the underlying propagator 0162 /// and thus to particles that fail to simulate. Here, full events are 0163 /// simulated and the failure to simulate one particle should not be 0164 /// considered a general failure of the simulator. Instead, a list of 0165 /// particles that fail to simulate is provided to the user. It is the 0166 /// users responsibility to handle them. 0167 /// @note Failed particles are removed from the regular output, i.e. they do 0168 /// not appear in the simulated particles containers nor do they 0169 /// generate hits. 0170 /// 0171 /// This takes all input particles and simulates those passing the selection 0172 /// using the appropriate simulator. All selected particle states including 0173 /// additional ones generated from interactions are stored in separate output 0174 /// containers; both the initial state at the production vertex and the final 0175 /// state after propagation are stored. Hits generated from selected input and 0176 /// generated particles are stored in the hit container. 0177 /// 0178 /// @tparam generator_t is the type of the random number generator 0179 /// @tparam input_particles_t is a Container for particles 0180 /// @tparam output_particles_t is a SequenceContainer for particles 0181 /// @tparam hits_t is a SequenceContainer for hits 0182 template <typename generator_t, typename input_particles_t, 0183 typename output_particles_t, typename hits_t> 0184 Acts::Result<std::vector<FailedParticle>> simulate( 0185 const Acts::GeometryContext &geoCtx, 0186 const Acts::MagneticFieldContext &magCtx, generator_t &generator, 0187 const input_particles_t &inputParticles, 0188 output_particles_t &simulatedParticlesInitial, 0189 output_particles_t &simulatedParticlesFinal, hits_t &hits) const { 0190 assert( 0191 (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) && 0192 "Inconsistent initial sizes of the simulated particle containers"); 0193 0194 using SingleParticleSimulationResult = Acts::Result<SimulationResult>; 0195 0196 std::vector<FailedParticle> failedParticles; 0197 0198 for (const Particle &inputParticle : inputParticles) { 0199 // only consider simulatable particles 0200 if (!selectParticle(inputParticle)) { 0201 continue; 0202 } 0203 // required to allow correct particle id numbering for secondaries later 0204 if ((inputParticle.particleId().generation() != 0u) || 0205 (inputParticle.particleId().subParticle() != 0u)) { 0206 return detail::SimulationError::eInvalidInputParticleId; 0207 } 0208 0209 // Do a *depth-first* simulation of the particle and its secondaries, 0210 // i.e. we simulate all secondaries, tertiaries, ... before simulating 0211 // the next primary particle. Use the end of the output container as 0212 // a queue to store particles that should be simulated. 0213 // 0214 // WARNING the initial particle state output container will be modified 0215 // during iteration. New secondaries are added to and failed 0216 // particles might be removed. To avoid issues, access must always 0217 // occur via indices. 0218 auto iinitial = simulatedParticlesInitial.size(); 0219 simulatedParticlesInitial.push_back(inputParticle); 0220 for (; iinitial < simulatedParticlesInitial.size(); ++iinitial) { 0221 const auto &initialParticle = simulatedParticlesInitial[iinitial]; 0222 0223 // only simulatable particles are pushed to the container and here we 0224 // only need to switch between charged/neutral. 0225 SingleParticleSimulationResult result = 0226 SingleParticleSimulationResult::success({}); 0227 if (initialParticle.charge() != 0.) { 0228 result = charged.simulate(geoCtx, magCtx, generator, initialParticle); 0229 } else { 0230 result = neutral.simulate(geoCtx, magCtx, generator, initialParticle); 0231 } 0232 0233 if (!result.ok()) { 0234 // remove particle from output container since it was not simulated. 0235 simulatedParticlesInitial.erase( 0236 std::next(simulatedParticlesInitial.begin(), iinitial)); 0237 // record the particle as failed 0238 failedParticles.push_back({initialParticle, result.error()}); 0239 continue; 0240 } 0241 0242 assert(result->particle.particleId() == initialParticle.particleId() && 0243 "Particle id must not change during simulation"); 0244 0245 copyOutputs(result.value(), simulatedParticlesInitial, 0246 simulatedParticlesFinal, hits); 0247 // since physics processes are independent, there can be particle id 0248 // collisions within the generated secondaries. they can be resolved by 0249 // renumbering within each sub-particle generation. this must happen 0250 // before the particle is simulated since the particle id is used to 0251 // associate generated hits back to the particle. 0252 renumberTailParticleIds(simulatedParticlesInitial, iinitial); 0253 } 0254 } 0255 0256 assert( 0257 (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) && 0258 "Inconsistent final sizes of the simulated particle containers"); 0259 0260 // the overall function call succeeded, i.e. no fatal errors occurred. 0261 // yet, there might have been some particle for which the propagation 0262 // failed. thus, the successful result contains a list of failed particles. 0263 // sounds a bit weird, but that is the way it is. 0264 return failedParticles; 0265 } 0266 0267 private: 0268 /// Select if the particle should be simulated at all. 0269 bool selectParticle(const Particle &particle) const { 0270 if (particle.charge() != 0.) { 0271 return selectCharged(particle); 0272 } else { 0273 return selectNeutral(particle); 0274 } 0275 } 0276 0277 /// Copy results to output containers. 0278 /// 0279 /// @tparam particles_t is a SequenceContainer for particles 0280 /// @tparam hits_t is a SequenceContainer for hits 0281 template <typename particles_t, typename hits_t> 0282 void copyOutputs(const SimulationResult &result, 0283 particles_t &particlesInitial, particles_t &particlesFinal, 0284 hits_t &hits) const { 0285 // initial particle state was already pushed to the container before 0286 // store final particle state at the end of the simulation 0287 particlesFinal.push_back(result.particle); 0288 std::copy(result.hits.begin(), result.hits.end(), std::back_inserter(hits)); 0289 0290 // move generated secondaries that should be simulated to the output 0291 std::copy_if( 0292 result.generatedParticles.begin(), result.generatedParticles.end(), 0293 std::back_inserter(particlesInitial), 0294 [this](const Particle &particle) { return selectParticle(particle); }); 0295 } 0296 0297 /// Renumber particle ids in the tail of the container. 0298 /// 0299 /// Ensures particle ids are unique by modifying the sub-particle number 0300 /// within each generation. 0301 /// 0302 /// @param particles particle container in which particles are renumbered 0303 /// @param lastValid index of the last particle with a valid particle id 0304 /// 0305 /// @tparam particles_t is a SequenceContainer for particles 0306 /// 0307 /// @note This function assumes that all particles in the tail have the same 0308 /// vertex numbers (primary/secondary) and particle number and are 0309 /// ordered according to their generation number. 0310 /// 0311 template <typename particles_t> 0312 static void renumberTailParticleIds(particles_t &particles, 0313 std::size_t lastValid) { 0314 // iterate over adjacent pairs; potentially modify the second element. 0315 // assume e.g. a primary particle 2 with generation=subparticle=0 that 0316 // generates two secondaries during simulation. we have the following 0317 // ids (decoded as vertex|particle|generation|subparticle) 0318 // 0319 // v|2|0|0, v|2|1|0, v|2|1|0 0320 // 0321 // where v represents the vertex numbers. this will be renumbered to 0322 // 0323 // v|2|0|0, v|2|1|0, v|2|1|1 0324 // 0325 // if each secondary then generates two tertiaries we could have e.g. 0326 // 0327 // 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 0328 // 0329 // which would then be renumbered to 0330 // 0331 // 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 0332 // 0333 for (auto j = lastValid; (j + 1u) < particles.size(); ++j) { 0334 const auto prevId = particles[j].particleId(); 0335 auto currId = particles[j + 1u].particleId(); 0336 // NOTE primary/secondary vertex and particle are assumed to be equal 0337 // only renumber within one generation 0338 if (prevId.generation() != currId.generation()) { 0339 continue; 0340 } 0341 // ensure the sub-particle is strictly monotonic within a generation 0342 if (prevId.subParticle() < currId.subParticle()) { 0343 continue; 0344 } 0345 // sub-particle numbering must be non-zero 0346 currId.setSubParticle(prevId.subParticle() + 1u); 0347 particles[j + 1u] = particles[j + 1u].withParticleId(currId); 0348 } 0349 } 0350 }; 0351 0352 } // namespace ActsFatras
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |