Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:24:12

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 <cstdint>
0012 #include <span>
0013 #include <stdexcept>
0014 #include <string>
0015 #include <vector>
0016 
0017 #include <boost/functional/hash.hpp>
0018 
0019 namespace ActsFatras {
0020 
0021 /// Particle identifier that encodes additional event information.
0022 ///
0023 /// The barcode has to fulfill two separate requirements: be able to act as
0024 /// unique identifier for particles within an event and to encode details
0025 /// on the event structure for fast lookup. Since we only care about tracking
0026 /// here, we need to support two scenarios:
0027 ///
0028 /// *   Identify which primary/secondary vertex particles belong to. No
0029 ///     information on intermediate/unstable/invisible particles needs to be
0030 ///     retained. This information is already available in the underlying
0031 ///     generator event and should not be duplicated.
0032 /// *   If (visible) particles convert, decay, or interact with the detector,
0033 ///     we need to be able to identify the initial (primary) particle. Typical
0034 ///     examples are pion nuclear interactions or electron/gamma conversions.
0035 ///
0036 /// The vertex information is encoded as two numbers that define the
0037 /// primary and secondary vertex. The primary vertex must be non-zero.
0038 /// Particles with a zero secondary vertex originate directly from the primary
0039 /// vertex.
0040 ///
0041 /// Within one vertex (primary+secondary) each particle is identified by
0042 /// a particle, generation, and sub-particle number. Particles originating
0043 /// from the vertex must have zero generation and zero sub-particle number;
0044 /// a consequence is that only non-zero generation can have non-zero
0045 /// sub-particle numbers. A non-zero generation indicates that the particle
0046 /// is a descendant of the original particle, e.g. from interactions or decay,
0047 /// while the sub-particle number identifies the descendant particle.
0048 ///
0049 /// With this encoding, non-primary particles and their primary parent can
0050 /// be easily identified at the expense of not storing the exact decay history.
0051 ///
0052 /// A barcode with all elements set to zero (the default value) is an invalid
0053 /// value that can be used e.g. to mark missing or unknown particles.
0054 ///
0055 /// ## Example
0056 ///
0057 /// A particle generated in a primary interaction might have the barcode
0058 ///
0059 ///     2|0|14|0|0 -> vertex=2 (primary), particle=14, generation=0, sub=0
0060 ///
0061 /// A simulation module might generate an interaction and create two new
0062 /// particles. These are descendants of the initial particle and the simulation
0063 /// module can generate the new barcodes directly by increasing the
0064 /// generation number and choosing sub-particle identifiers:
0065 ///
0066 ///     2|0|14|1|0 -> vertex=2 (primary), particle=14, generation=1, sub=0
0067 ///     2|0|14|1|1 -> vertex=2 (primary), particle=14, generation=1, sub=1
0068 ///
0069 /// If these secondary particles generate further tertiary particles
0070 /// the barcode would be e.g.
0071 ///
0072 ///     2|0|14|2|0 -> vertex=2 (primary), particle=14, generation=2, sub=0
0073 ///
0074 /// ## Possible issues
0075 ///
0076 /// The hierarchical nature of the barcode allows barcode creation without
0077 /// a central service. Since the full history is not stored, generated barcodes
0078 /// for higher-generation particles can overlap when generated by independent
0079 /// interactions. Assuming an initial primary particle with barcode
0080 ///
0081 ///     3|4|5|0|0 -> particle=5
0082 ///
0083 /// a first interaction might create a secondary particle by increasing the
0084 /// generation number (without destroying the initial particle)
0085 ///
0086 ///     3|4|5|1|0 -> particle=5, generation+=1, first sub-particle
0087 ///
0088 /// The initial particle gets simulated further and at another step a second
0089 /// interaction also creates a new particle. Since it knows nothing about
0090 /// the previously created particle (no central service), it will generate
0091 ///
0092 ///     3|4|5|1|0 -> particle=5, generation+=1, first sub-particle
0093 ///
0094 /// which is identical to the previously create barcode. These cases can be
0095 /// easily solved by renumbering the sub-particle identifier within each
0096 /// generation to contain unique values. However, this can only be done when all
0097 /// particles are known.
0098 class Barcode {
0099  public:
0100   using PrimaryVertexId = std::uint16_t;
0101   using SecondaryVertexId = std::uint16_t;
0102   using ParticleId = std::uint32_t;
0103   using GenerationId = std::uint8_t;
0104   using SubParticleId = std::uint32_t;
0105 
0106   // Construct an invalid barcode with all levels set to zero.
0107   static constexpr Barcode Invalid() { return Barcode(); }
0108 
0109   /// Empty barcode
0110   constexpr Barcode() = default;
0111   /// Copy constructor
0112   constexpr Barcode(const Barcode&) = default;
0113   /// Move constructor
0114   constexpr Barcode(Barcode&&) = default;
0115   /// Copy assignment operator
0116   /// @return Reference to this barcode after copying
0117   Barcode& operator=(const Barcode&) = default;
0118   /// Move assignment operator
0119   /// @return Reference to this barcode after moving
0120   Barcode& operator=(Barcode&&) = default;
0121 
0122   ///  Compare two barcodes
0123   bool operator==(const Barcode&) const = default;
0124   friend constexpr auto operator<=>(Barcode lhs, Barcode rhs) {
0125     return lhs.asVector() <=> rhs.asVector();
0126   }
0127 
0128   /// Check validity of the barcode
0129   static constexpr bool isValid(const Barcode& b) { return b != Invalid(); }
0130   constexpr bool isValid() const { return isValid(*this); }
0131 
0132   /// Return the primary vertex identifier.
0133   /// @return The primary vertex identifier value
0134   constexpr PrimaryVertexId vertexPrimary() const { return vertexPrimaryID; }
0135 
0136   /// Return the secondary vertex identifier.
0137   /// @return The secondary vertex identifier value
0138   constexpr SecondaryVertexId vertexSecondary() const {
0139     return vertexSecondaryID;
0140   }
0141 
0142   /// Return the particle identifier.
0143   /// @return The particle identifier value
0144   constexpr ParticleId particle() const { return particleID; }
0145 
0146   /// Return the generation identifier.
0147   /// @return The generation identifier value
0148   constexpr GenerationId generation() const { return generationID; }
0149 
0150   /// Return the sub-particle identifier.
0151   /// @return The sub-particle identifier value
0152   constexpr SubParticleId subParticle() const { return subParticleID; }
0153 
0154   /// Export barcode as vector
0155   constexpr std::vector<std::uint32_t> asVector() const {
0156     return {vertexPrimary(), vertexSecondary(), particle(), generation(),
0157             subParticle()};
0158   }
0159 
0160   /// Create a new barcode with a different primary vertex identifier.
0161   /// @param id Primary vertex identifier to set
0162   /// @return New barcode with modified primary vertex identifier
0163   [[nodiscard]]
0164   constexpr Barcode withVertexPrimary(PrimaryVertexId id) const {
0165     Barcode barcode = *this;
0166     barcode.vertexPrimaryID = id;
0167     return barcode;
0168   }
0169 
0170   /// Create a new barcode with a different secondary vertex identifier.
0171   /// @param id Secondary vertex identifier to set
0172   /// @return New barcode with modified secondary vertex identifier
0173   [[nodiscard]]
0174   constexpr Barcode withVertexSecondary(SecondaryVertexId id) const {
0175     Barcode barcode = *this;
0176     barcode.vertexSecondaryID = id;
0177     return barcode;
0178   }
0179 
0180   /// Create a new barcode with a different particle identifier.
0181   /// @param id Particle identifier to set
0182   /// @return New barcode with modified particle identifier
0183   [[nodiscard]]
0184   constexpr Barcode withParticle(ParticleId id) const {
0185     Barcode barcode = *this;
0186     barcode.particleID = id;
0187     return barcode;
0188   }
0189 
0190   /// Create a new barcode with a different generation identifier.
0191   /// @param id Generation identifier to set
0192   /// @return New barcode with modified generation identifier
0193   [[nodiscard]]
0194   constexpr Barcode withGeneration(GenerationId id) const {
0195     Barcode barcode = *this;
0196     barcode.generationID = id;
0197     return barcode;
0198   }
0199 
0200   /// Create a new barcode with a different sub-particle identifier.
0201   /// @param id Sub-particle identifier to set
0202   /// @return New barcode with modified sub-particle identifier
0203   [[nodiscard]]
0204   constexpr Barcode withSubParticle(SubParticleId id) const {
0205     Barcode barcode = *this;
0206     barcode.subParticleID = id;
0207     return barcode;
0208   }
0209 
0210   /// Create a new barcode from a vector
0211   /// @param data Vector containing exactly 5 elements
0212   /// @return New barcode with data from the vector
0213   [[nodiscard]]
0214   constexpr Barcode withData(std::span<std::uint32_t> data) {
0215     if (data.size() != 5) {
0216       throw std::invalid_argument(
0217           "Size of the data is " + std::to_string(data.size()) +
0218           " but Barcode requires data vector to have exactly 5 elements");
0219     }
0220 
0221     Barcode barcode = *this;
0222     barcode.vertexPrimaryID = data[0];
0223     barcode.vertexSecondaryID = data[1];
0224     barcode.particleID = data[2];
0225     barcode.generationID = data[3];
0226     barcode.subParticleID = data[4];
0227     return barcode;
0228   }
0229 
0230   /// Construct a new barcode representing a descendant particle.
0231   ///
0232   /// @param sub sub-particle index of the new barcode.
0233   /// @return New barcode with increased generation and specified sub-particle index
0234   Barcode makeDescendant(SubParticleId sub = 0u) const {
0235     Barcode barcode = *this;
0236     barcode.generationID += 1;
0237     barcode.subParticleID = sub;
0238     return barcode;
0239   }
0240 
0241   /// Reduce the barcode to the vertex identifier.
0242   /// @return Barcode containing only vertex and generation information
0243   constexpr Barcode vertexId() const {
0244     // The vertex is identified by primary vertex, secondary vertex, and
0245     // generation. The other components are set to 0 so two particle originating
0246     // from the same vertex will have the same vertex ID.
0247     Barcode barcode = *this;
0248     barcode.particleID = 0u;
0249     barcode.subParticleID = 0u;
0250     return barcode;
0251   }
0252 
0253   /// Reduce the barcode to the particle identifier.
0254   constexpr Barcode withoutSubparticle() const {
0255     // Provide a pseudo-barcode that contains all fields but not the
0256     // subparticle counter. This can be used as key in a map to store the
0257     // subparticle information
0258     Barcode barcode = *this;
0259     barcode.subParticleID = 0u;
0260     return barcode;
0261   }
0262 
0263   /// Print the barcode
0264   friend inline std::ostream& operator<<(std::ostream& os, Barcode barcode) {
0265     // extra "+" to ensure printing as a number and not as a character
0266     os << "vp=" << +barcode.vertexPrimary()
0267        << "|vs=" << +barcode.vertexSecondary() << "|p=" << +barcode.particle()
0268        << "|g=" << +barcode.generation() << "|sp=" << +barcode.subParticle();
0269     return os;
0270   }
0271 
0272   /// Get hash of the barcode
0273   std::size_t hash() const {
0274     std::size_t seed = 0;
0275     boost::hash_combine(seed, vertexPrimary());
0276     boost::hash_combine(seed, vertexSecondary());
0277     boost::hash_combine(seed, particle());
0278     boost::hash_combine(seed, generation());
0279     boost::hash_combine(seed, subParticle());
0280 
0281     return seed;
0282   }
0283 
0284  private:
0285   PrimaryVertexId vertexPrimaryID = 0u;
0286   SecondaryVertexId vertexSecondaryID = 0u;
0287   ParticleId particleID = 0u;
0288   GenerationId generationID = 0u;
0289   SubParticleId subParticleID = 0u;
0290 };
0291 
0292 }  // namespace ActsFatras
0293 
0294 // specialize std::hash so Barcode can be used e.g. in an unordered_map
0295 namespace std {
0296 template <>
0297 struct hash<ActsFatras::Barcode> {
0298   auto operator()(ActsFatras::Barcode barcode) const noexcept {
0299     return barcode.hash();
0300   }
0301 };
0302 }  // namespace std