Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-23 07:48:32

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   /// Identifier type for primary vertex
0101   using PrimaryVertexId = std::uint16_t;
0102   /// Identifier type for secondary vertex
0103   using SecondaryVertexId = std::uint16_t;
0104   /// Identifier type for particle
0105   using ParticleId = std::uint32_t;
0106   /// Identifier type for generation
0107   using GenerationId = std::uint8_t;
0108   /// Identifier type for sub-particle
0109   using SubParticleId = std::uint32_t;
0110 
0111   /// Construct an invalid barcode with all levels set to zero.
0112   /// @return An invalid barcode
0113   static constexpr Barcode Invalid() { return Barcode(); }
0114 
0115   /// Empty barcode
0116   constexpr Barcode() = default;
0117   /// Copy constructor
0118   constexpr Barcode(const Barcode&) = default;
0119   /// Move constructor
0120   constexpr Barcode(Barcode&&) = default;
0121   /// Copy assignment operator
0122   /// @return Reference to this barcode after copying
0123   Barcode& operator=(const Barcode&) = default;
0124   /// Move assignment operator
0125   /// @return Reference to this barcode after moving
0126   Barcode& operator=(Barcode&&) = default;
0127 
0128   ///  Compare two barcodes
0129   /// @return True if barcodes are equal
0130   bool operator==(const Barcode&) const = default;
0131   friend constexpr auto operator<=>(Barcode lhs, Barcode rhs) {
0132     return lhs.asVector() <=> rhs.asVector();
0133   }
0134 
0135   /// Check validity of the barcode
0136   /// @param b The barcode to check
0137   /// @return True if barcode is valid
0138   static constexpr bool isValid(const Barcode& b) { return b != Invalid(); }
0139   /// Check if this barcode is valid
0140   /// @return True if this barcode is valid
0141   constexpr bool isValid() const { return isValid(*this); }
0142 
0143   /// Return the primary vertex identifier.
0144   /// @return The primary vertex identifier value
0145   constexpr PrimaryVertexId vertexPrimary() const { return vertexPrimaryID; }
0146 
0147   /// Return the secondary vertex identifier.
0148   /// @return The secondary vertex identifier value
0149   constexpr SecondaryVertexId vertexSecondary() const {
0150     return vertexSecondaryID;
0151   }
0152 
0153   /// Return the particle identifier.
0154   /// @return The particle identifier value
0155   constexpr ParticleId particle() const { return particleID; }
0156 
0157   /// Return the generation identifier.
0158   /// @return The generation identifier value
0159   constexpr GenerationId generation() const { return generationID; }
0160 
0161   /// Return the sub-particle identifier.
0162   /// @return The sub-particle identifier value
0163   constexpr SubParticleId subParticle() const { return subParticleID; }
0164 
0165   /// Export barcode as vector
0166   /// @return Vector of barcode components
0167   constexpr std::vector<std::uint32_t> asVector() const {
0168     return {vertexPrimary(), vertexSecondary(), particle(), generation(),
0169             subParticle()};
0170   }
0171 
0172   /// Create a new barcode with a different primary vertex identifier.
0173   /// @param id Primary vertex identifier to set
0174   /// @return New barcode with modified primary vertex identifier
0175   [[nodiscard]]
0176   constexpr Barcode withVertexPrimary(PrimaryVertexId id) const {
0177     Barcode barcode = *this;
0178     barcode.vertexPrimaryID = id;
0179     return barcode;
0180   }
0181 
0182   /// Create a new barcode with a different secondary vertex identifier.
0183   /// @param id Secondary vertex identifier to set
0184   /// @return New barcode with modified secondary vertex identifier
0185   [[nodiscard]]
0186   constexpr Barcode withVertexSecondary(SecondaryVertexId id) const {
0187     Barcode barcode = *this;
0188     barcode.vertexSecondaryID = id;
0189     return barcode;
0190   }
0191 
0192   /// Create a new barcode with a different particle identifier.
0193   /// @param id Particle identifier to set
0194   /// @return New barcode with modified particle identifier
0195   [[nodiscard]]
0196   constexpr Barcode withParticle(ParticleId id) const {
0197     Barcode barcode = *this;
0198     barcode.particleID = id;
0199     return barcode;
0200   }
0201 
0202   /// Create a new barcode with a different generation identifier.
0203   /// @param id Generation identifier to set
0204   /// @return New barcode with modified generation identifier
0205   [[nodiscard]]
0206   constexpr Barcode withGeneration(GenerationId id) const {
0207     Barcode barcode = *this;
0208     barcode.generationID = id;
0209     return barcode;
0210   }
0211 
0212   /// Create a new barcode with a different sub-particle identifier.
0213   /// @param id Sub-particle identifier to set
0214   /// @return New barcode with modified sub-particle identifier
0215   [[nodiscard]]
0216   constexpr Barcode withSubParticle(SubParticleId id) const {
0217     Barcode barcode = *this;
0218     barcode.subParticleID = id;
0219     return barcode;
0220   }
0221 
0222   /// Create a new barcode from a vector
0223   /// @param data Vector containing exactly 5 elements
0224   /// @return New barcode with data from the vector
0225   [[nodiscard]]
0226   constexpr Barcode withData(std::span<std::uint32_t> data) {
0227     if (data.size() != 5) {
0228       throw std::invalid_argument(
0229           "Size of the data is " + std::to_string(data.size()) +
0230           " but Barcode requires data vector to have exactly 5 elements");
0231     }
0232 
0233     Barcode barcode = *this;
0234     barcode.vertexPrimaryID = data[0];
0235     barcode.vertexSecondaryID = data[1];
0236     barcode.particleID = data[2];
0237     barcode.generationID = data[3];
0238     barcode.subParticleID = data[4];
0239     return barcode;
0240   }
0241 
0242   /// Construct a new barcode representing a descendant particle.
0243   ///
0244   /// @param sub sub-particle index of the new barcode.
0245   /// @return New barcode with increased generation and specified sub-particle index
0246   Barcode makeDescendant(SubParticleId sub = 0u) const {
0247     Barcode barcode = *this;
0248     barcode.generationID += 1;
0249     barcode.subParticleID = sub;
0250     return barcode;
0251   }
0252 
0253   /// Reduce the barcode to the vertex identifier.
0254   /// @return Barcode containing only vertex and generation information
0255   constexpr Barcode vertexId() const {
0256     // The vertex is identified by primary vertex, secondary vertex, and
0257     // generation. The other components are set to 0 so two particle originating
0258     // from the same vertex will have the same vertex ID.
0259     Barcode barcode = *this;
0260     barcode.particleID = 0u;
0261     barcode.subParticleID = 0u;
0262     return barcode;
0263   }
0264 
0265   /// Reduce the barcode to the particle identifier.
0266   /// @return Barcode with subparticle identifier set to zero
0267   constexpr Barcode withoutSubparticle() const {
0268     // Provide a pseudo-barcode that contains all fields but not the
0269     // subparticle counter. This can be used as key in a map to store the
0270     // subparticle information
0271     Barcode barcode = *this;
0272     barcode.subParticleID = 0u;
0273     return barcode;
0274   }
0275 
0276   /// Print the barcode
0277   friend inline std::ostream& operator<<(std::ostream& os, Barcode barcode) {
0278     // extra "+" to ensure printing as a number and not as a character
0279     os << "vp=" << +barcode.vertexPrimary()
0280        << "|vs=" << +barcode.vertexSecondary() << "|p=" << +barcode.particle()
0281        << "|g=" << +barcode.generation() << "|sp=" << +barcode.subParticle();
0282     return os;
0283   }
0284 
0285   /// Get hash of the barcode
0286   /// @return Hash value of the barcode
0287   std::size_t hash() const {
0288     std::size_t seed = 0;
0289     boost::hash_combine(seed, vertexPrimary());
0290     boost::hash_combine(seed, vertexSecondary());
0291     boost::hash_combine(seed, particle());
0292     boost::hash_combine(seed, generation());
0293     boost::hash_combine(seed, subParticle());
0294 
0295     return seed;
0296   }
0297 
0298  private:
0299   PrimaryVertexId vertexPrimaryID = 0u;
0300   SecondaryVertexId vertexSecondaryID = 0u;
0301   ParticleId particleID = 0u;
0302   GenerationId generationID = 0u;
0303   SubParticleId subParticleID = 0u;
0304 };
0305 
0306 }  // namespace ActsFatras
0307 
0308 // specialize std::hash so Barcode can be used e.g. in an unordered_map
0309 namespace std {
0310 template <>
0311 struct hash<ActsFatras::Barcode> {
0312   auto operator()(ActsFatras::Barcode barcode) const noexcept {
0313     return barcode.hash();
0314   }
0315 };
0316 }  // namespace std