|
|
|||
File indexing completed on 2026-04-28 07:12:10
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 "ActsFatras/EventData/Particle.hpp" 0014 #include "ActsFatras/Kernel/SingleParticleSimulationResult.hpp" 0015 #include "ActsFatras/Kernel/detail/SimulationError.hpp" 0016 0017 #include <algorithm> 0018 #include <cassert> 0019 #include <iterator> 0020 0021 namespace ActsFatras { 0022 0023 /// A particle that failed to simulate. 0024 struct FailedParticle { 0025 /// Initial particle state of the failed particle. 0026 /// 0027 /// This must store the full particle state to be able to handle secondaries 0028 /// that are not in the input particle list. Otherwise they could not be 0029 /// referenced. 0030 Particle particle; 0031 /// The associated error code for this particular failure case. 0032 std::error_code error; 0033 }; 0034 0035 /// Multi-particle/event simulation. 0036 /// 0037 /// @tparam charged_selector_t Callable selector type for charged particles 0038 /// @tparam charged_simulator_t Single particle simulator for charged particles 0039 /// @tparam neutral_selector_t Callable selector type for neutral particles 0040 /// @tparam neutral_simulator_t Single particle simulator for neutral particles 0041 /// 0042 /// The selector types for charged and neutral particles **do not** need to 0043 /// check for the particle charge. This is done automatically by the simulator 0044 /// to ensure consistency. 0045 template <typename charged_selector_t, typename charged_simulator_t, 0046 typename neutral_selector_t, typename neutral_simulator_t> 0047 struct MultiParticleSimulation { 0048 /// Selector for charged particle simulation 0049 charged_selector_t selectCharged; 0050 /// Selector for neutral particle simulation 0051 neutral_selector_t selectNeutral; 0052 /// Simulator for charged particles 0053 charged_simulator_t charged; 0054 /// Simulator for neutral particles 0055 neutral_simulator_t neutral; 0056 0057 /// Construct from the single charged/neutral particle simulators. 0058 /// @param charged_ Simulator for charged particles 0059 /// @param neutral_ Simulator for neutral particles 0060 MultiParticleSimulation(charged_simulator_t &&charged_, 0061 neutral_simulator_t &&neutral_) 0062 : charged(std::move(charged_)), neutral(std::move(neutral_)) {} 0063 0064 /// Simulate multiple particles and generated secondaries. 0065 /// 0066 /// @param geoCtx is the geometry context to access surface geometries 0067 /// @param magCtx is the magnetic field context to access field values 0068 /// @param generator is the random number generator 0069 /// @param inputParticles contains all particles that should be simulated 0070 /// @param simulatedParticlesInitial contains initial particle states 0071 /// @param simulatedParticlesFinal contains final particle states 0072 /// @param hits contains all generated hits 0073 /// @retval Acts::Result::Error if there is a fundamental issue 0074 /// @retval Acts::Result::Success with all particles that failed to simulate 0075 /// 0076 /// @warning Particle-hit association is based on particle ids generated 0077 /// during the simulation. This requires that all input particles 0078 /// **must** have generation and sub-particle number set to zero. 0079 /// @note Parameter edge-cases can lead to errors in the underlying propagator 0080 /// and thus to particles that fail to simulate. Here, full events are 0081 /// simulated and the failure to simulate one particle should not be 0082 /// considered a general failure of the simulator. Instead, a list of 0083 /// particles that fail to simulate is provided to the user. It is the 0084 /// users responsibility to handle them. 0085 /// @note Failed particles are removed from the regular output, i.e. they do 0086 /// not appear in the simulated particles containers nor do they 0087 /// generate hits. 0088 /// 0089 /// This takes all input particles and simulates those passing the selection 0090 /// using the appropriate simulator. All selected particle states including 0091 /// additional ones generated from interactions are stored in separate output 0092 /// containers; both the initial state at the production vertex and the final 0093 /// state after propagation are stored. Hits generated from selected input and 0094 /// generated particles are stored in the hit container. 0095 /// 0096 /// @tparam generator_t is the type of the random number generator 0097 /// @tparam input_particles_t is a Container for particles 0098 /// @tparam output_particles_t is a SequenceContainer for particles 0099 /// @tparam hits_t is a SequenceContainer for hits 0100 template <typename generator_t, typename input_particles_t, 0101 typename output_particles_t, typename hits_t> 0102 Acts::Result<std::vector<FailedParticle>> simulate( 0103 const Acts::GeometryContext &geoCtx, 0104 const Acts::MagneticFieldContext &magCtx, generator_t &generator, 0105 const input_particles_t &inputParticles, 0106 output_particles_t &simulatedParticlesInitial, 0107 output_particles_t &simulatedParticlesFinal, hits_t &hits) const { 0108 assert( 0109 (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) && 0110 "Inconsistent initial sizes of the simulated particle containers"); 0111 0112 std::vector<FailedParticle> failedParticles; 0113 0114 for (const Particle &inputParticle : inputParticles) { 0115 // only consider simulatable particles 0116 if (!selectParticle(inputParticle)) { 0117 continue; 0118 } 0119 // required to allow correct particle id numbering for secondaries later 0120 if ((inputParticle.particleId().generation() != 0u) || 0121 (inputParticle.particleId().subParticle() != 0u)) { 0122 return detail::SimulationError::InvalidInputParticleId; 0123 } 0124 0125 // Do a *depth-first* simulation of the particle and its secondaries, 0126 // i.e. we simulate all secondaries, tertiaries, ... before simulating 0127 // the next primary particle. Use the end of the output container as 0128 // a queue to store particles that should be simulated. 0129 // 0130 // WARNING the initial particle state output container will be modified 0131 // during iteration. New secondaries are added to and failed 0132 // particles might be removed. To avoid issues, access must always 0133 // occur via indices. 0134 std::size_t iinitial = simulatedParticlesInitial.size(); 0135 simulatedParticlesInitial.push_back(inputParticle); 0136 while (iinitial < simulatedParticlesInitial.size()) { 0137 const auto &initialParticle = simulatedParticlesInitial[iinitial]; 0138 0139 // only simulatable particles are pushed to the container and here we 0140 // only need to switch between charged/neutral. 0141 auto result = Acts::Result<SingleParticleSimulationResult>::success({}); 0142 if (initialParticle.charge() != 0.) { 0143 result = charged.simulate(geoCtx, magCtx, generator, initialParticle); 0144 } else { 0145 result = neutral.simulate(geoCtx, magCtx, generator, initialParticle); 0146 } 0147 0148 if (!result.ok()) { 0149 // remove particle from output container since it was not simulated. 0150 simulatedParticlesInitial.erase( 0151 std::next(simulatedParticlesInitial.begin(), iinitial)); 0152 // record the particle as failed 0153 failedParticles.push_back({initialParticle, result.error()}); 0154 continue; 0155 } 0156 0157 assert(result->particle.particleId() == initialParticle.particleId() && 0158 "Particle id must not change during simulation"); 0159 0160 copyOutputs(result.value(), simulatedParticlesInitial, 0161 simulatedParticlesFinal, hits); 0162 // since physics processes are independent, there can be particle id 0163 // collisions within the generated secondaries. they can be resolved by 0164 // renumbering within each sub-particle generation. this must happen 0165 // before the particle is simulated since the particle id is used to 0166 // associate generated hits back to the particle. 0167 renumberTailParticleIds(simulatedParticlesInitial, iinitial); 0168 0169 ++iinitial; 0170 } 0171 } 0172 0173 assert( 0174 (simulatedParticlesInitial.size() == simulatedParticlesFinal.size()) && 0175 "Inconsistent final sizes of the simulated particle containers"); 0176 0177 // the overall function call succeeded, i.e. no fatal errors occurred. 0178 // yet, there might have been some particle for which the propagation 0179 // failed. thus, the successful result contains a list of failed particles. 0180 // sounds a bit weird, but that is the way it is. 0181 return failedParticles; 0182 } 0183 0184 private: 0185 /// Select if the particle should be simulated at all. 0186 bool selectParticle(const Particle &particle) const { 0187 if (particle.charge() != 0.) { 0188 return selectCharged(particle); 0189 } else { 0190 return selectNeutral(particle); 0191 } 0192 } 0193 0194 /// Copy results to output containers. 0195 /// 0196 /// @tparam particles_t is a SequenceContainer for particles 0197 /// @tparam hits_t is a SequenceContainer for hits 0198 template <typename particles_t, typename hits_t> 0199 void copyOutputs(const SingleParticleSimulationResult &result, 0200 particles_t &particlesInitial, particles_t &particlesFinal, 0201 hits_t &hits) const { 0202 // initial particle state was already pushed to the container before 0203 // store final particle state at the end of the simulation 0204 particlesFinal.push_back(result.particle); 0205 std::copy(result.hits.begin(), result.hits.end(), std::back_inserter(hits)); 0206 0207 // move generated secondaries that should be simulated to the output 0208 std::copy_if( 0209 result.generatedParticles.begin(), result.generatedParticles.end(), 0210 std::back_inserter(particlesInitial), 0211 [this](const Particle &particle) { return selectParticle(particle); }); 0212 } 0213 0214 /// Renumber particle ids in the tail of the container. 0215 /// 0216 /// Ensures particle ids are unique by modifying the sub-particle number 0217 /// within each generation. 0218 /// 0219 /// @param particles particle container in which particles are renumbered 0220 /// @param lastValid index of the last particle with a valid particle id 0221 /// 0222 /// @tparam particles_t is a SequenceContainer for particles 0223 /// 0224 /// @note This function assumes that all particles in the tail have the same 0225 /// vertex numbers (primary/secondary) and particle number and are 0226 /// ordered according to their generation number. 0227 /// 0228 template <typename particles_t> 0229 static void renumberTailParticleIds(particles_t &particles, 0230 std::size_t lastValid) { 0231 // iterate over adjacent pairs; potentially modify the second element. 0232 // assume e.g. a primary particle 2 with generation=subparticle=0 that 0233 // generates two secondaries during simulation. we have the following 0234 // ids (decoded as vertex|particle|generation|subparticle) 0235 // 0236 // v|2|0|0, v|2|1|0, v|2|1|0 0237 // 0238 // where v represents the vertex numbers. this will be renumbered to 0239 // 0240 // v|2|0|0, v|2|1|0, v|2|1|1 0241 // 0242 // if each secondary then generates two tertiaries we could have e.g. 0243 // 0244 // 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 0245 // 0246 // which would then be renumbered to 0247 // 0248 // 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 0249 // 0250 for (auto j = lastValid; (j + 1u) < particles.size(); ++j) { 0251 const auto prevId = particles[j].particleId(); 0252 auto currId = particles[j + 1u].particleId(); 0253 // NOTE primary/secondary vertex and particle are assumed to be equal 0254 // only renumber within one generation 0255 if (prevId.generation() != currId.generation()) { 0256 continue; 0257 } 0258 // ensure the sub-particle is strictly monotonic within a generation 0259 if (prevId.subParticle() < currId.subParticle()) { 0260 continue; 0261 } 0262 // sub-particle numbering must be non-zero 0263 currId = currId.withSubParticle(prevId.subParticle() + 1u); 0264 particles[j + 1u] = particles[j + 1u].withParticleId(currId); 0265 } 0266 } 0267 }; 0268 0269 } // 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 |
|