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