Back to home page

EIC code displayed by LXR

 
 

    


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