![]() |
|
|||
File indexing completed on 2025-10-18 08:22:02
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/Material/MaterialSlab.hpp" 0012 #include "ActsFatras/EventData/Particle.hpp" 0013 0014 #include <bitset> 0015 #include <tuple> 0016 #include <type_traits> 0017 #include <utility> 0018 0019 namespace ActsFatras { 0020 namespace detail { 0021 0022 /// Retrieve index of the first matching type in the tuple. 0023 /// 0024 /// Taken from https://stackoverflow.com/a/18063608. 0025 template <class T, class Tuple> 0026 struct TupleIndexOf; 0027 template <class T, class... Types> 0028 struct TupleIndexOf<T, std::tuple<T, Types...>> { 0029 static constexpr std::size_t value = 0u; 0030 }; 0031 template <class T, class U, class... Types> 0032 struct TupleIndexOf<T, std::tuple<U, Types...>> { 0033 static constexpr std::size_t value = 0034 1u + TupleIndexOf<T, std::tuple<Types...>>::value; 0035 }; 0036 0037 // Construct an index sequence for a subset of the tuple elements. 0038 // 0039 // Whether an element is part of the subset is defined by the predicate 0040 // template type. It must take the element type as its only template parameter 0041 // and must provide a static `value` member value. If the value evaluates to 0042 // `true`, then the corresponding index will be part of the index sequence. 0043 // 0044 // Example: The tuple contains four elements, where all but the third one (i=2) 0045 // should be selected. This leads to the following recursive expansion 0046 // where the index sequence of the subset is filled from the front. 0047 // 0048 // TupleFilterImpl<..., kCounter=4> // select index=3 0049 // -> TupleFilterImpl<..., kCounter=3, 3> // skip index=2 0050 // -> TupleFilterImpl<..., kCounter=2, 3> // select index=1 0051 // -> TupleFilterImpl<..., kCounter=1, 1, 3> // select index=0 0052 // -> TupleFilterImpl<..., kCounter=0, 0, 1, 3> // terminate 0053 // 0054 template <template <typename> typename predicate_t, typename tuple_t, 0055 std::size_t kCounter, std::size_t... kIndices> 0056 struct TupleFilterImpl { 0057 static constexpr auto kIndex = kCounter - 1u; 0058 static constexpr bool kElementSelection = 0059 predicate_t<std::tuple_element_t<kIndex, tuple_t>>::value; 0060 // recursive type if the element would be selected 0061 using SelectElement = typename TupleFilterImpl<predicate_t, tuple_t, kIndex, 0062 kIndex, kIndices...>::Type; 0063 // recursive type if the element would be skipped 0064 using SkipElement = 0065 typename TupleFilterImpl<predicate_t, tuple_t, kIndex, kIndices...>::Type; 0066 // select recursive type based on the selector decision 0067 using Type = 0068 std::conditional_t<kElementSelection, SelectElement, SkipElement>; 0069 }; 0070 template <template <typename> typename predicate_t, typename tuple_t, 0071 std::size_t... kIndices> 0072 struct TupleFilterImpl<predicate_t, tuple_t, 0u, kIndices...> { 0073 using Type = std::index_sequence<kIndices...>; 0074 }; 0075 template <template <typename> typename predicate_t, typename tuple_t> 0076 using TupleFilter = typename TupleFilterImpl<predicate_t, tuple_t, 0077 std::tuple_size_v<tuple_t>>::Type; 0078 0079 /// Check if the given type is a point-like process. 0080 /// 0081 /// Only checks for the existence of the templated `generatePathLimits` method 0082 template <typename process_t> 0083 concept PointLikeProcessConcept = requires( 0084 const process_t& p, std::uniform_int_distribution<unsigned int>& rng, 0085 const Particle& prt) { 0086 { p.generatePathLimits(rng, prt) } -> std::same_as<std::pair<double, double>>; 0087 }; 0088 0089 template <typename process_t> 0090 concept ContinuousProcessConcept = !PointLikeProcessConcept<process_t>; 0091 0092 template <typename process_t> 0093 struct PointLikeProcessTrait { 0094 static constexpr bool value = PointLikeProcessConcept<process_t>; 0095 }; 0096 0097 template <typename process_t> 0098 struct ContinuousProcessTrait { 0099 static constexpr bool value = ContinuousProcessConcept<process_t>; 0100 }; 0101 0102 template <typename processes_t> 0103 using ContinuousIndices = TupleFilter<ContinuousProcessTrait, processes_t>; 0104 template <typename processes_t> 0105 using PointLikeIndices = TupleFilter<PointLikeProcessTrait, processes_t>; 0106 0107 } // namespace detail 0108 0109 /// Compile-time set of interaction processes for the simulation. 0110 /// 0111 /// Two different type of interaction processes are supported: continuous and 0112 /// point-like interactions. 0113 /// 0114 /// Continuous processes scale with the passed material. They tpyically 0115 /// describe effective results of a large number of small interactions such as 0116 /// multiple scattering or ionisation. Continuous process types **must** provide 0117 /// a call operator with the following signature: 0118 /// 0119 /// template <typename generator_t> 0120 /// bool 0121 /// operator()( 0122 /// generator_t& rng, 0123 /// const Acts::MaterialSlab& slab, 0124 /// Particle& particle, 0125 /// std::vector<Particle>& generatedParticles) const 0126 /// 0127 /// If multiple continuous processes are defined, they are executed serially in 0128 /// the order in which they are given. 0129 /// 0130 /// For point-like processes, the passed material only affects the probability 0131 /// with which they occur but not the interaction itself, e.g. photon conversion 0132 /// into electron pairs. They are simulated by first drawing a limit on the 0133 /// material paths and then executing the interaction with the shortest limit 0134 /// when the drawn amount of material has been passed. Point-like process 0135 /// types **must** provide the following two member functions: 0136 /// 0137 /// // generate X0/L0 limits 0138 /// template <typename generator_t> 0139 /// std::pair<Scalar, Scalar> 0140 /// generatePathLimits( 0141 /// generator& rng, 0142 /// const Particle& particle) const 0143 /// 0144 /// // run the process simulation 0145 /// template <typename generator_t> 0146 /// bool 0147 /// run( 0148 /// generator_t& rng, 0149 /// Particle& particle, 0150 /// std::vector<Particle>& generatedParticles) const 0151 /// 0152 /// For both continuous and point-like interactions, the output particle is 0153 /// modified in-place (if needed) and the return value indicates a break 0154 /// condition in the simulation, i.e. the particle is dead (true) or alive 0155 /// (false) after the interaction. 0156 /// 0157 /// @note If an interaction destroys the incoming particle, the process 0158 /// simulation should indicate this via the break condition only and not 0159 /// by reducing the particle momentum to zero. The incoming particle should 0160 /// retain its initial kinematic state; the final kinematic state before 0161 /// destruction is typically of more interest to the user and this simplifies 0162 /// validation. 0163 /// 0164 /// The physics processes are extendable by the user to accommodate their 0165 /// specific requirements. While the set of available physics processes must be 0166 /// configured at compile-time, within that set, processes can again be 0167 /// selectively disabled at run-time. By default all processes are applied. 0168 template <typename... processes_t> 0169 class InteractionList { 0170 using Mask = std::bitset<sizeof...(processes_t)>; 0171 using Processes = std::tuple<processes_t...>; 0172 using ContinuousIndices = detail::ContinuousIndices<Processes>; 0173 using PointLikeIndices = detail::PointLikeIndices<Processes>; 0174 0175 public: 0176 /// Point-like interaction selection. 0177 struct Selection { 0178 /// X0 (radiation length) limit for interaction selection 0179 double x0Limit = std::numeric_limits<double>::infinity(); 0180 /// L0 (absorption length) limit for interaction selection 0181 double l0Limit = std::numeric_limits<double>::infinity(); 0182 /// X0-based process index for point-like interactions 0183 std::size_t x0Process = std::numeric_limits<std::size_t>::max(); 0184 /// L0-based process index for point-like interactions 0185 std::size_t l0Process = std::numeric_limits<std::size_t>::max(); 0186 }; 0187 0188 /// Disable a specific process identified by index. 0189 /// @param process Index of the process to disable 0190 void disable(std::size_t process) { m_mask.set(process); } 0191 /// Disable a specific process identified by type. 0192 /// 0193 /// @note Disables only the first element, if multiple elements of the same 0194 /// type exist. 0195 template <typename process_t> 0196 void disable() { 0197 m_mask.set(detail::TupleIndexOf<process_t, Processes>::value); 0198 } 0199 0200 /// Access a specific process identified by index. 0201 /// @return Reference to the process at the specified index 0202 template <std::size_t kProcess> 0203 std::tuple_element_t<kProcess, Processes>& get() { 0204 return std::get<kProcess>(m_processes); 0205 } 0206 /// Access a specific process identified by type. 0207 /// 0208 /// @warning This function only works if all configured processes have 0209 /// different types. 0210 /// @return Reference to the process of the specified type 0211 template <typename process_t> 0212 process_t& get() { 0213 return std::get<process_t>(m_processes); 0214 } 0215 0216 /// Simulate the combined effects from all continuous interactions. 0217 /// 0218 /// @tparam generator_t must be a RandomNumberEngine 0219 /// @param[in] rng is the random number generator 0220 /// @param[in] slab is the passed material 0221 /// @param[in,out] particle is the particle being updated 0222 /// @param[out] generated is the container of generated particles 0223 /// @return Break condition, i.e. whether a process stopped the propagation 0224 template <typename generator_t> 0225 bool runContinuous(generator_t& rng, const Acts::MaterialSlab& slab, 0226 Particle& particle, 0227 std::vector<Particle>& generated) const { 0228 return runContinuousImpl(rng, slab, particle, generated, 0229 ContinuousIndices()); 0230 } 0231 0232 /// Arm the point-like interactions by generating limits and select processes. 0233 /// 0234 /// @tparam generator_t must be a RandomNumberEngine 0235 /// @param[in] rng is the random number generator 0236 /// @param[in] particle is the initial particle state 0237 /// @return X0/L0 limits for the particle and the process index that should be 0238 /// executed once the limit has been reached. 0239 template <typename generator_t> 0240 Selection armPointLike(generator_t& rng, const Particle& particle) const { 0241 Selection selection; 0242 armPointLikeImpl(rng, particle, selection, PointLikeIndices()); 0243 return selection; 0244 } 0245 0246 /// Simulate the effects from a single point-like interaction. 0247 /// 0248 /// @tparam generator_t must be a RandomNumberEngine 0249 /// @param[in] rng is the random number generator 0250 /// @param[in] processIndex is the index of the process to be executed 0251 /// @param[in,out] particle is the particle being updated 0252 /// @param[out] generated is the container of generated particles 0253 /// @return Break condition, i.e. whether a process killed the particle 0254 /// 0255 /// The process index is expected to originate from a previous 0256 /// `armPointLike(...)` call, but this is not enforced. How to select the 0257 /// correct process requires more information that is not available here. 0258 template <typename generator_t> 0259 bool runPointLike(generator_t& rng, std::size_t processIndex, 0260 Particle& particle, 0261 std::vector<Particle>& generated) const { 0262 return runPointLikeImpl(rng, processIndex, particle, generated, 0263 PointLikeIndices()); 0264 } 0265 0266 private: 0267 // allow processes to be masked. defaults to zeros -> no masked processes 0268 Mask m_mask; 0269 Processes m_processes; 0270 0271 // for the `runContinuous` call, we need to iterate over all available 0272 // processes and apply the ones that implement the continuous process 0273 // interface. this is done using an index-based compile-time recursive call. 0274 template <typename generator_t, std::size_t kI0, std::size_t... kIs> 0275 bool runContinuousImpl(generator_t& rng, const Acts::MaterialSlab& slab, 0276 Particle& particle, std::vector<Particle>& generated, 0277 std::index_sequence<kI0, kIs...> /*indices*/) const { 0278 const auto& process = std::get<kI0>(m_processes); 0279 // only call process if it is not masked 0280 if (!m_mask[kI0] && process(rng, slab, particle, generated)) { 0281 // exit early in case the process signals an abort 0282 return true; 0283 } 0284 return runContinuousImpl(rng, slab, particle, generated, 0285 std::index_sequence<kIs...>()); 0286 } 0287 template <typename generator_t> 0288 bool runContinuousImpl(generator_t& /*rng*/, 0289 const Acts::MaterialSlab& /*slab*/, 0290 Particle& /*particle*/, 0291 std::vector<Particle>& /*generated*/, 0292 std::index_sequence<> /*indices*/) const { 0293 return false; 0294 } 0295 0296 // for the `armPointLike` call, we need to iterate over all available 0297 // processes and select the ones that generate the smallest limits. this is 0298 // done using an index-based compile-time recursive call. 0299 template <typename generator_t, std::size_t kI0, std::size_t... kIs> 0300 void armPointLikeImpl(generator_t& rng, const Particle& particle, 0301 Selection& selection, 0302 std::index_sequence<kI0, kIs...> /*indices*/) const { 0303 // only arm the process if it is not masked 0304 if (!m_mask[kI0]) { 0305 auto [x0Limit, l0Limit] = 0306 std::get<kI0>(m_processes).generatePathLimits(rng, particle); 0307 if (x0Limit < selection.x0Limit) { 0308 selection.x0Limit = x0Limit; 0309 selection.x0Process = kI0; 0310 } 0311 if (l0Limit < selection.l0Limit) { 0312 selection.l0Limit = l0Limit; 0313 selection.l0Process = kI0; 0314 } 0315 } 0316 // continue with the remaining processes 0317 armPointLikeImpl(rng, particle, selection, std::index_sequence<kIs...>()); 0318 } 0319 template <typename generator_t> 0320 void armPointLikeImpl(generator_t& /*rng*/, const Particle& /*particle*/, 0321 Selection& /*selection*/, 0322 std::index_sequence<> /*indices*/) const {} 0323 0324 // for the `runPointLike` call we need to call just one process. since we can 0325 // not select a tuple element with a runtime index, we need to iterate over 0326 // all processes with a compile-time recursive function until we reach the 0327 // requested one. 0328 template <typename generator_t, std::size_t kI0, std::size_t... kIs> 0329 bool runPointLikeImpl(generator_t& rng, std::size_t processIndex, 0330 Particle& particle, std::vector<Particle>& generated, 0331 std::index_sequence<kI0, kIs...> /*indices*/) const { 0332 if (kI0 == processIndex) { 0333 if (m_mask[kI0]) { 0334 // the selected process is masked. since nothing is executed the 0335 // particle continues to be alive; not a break condition. 0336 return false; 0337 } 0338 return std::get<kI0>(m_processes).run(rng, particle, generated); 0339 } 0340 // continue the iteration with the remaining processes 0341 return runPointLikeImpl(rng, processIndex, particle, generated, 0342 std::index_sequence<kIs...>()); 0343 } 0344 template <typename generator_t> 0345 bool runPointLikeImpl(generator_t& /*rng*/, std::size_t /*processIndex*/, 0346 Particle& /*particle*/, 0347 std::vector<Particle>& /*generated*/, 0348 std::index_sequence<> /*indices*/) const { 0349 // the requested process index is outside the possible range. **do not** 0350 // treat this as an error to simplify the case of an empty physics lists or 0351 // a default process index. 0352 return false; 0353 } 0354 }; 0355 0356 } // 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 |
![]() ![]() |