Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:23: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 #include "Acts/Definitions/ParticleData.hpp"
0010 #include "Acts/Definitions/PdgParticle.hpp"
0011 #include "Acts/Utilities/MathHelpers.hpp"
0012 
0013 #include <algorithm>
0014 #include <array>
0015 #include <cmath>
0016 #include <ostream>
0017 #include <ranges>
0018 #include <utility>
0019 #include <vector>
0020 
0021 // This file is based on the following files, with modifications:
0022 //
0023 // https://gitlab.cern.ch/atlas/athena/-/blob/b0898f93585c4eec97550a9e0a16f5b6e6b6b973/Generators/TruthUtils/TruthUtils/AtlasPID.h
0024 //
0025 // These files are licensed under Apache License 2.0
0026 
0027 namespace Acts {
0028 namespace {
0029 
0030 class DecodedPID : public std::pair<int, std::vector<int>> {
0031  public:
0032   explicit DecodedPID(const int& p) {
0033     this->first = p;
0034     this->second.reserve(10);
0035     int ap = std::abs(p);
0036     for (; ap != 0; ap /= 10) {
0037       this->second.push_back(ap % 10);
0038     }
0039     std::ranges::reverse(this->second);
0040   }
0041   inline DecodedPID shift(const std::size_t n) const {
0042     return DecodedPID(this->first %
0043                       static_cast<int>(std::pow(10, ndigits() - n)));
0044   }
0045   inline const int& operator()(const std::size_t n) const {
0046     return this->second.at(n);
0047   }
0048   inline const int& last() const { return this->second.back(); }
0049   inline const int& pid() const { return this->first; }
0050   inline int max_digit(const int m, const int n) const {
0051     return *std::max_element(second.rbegin() + m, second.rbegin() + n);
0052   }
0053   inline int min_digit(const int m, const int n) const {
0054     return *std::min_element(second.rbegin() + m, second.rbegin() + n);
0055   }
0056   inline std::size_t ndigits() const { return this->second.size(); }
0057 };
0058 
0059 const int TABLESIZE = 100;
0060 const std::array<int, TABLESIZE> triple_charge = {
0061     +0, -1, +2, -1, +2, -1, +2, -1, +2, +0, +0, -3, +0, -3, +0, -3, +0,
0062     -3, +0, +0, +0, +0, +0, +0, +3, +0, +0, +0, +0, +0, +0, +0, +0, +0,
0063     +3, +0, +0, +3, +6, +0, +0, +0, -1, +0, +0, +0, +0, +0, +0, +0, +0,
0064     +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
0065     +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
0066     +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0};
0067 
0068 const int DQUARK = 1;
0069 const int UQUARK = 2;
0070 const int SQUARK = 3;
0071 const int CQUARK = 4;
0072 const int BQUARK = 5;
0073 const int TQUARK = 6;
0074 
0075 const int ELECTRON = 11;
0076 const int MUON = 13;
0077 const int TAU = 15;
0078 
0079 const int GLUON = 21;
0080 // APID: 9 rather than 21 is used to denote a gluon/gluino in composite states.
0081 // (From PDG 11g)
0082 const int COMPOSITEGLUON = 9;
0083 const int PHOTON = 22;
0084 const int Z0BOSON = 23;
0085 const int ZPRIME = 32;     // Z′/Z^0_2
0086 const int ZDBLPRIME = 33;  // Z′′/Z^0_3
0087 const int LEPTOQUARK = 42;
0088 
0089 /// PDG Ids for Mavtop madgraph UFO model found under DarkX. The
0090 /// mavtop is a vector-like top partner with coupling to a dark photon.
0091 /// Theory paper: https://arxiv.org/abs/1904.05893
0092 /// Pheno paper: https://arxiv.org/pdf/2112.08425
0093 const int MAVTOP = 60001;
0094 
0095 const int K0L = 130;
0096 
0097 const int K0S = 310;
0098 const int K0 = 311;
0099 const int PROTON = 2212;
0100 
0101 /// PDG rule 10:
0102 /// Codes 81–100 are reserved for generator-specific pseudoparticles and
0103 /// concepts. Codes 901–930, 1901–1930, 2901–2930, and 3901–3930 are for
0104 /// additional components of Standard Modelparton distribution functions, where
0105 /// the latter three ranges are intended to distinguish left/right/ longitudinal
0106 /// components. Codes 998 and 999 are reserved for GEANT tracking pur-poses.
0107 const int GEANTINOPLUS = 998;
0108 const int GEANTINO0 = 999;
0109 
0110 /// PDG rule 2:
0111 /// Quarks and leptons are numbered consecutively starting from 1 and 11
0112 /// respectively; to dothis they are first ordered by family and within
0113 /// families by weak isospin.
0114 /// APID: the fourth generation quarks are quarks.
0115 bool isQuark(int p) {
0116   return p != 0 && (std::abs(p) <= 8 || std::abs(p) == MAVTOP);
0117 }
0118 
0119 bool isSMQuark(int p) {
0120   return p != 0 && std::abs(p) <= TQUARK;
0121 }
0122 
0123 /// PDG rule 4
0124 /// Diquarks have 4-digit numbers with nq1 >= nq2 and nq3 = 0
0125 /// APID: the diquarks with fourth generation are not diquarks
0126 bool isDiquark(const DecodedPID& p) {
0127   if (p.ndigits() == 4 && p(0) >= p(1) && p(2) == 0 && p.last() % 2 == 1 &&
0128       p.max_digit(2, 4) <= TQUARK) {
0129     return true;
0130   }
0131   return false;
0132 }
0133 
0134 /// Table 43.1
0135 ///  PDG rule 5a:
0136 ///  The numbers specifying the meson's quark content conform to the convention
0137 ///  nq1= 0 and nq2 >= nq3. The special case K0L is the sole exception to this
0138 ///  rule. PDG rule 5C: The special numbers 310 and 130 are given to the K0S and
0139 ///  K0L respectively. APID: The special code K0 is used when a generator uses
0140 ///  K0S/K0L
0141 bool isMeson(const DecodedPID& p) {
0142   if (p.ndigits() < 3) {
0143     return false;
0144   }
0145   if (p.ndigits() == 7 && (p(0) == 1 || p(0) == 2)) {
0146     return false;  // APID don't match SUSY particles
0147   }
0148   if (std::abs(p.pid()) == K0S) {
0149     return true;
0150   }
0151   if (std::abs(p.pid()) == K0L) {
0152     return true;
0153   }
0154   if (std::abs(p.pid()) == K0) {
0155     return true;
0156   }
0157   if (p.last() % 2 != 1) {
0158     return false;
0159   }
0160   if (p.max_digit(1, 3) >= 6) {
0161     return false;
0162   }
0163   if (p.max_digit(1, 3) == 0) {
0164     return false;
0165   }
0166   if (p.ndigits() > 3 && *(p.second.rbegin() + 3) != 0) {
0167     return false;
0168   }
0169 
0170   if (p.ndigits() == 3 && p(0) == p(1) && p.pid() < 0) {
0171     return false;
0172   }
0173   if (p.ndigits() == 5 && p(2) == p(3) && p.pid() < 0) {
0174     return false;
0175   }
0176   if (p.ndigits() == 7 && p(4) == p(5) && p.pid() < 0) {
0177     return false;
0178   }
0179 
0180   if (p.ndigits() == 3 && p(0) >= p(1) && p(1) != 0) {
0181     return true;
0182   }
0183   if (p.ndigits() == 5 && p(2) >= p(3) && p(3) != 0 && p(0) == 1 && p(1) == 0) {
0184     return true;
0185   }
0186   if (p.ndigits() == 5 && p(2) >= p(3) && p(3) != 0 && p(0) == 2 && p(1) == 0 &&
0187       p.last() > 1) {
0188     return true;
0189   }
0190   if (p.ndigits() == 5 && p(2) >= p(3) && p(3) != 0 && p(0) == 3 && p(1) == 0 &&
0191       p.last() > 1) {
0192     return true;
0193   }
0194 
0195   if (p.ndigits() == 6 && p(3) >= p(4) && p(4) != 0 && p.last() % 2 == 1) {
0196     return true;
0197   }
0198 
0199   if (p.ndigits() == 7 && p(0) == 9 && p(1) == 0 && p(4) >= p(5) && p(5) != 0) {
0200     return true;
0201   }
0202 
0203   return false;
0204 }
0205 
0206 /// Table 43.2
0207 bool isBaryon(const DecodedPID& p) {
0208   if (p.ndigits() < 4) {
0209     return false;
0210   }
0211   if (p.max_digit(1, 4) >= 6) {
0212     return false;
0213   }
0214   if (p.min_digit(1, 4) == 0) {
0215     return false;
0216   }
0217   if (p.ndigits() == 4 &&
0218       (p.last() == 2 || p.last() == 4 || p.last() == 6 || p.last() == 8)) {
0219     return true;
0220   }
0221 
0222   if (p.ndigits() == 5 && p(0) == 1 && (p.last() == 2 || p.last() == 4)) {
0223     return true;
0224   }
0225   if (p.ndigits() == 5 && p(0) == 3 && (p.last() == 2 || p.last() == 4)) {
0226     return true;
0227   }
0228 
0229   if (p.ndigits() == 6) {
0230     if (p(0) == 1 && p(1) == 0 && p.last() == 2) {
0231       return true;
0232     }
0233     if (p(0) == 1 && p(1) == 1 && p.last() == 2) {
0234       return true;
0235     }
0236     if (p(0) == 1 && p(1) == 2 && p.last() == 4) {
0237       return true;
0238     }
0239 
0240     if (p(0) == 2 && p(1) == 0 && p.last() == 2) {
0241       return true;
0242     }
0243     if (p(0) == 2 && p(1) == 0 && p.last() == 4) {
0244       return true;
0245     }
0246     if (p(0) == 2 && p(1) == 1 && p.last() == 2) {
0247       return true;
0248     }
0249 
0250     if (p(0) == 1 && p(1) == 0 && p.last() == 4) {
0251       return true;
0252     }
0253     if (p(0) == 1 && p(1) == 0 && p.last() == 6) {
0254       return true;
0255     }
0256     if (p(0) == 2 && p(1) == 0 && p.last() == 6) {
0257       return true;
0258     }
0259     if (p(0) == 2 && p(1) == 0 && p.last() == 8) {
0260       return true;
0261     }
0262   }
0263 
0264   if (p.ndigits() == 5) {
0265     if (p(0) == 2 && p.last() == 2) {
0266       return true;
0267     }
0268     if (p(0) == 2 && p.last() == 4) {
0269       return true;
0270     }
0271     if (p(0) == 2 && p.last() == 6) {
0272       return true;
0273     }
0274     if (p(0) == 5 && p.last() == 2) {
0275       return true;
0276     }
0277     if (p(0) == 1 && p.last() == 6) {
0278       return true;
0279     }
0280     if (p(0) == 4 && p.last() == 2) {
0281       return true;
0282     }
0283   }
0284   return false;
0285 }
0286 
0287 /// PDG rule 14
0288 /// The 9-digit tetra-quark codes are±1nrnLnq1nq20nq3nq4nJ. For the
0289 /// particleq1q2is a diquarkand
0290 /// ̄q3 ̄q4an antidiquark, sorted such thatnq1≥nq2,nq3≥nq4,nq1≥nq3,
0291 /// andnq2≥nq4ifnq1=nq3.
0292 /// For the antiparticle, given with a negative sign, ̄q1 ̄q2is an antidiquark
0293 /// andq3q4a diquark,
0294 /// with the same sorting except that eithernq1> nq3ornq2> nq4(so
0295 /// thatflavour-diagonal states are particles). Thenr,nL, andnJnumbers have the
0296 /// same meaningas for ordinary hadrons.
0297 bool isTetraquark(const DecodedPID& p) {
0298   return (p.ndigits() == 9 && p(0) == 1 && p(5) == 0 &&
0299           p.max_digit(1, 3) <= 6 && p.min_digit(1, 3) > 0 &&
0300           p.max_digit(1 + 3, 3 + 3) <= 6 && p.min_digit(1 + 3, 3 + 3) > 0 &&
0301           (p(3) >= p(4) && p(6) >= p(7)) &&
0302           ((p(3) > p(6)) || (p(3) == p(6) && (p(4) >= p(7)))));
0303 }
0304 
0305 /// PDG rule 15
0306 /// The 9-digit penta-quark codes are±1nrnLnq1nq2nq3nq4nq5nJ, sorted such
0307 /// thatnq1≥nq2≥nq3≥nq4. In the particle the first four are quarks and the fifth
0308 /// an antiquark while t
0309 /// heopposite holds in the antiparticle, which is given with a negative sign.
0310 /// Thenr,nL, andnJnumbers have the same meaning as for ordinary hadrons.
0311 bool isPentaquark(const DecodedPID& p) {
0312   return (p.ndigits() == 9 && p(0) == 1 && p.max_digit(1, 6) <= 6 &&
0313           p.min_digit(1, 6) > 0 &&
0314           (p(3) >= p(4) && p(4) >= p(5) && p(5) >= p(6)));
0315 }
0316 
0317 // APID Mesons, Baryons, Tetraquarks and Pentaquarks are Hadrons
0318 bool isHadron(const DecodedPID& p) {
0319   return isMeson(p) || isBaryon(p) || isTetraquark(p) || isPentaquark(p);
0320 }
0321 
0322 /// PDG rule 9:
0323 /// Two-digit numbers in the range 21–30 are provided for the Standard
0324 /// Model gauge and Higgs bosons.
0325 /// PDG rule 11b:
0326 /// The graviton and the boson content of a two-Higgs-doublet scenario
0327 /// and of additional SU(2)×U(1) groups are found in the range 31–40.
0328 
0329 bool isGluon(int p) {
0330   return p == GLUON;
0331 }
0332 
0333 bool isPhoton(int p) {
0334   return p == PHOTON;
0335 }
0336 
0337 bool isZ(int p) {
0338   return p == Z0BOSON;
0339 }
0340 
0341 /// PDG rule 11c:
0342 /// "One-of-a-kind" exotic particles are assigned numbers in the range
0343 /// 41–80. The subrange 61-80 can be used for new heavier fermions in
0344 /// generic models, where partners to the SM fermions would have codes
0345 /// offset by 60. If required, however, other assignments could be
0346 /// made.
0347 bool isLeptoQuark(int p) {
0348   return std::abs(p) == LEPTOQUARK;
0349 }
0350 
0351 /// Main Table
0352 /// for MC internal use 81–100,901–930,998-999,1901–1930,2901–2930, and
0353 /// 3901–3930
0354 bool isGenSpecific(int p) {
0355   if (p >= 81 && p <= 100) {
0356     return true;
0357   }
0358   if (p >= 901 && p <= 930) {
0359     return true;
0360   }
0361   if (p >= 998 && p <= 999) {
0362     return true;
0363   }
0364   if (p >= 1901 && p <= 1930) {
0365     return true;
0366   }
0367   if (p >= 2901 && p <= 2930) {
0368     return true;
0369   }
0370   if (p >= 3901 && p <= 3930) {
0371     return true;
0372   }
0373   return false;
0374 }
0375 
0376 bool isGeantino(int p) {
0377   return (std::abs(p) == GEANTINO0 || std::abs(p) == GEANTINOPLUS);
0378 }
0379 
0380 /// APID: Definition of Glueballs: SM glueballs 99X (X=1,5), 999Y (Y=3,7)
0381 bool isGlueball(const DecodedPID& p) {
0382   if (p.ndigits() > 4) {
0383     return false;  // APID avoid classifying R-Glueballs as SM Glueballs
0384   }
0385   return ((p.ndigits() == 3 && p(0) == COMPOSITEGLUON &&
0386            p(1) == COMPOSITEGLUON && (p(2) == 1 || p(2) == 5)) ||
0387           (p.ndigits() == 4 && p(0) == COMPOSITEGLUON &&
0388            p(1) == COMPOSITEGLUON && p(2) == COMPOSITEGLUON &&
0389            (p(3) == 3 || p(3) == 7)));
0390 }
0391 
0392 /// PDG rule 11d
0393 /// Fundamental supersymmetric particles are identified by adding a nonzero n to
0394 /// the particle number. The superpartner of a boson or a left-handed fermion
0395 /// has n = 1 while the superpartner of a right-handed fermion has n = 2. When
0396 /// mixing occurs, such as between the winos and charged Higgsinos to give
0397 /// charginos, or between left and right sfermions, the lighter physical state
0398 /// is given the smaller basis state number.
0399 bool isSUSY(const DecodedPID& p) {
0400   return (p.ndigits() == 7 && (p(0) == 1 || p(0) == 2) &&
0401           !isGenSpecific(p.shift(2).pid()));
0402 }
0403 
0404 /// PDG rule 11g:
0405 /// Within several scenarios of new physics, it is possible to have colored
0406 /// particles sufficiently long-lived for color-singlet hadronic states to form
0407 /// around them. In the context of supersymmetric scenarios, these states are
0408 /// called R-hadrons, since they carry odd R- parity. R-hadron codes, defined
0409 /// here, should be viewed as templates for corresponding codes also in other
0410 /// scenarios, for any long-lived particle that is either an unflavored color
0411 /// octet or a flavored color triplet. The R-hadron code is obtained by combining
0412 /// the SUSY particle code with a code for the light degrees of freedom, with as
0413 /// many intermediate zeros removed from the former as required to make place
0414 /// for the latter at the end. (To exemplify, a sparticle n00000n˜q combined
0415 /// with quarks q1 and q2 obtains code n00n˜qnq1 nq2 nJ .) Specifically, the
0416 /// new-particle spin decouples in the limit of large masses, so that the final
0417 /// nJ digit is defined by the spin state of the light-quark system alone. An
0418 /// appropriate number of nq digits is used to define the ordinary-quark content.
0419 /// As usual, 9 rather than 21 is used to denote a gluon/gluino in composite
0420 /// states. The sign of the hadron agrees with that of the constituent new
0421 /// particle (a color triplet) where there is a distinct new antiparticle, and
0422 /// else is defined as for normal hadrons. Particle names are R with the flavor
0423 /// content as lower index.
0424 
0425 /// APID: Definition of R-Glueballs: 100099X (X=1,3), 100999Y (Y=1,5)
0426 /// APID: NB In the current numbering scheme, some states with 2
0427 /// gluinos + gluon or 2 gluons + gluino could have degenerate
0428 /// PDG_IDs.
0429 bool isRGlueball(const DecodedPID& p) {
0430   if (p.ndigits() != 7 || p(0) != 1) {
0431     return false;
0432   }
0433   auto pp = p.shift(1);
0434   return ((pp.ndigits() == 3 && pp(0) == COMPOSITEGLUON &&
0435            pp(1) == COMPOSITEGLUON && (pp(2) == 1 || pp(2) == 3)) ||
0436           (pp.ndigits() == 4 && pp(0) == COMPOSITEGLUON &&
0437            pp(1) == COMPOSITEGLUON && pp(2) == COMPOSITEGLUON &&
0438            (pp(3) == 1 || pp(3) == 5)));
0439 }
0440 
0441 // APID Define R-Mesons as gluino-quark-antiquark and squark-antiquark bound
0442 // states (ignore 4th generation squarks/quarks) NB Current models only allow
0443 // gluino-quark-antiquark, stop-antiquark and sbottom-antiquark states
0444 bool isRMeson(const DecodedPID& p) {
0445   if (!(p.ndigits() == 7 && (p(0) == 1 || p(0) == 2))) {
0446     return false;
0447   }
0448   auto pp = p.shift(1);
0449   return (
0450       // Handle ~gluino-quark-antiquark states
0451       (pp.ndigits() == 4 && pp(0) == COMPOSITEGLUON &&
0452        pp.max_digit(1, 3) < COMPOSITEGLUON && pp(2) <= pp(1) &&
0453        isSMQuark(pp(1)) && isSMQuark(pp(2)) &&
0454        (pp.last() == 1 || pp.last() == 3)) ||
0455       // Handle squark-antiquark states (previously called Smeson/mesoninos)
0456       (pp.ndigits() == 3 && pp.max_digit(1, 3) < COMPOSITEGLUON &&
0457        pp(1) <= pp(0) && isSMQuark(pp(0)) && isSMQuark(pp(1)) &&
0458        pp.last() == 2));
0459 }
0460 
0461 // APID Define R-Baryons as gluino-quark-quark-quark and squark-quark-quark
0462 // bound states (ignore 4th generation squarks/quarks) NB Current models only
0463 // allow gluino-quark-quark-quark, stop-quark-quark and sbottom-quark-quark
0464 // states
0465 bool isRBaryon(const DecodedPID& p) {
0466   if (!(p.ndigits() == 7 && (p(0) == 1 || p(0) == 2))) {
0467     return false;
0468   }
0469   auto pp = p.shift(1);
0470   return (
0471       // Handle ~gluino-quark-quark-quark states
0472       (pp.ndigits() == 5 && pp(0) == COMPOSITEGLUON &&
0473        pp.max_digit(1, 4) < COMPOSITEGLUON && pp(2) <= pp(1) &&
0474        pp(3) <= pp(2) && isSMQuark(pp(1)) && isSMQuark(pp(2)) &&
0475        isSMQuark(pp(3)) && (pp.last() == 2 || pp.last() == 4)) ||
0476       // Handle squark-quark-quark states (previously called Sbaryons)
0477       (pp.ndigits() == 4 && pp.max_digit(1, 4) < COMPOSITEGLUON &&
0478        pp(1) <= pp(0) && pp(2) <= pp(1) && isSMQuark(pp(0)) &&
0479        isSMQuark(pp(1)) && isSMQuark(pp(2)) &&
0480        (pp.last() == 1 || pp.last() == 3)));
0481 }
0482 
0483 /// PDG rule 11i
0484 /// Magnetic monopoles and dyons are assumed to have one unit of Dirac monopole
0485 /// charge and a variable integer number nq1nq2 nq3 units of electric charge.
0486 /// Codes 411nq1nq2 nq3 0 are then used when the magnetic and electrical charge
0487 /// sign agree and 412nq1nq2 nq3 0 when they disagree, with the overall sign of
0488 /// the particle set by the magnetic charge. For now no spin information is
0489 /// provided.
0490 bool isMonopole(const DecodedPID& p) {
0491   return (p.ndigits() == 7 && p(0) == 4 && p(1) == 1 &&
0492           (p(2) == 1 || p(2) == 2) && p(6) == 0);
0493 }
0494 
0495 /// In addition, there is a need to identify "Q-ball" and similar very exotic
0496 /// (multi-charged) particles which may have large, non-integer charge. These
0497 /// particles are assigned the ad-hoc numbering +/-100XXXY0, where the charge is
0498 /// XXX.Y. or +/-200XXYY0, where the charge is XX/YY. The case of +/-200XXYY0 is
0499 /// legacy, see https://gitlab.cern.ch/atlas/athena/-/merge_requests/25862 Note
0500 /// that no other quantum numbers besides the charge are considered for these
0501 /// generic multi-charged particles (e.g. isSUSY() is false for them). Such a
0502 /// model was used in previous Run-1 (1301.5272,1504.04188) and Run-2
0503 /// (1812.03673,2303.13613) ATLAS searches.
0504 bool isGenericMultichargedParticle(const DecodedPID& p) {
0505   return (p.ndigits() == 8 && (p(0) == 1 || p(0) == 2) && p(1) == 0 &&
0506           p(2) == 0 && p(7) == 0);
0507 }
0508 
0509 /// PDG rule 16
0510 /// Nuclear codes are given as 10-digit numbers ±10LZZZAAAI.
0511 /// For a (hyper)nucleus consisting of n_p protons, n_n neutrons and
0512 /// n_Λ Λ's:
0513 /// A = n_p + n_n + n_Λ gives the total baryon number,
0514 /// Z = n_p gives the total charge,
0515 /// L = n_Λ gives the total number of strange quarks.
0516 /// I gives the isomer level, with I= 0 corresponding to the ground
0517 /// state and I > 0 to excitations, see
0518 /// [http://www.nndc.bnl.gov/amdc/web/nubase en.html], where states
0519 /// denoted m, n, p ,q translate to I= 1–4. As examples, the deuteron
0520 /// is 1000010020 and 235U is 1000922350. To avoid ambiguities,
0521 /// nuclear codes should not be applied to a single hadron, like p, n or
0522 /// Λ^0, where quark-contents-based codes already exist.
0523 bool isNucleus(const DecodedPID& p) {
0524   if (std::abs(p.pid()) == PROTON) {
0525     return true;
0526   }
0527   return (p.ndigits() == 10 && p(0) == 1 && p(1) == 0);
0528 }
0529 
0530 int numberOfProtons(const DecodedPID& p) {
0531   if (std::abs(p.pid()) == PROTON) {
0532     return (p.pid() > 0) ? 1 : -1;
0533   }
0534   if (isNucleus(p)) {
0535     const int result = p(5) + 10 * p(4) + 100 * p(3);
0536     return (p.pid() > 0) ? result : -result;
0537   }
0538   return 0;
0539 }
0540 
0541 inline int leadingQuark(const DecodedPID& p) {
0542   if (isQuark(p.pid())) {
0543     return std::abs(p.pid());
0544   }
0545   if (isMeson(p)) {
0546     return p.max_digit(1, 3);
0547   }
0548   if (isDiquark(p)) {
0549     return p.max_digit(2, 4);
0550   }
0551   if (isBaryon(p)) {
0552     return p.max_digit(1, 4);
0553   }
0554   if (isTetraquark(p)) {
0555     return p.max_digit(1, 5);
0556   }
0557   if (isPentaquark(p)) {
0558     return p.max_digit(1, 6);
0559   }
0560   if (isSUSY(p)) {  // APID SUSY case
0561     auto pp = p.shift(1);
0562     if (pp.ndigits() == 1) {
0563       return 0;
0564     }  // Handle squarks
0565     if (pp.ndigits() == 3) {
0566       pp = DecodedPID(pp(1));
0567     }  // Handle ~q qbar pairs
0568     if (pp.ndigits() > 3) {
0569       pp = pp.shift(1);
0570     }  // Drop gluinos and squarks
0571     return leadingQuark(pp);
0572   }
0573   return 0;
0574 }
0575 
0576 bool isLightMeson(const DecodedPID& p) {
0577   auto lq = leadingQuark(p);
0578   return (lq == DQUARK || lq == UQUARK || lq == SQUARK) && isMeson(p);
0579 }
0580 
0581 bool isStrangeMeson(const DecodedPID& p) {
0582   return leadingQuark(p) == SQUARK && isMeson(p);
0583 }
0584 
0585 bool isCharmMeson(const DecodedPID& p) {
0586   return leadingQuark(p) == CQUARK && isMeson(p);
0587 }
0588 
0589 bool isBottomMeson(const DecodedPID& p) {
0590   return leadingQuark(p) == BQUARK && isMeson(p);
0591 }
0592 
0593 inline bool isCCbarMeson(const DecodedPID& p) {
0594   return leadingQuark(p) == CQUARK && isMeson(p) &&
0595          (*(p.second.rbegin() + 2)) == CQUARK &&
0596          (*(p.second.rbegin() + 1)) == CQUARK;
0597 }
0598 
0599 inline bool isBBbarMeson(const DecodedPID& p) {
0600   return leadingQuark(p) == BQUARK && isMeson(p) &&
0601          (*(p.second.rbegin() + 2)) == BQUARK &&
0602          (*(p.second.rbegin() + 1)) == BQUARK;
0603 }
0604 
0605 bool isLightBaryon(const DecodedPID& p) {
0606   auto lq = leadingQuark(p);
0607   return (lq == DQUARK || lq == UQUARK || lq == SQUARK) && isBaryon(p);
0608 }
0609 
0610 bool isStrangeBaryon(const DecodedPID& p) {
0611   return leadingQuark(p) == SQUARK && isBaryon(p);
0612 }
0613 
0614 bool isCharmBaryon(const DecodedPID& p) {
0615   return leadingQuark(p) == CQUARK && isBaryon(p);
0616 }
0617 
0618 bool isBottomBaryon(const DecodedPID& p) {
0619   return leadingQuark(p) == BQUARK && isBaryon(p);
0620 }
0621 
0622 double fractionalCharge(const DecodedPID& p);
0623 int charge3(const DecodedPID& p);
0624 
0625 double charge(const DecodedPID& p) {
0626   if (isGenericMultichargedParticle(
0627           p)) {  // BSM multi-charged particles might have a fractional charge
0628                  // that's not a multiple of 1/3
0629     return fractionalCharge(p);
0630   } else {
0631     return 1.0 * charge3(p) / 3.0;
0632   }
0633 }
0634 
0635 int charge3(const DecodedPID& p) {
0636   auto ap = std::abs(p.pid());
0637   if (ap < TABLESIZE) {
0638     return copySign(triple_charge.at(ap), p.pid());
0639   }
0640   if (ap == K0) {
0641     return 0;
0642   }
0643   if (ap == GEANTINO0) {
0644     return 0;
0645   }
0646   if (ap == GEANTINOPLUS) {
0647     return copySign(3, p.pid());
0648   }
0649   if (ap == MAVTOP) {
0650     return copySign(2, p.pid());
0651   }
0652   std::size_t nq = 0;
0653   int sign = 1;
0654   int signmult = 1;
0655   int result = 0;
0656   bool classified = false;
0657   if (!classified && isMeson(p)) {
0658     classified = true;
0659     nq = 2;
0660     if ((*(p.second.rbegin() + 2)) == 2 || (*(p.second.rbegin() + 2)) == 4) {
0661       sign = -1;
0662     }
0663     signmult = -1;
0664   }
0665   if (!classified && isDiquark(p)) {
0666     return triple_charge.at(p(0)) + triple_charge.at(p(1));
0667   }
0668   if (!classified && isBaryon(p)) {
0669     classified = true;
0670     nq = 3;
0671   }
0672   if (!classified && isTetraquark(p)) {
0673     return triple_charge.at(p(3)) + triple_charge.at(p(4)) -
0674            triple_charge.at(p(6)) - triple_charge.at(p(7));
0675   }
0676   if (!classified && isPentaquark(p)) {
0677     return triple_charge.at(p(3)) + triple_charge.at(p(4)) +
0678            triple_charge.at(p(5)) + triple_charge.at(p(6)) -
0679            triple_charge.at(p(7));
0680   }
0681   if (!classified && isNucleus(p)) {
0682     return 3 * numberOfProtons(p);
0683   }
0684   if (!classified && isSUSY(p)) {
0685     nq = 0;
0686     auto pp = p.shift(1);
0687     if (pp.ndigits() < 3) {
0688       return charge3(pp);
0689     }  // super-partners of fundamental particles
0690     if (pp(0) == COMPOSITEGLUON) {
0691       if (pp(1) == COMPOSITEGLUON) {
0692         return 0;
0693       }  // R-Glueballs
0694       if (pp.ndigits() == 4 || pp.ndigits() == 5) {
0695         pp = pp.shift(1);  // Remove gluino
0696       }
0697     }
0698     if (pp.ndigits() == 3) {
0699       classified = true;
0700       nq = 2;
0701       if (p.last() % 2 == 0) {
0702         sign = -1;
0703       }
0704       signmult = -1;
0705     }  // states with squark-antiquark or quark-anti-quark
0706     if (pp.ndigits() == 4) {
0707       classified = true;
0708       nq = 3;
0709     }  // states with squark-quark-quark or quark-quark-quark
0710   }
0711   if (!classified && isMonopole(p)) {
0712     /// Codes 411nq1nq2 nq3 0  are then used when the magnetic and electrical
0713     /// charge sign agree and 412nq1nq2 nq3 0
0714     ///  when they disagree, with the overall sign of the particle set by the
0715     ///  magnetic charge.
0716     result = 3 * (p(3) * 100 + p(4) * 10 + p(5));
0717     return ((p.pid() > 0 && p(2) == 1) || (p.pid() < 0 && p(2) == 2)) ? result
0718                                                                       : -result;
0719   }
0720   if (!classified && isGenericMultichargedParticle(p)) {
0721     double abs_charge = 0.0;
0722     if (p(0) == 1) {
0723       abs_charge = p(3) * 100. + p(4) * 10. + p(5) * 1 +
0724                    p(6) * 0.1;  // multi-charged particle PDG ID is
0725                                 // +/-100XXXY0, where the charge is XXX.Y
0726     }
0727     if (p(0) == 2) {
0728       abs_charge = (p(3) * 10. + p(4)) /
0729                    (p(5) * 10.0 + p(6));  // multi-charged particle PDG ID is
0730       // +/-200XXYY0, where the charge is XX/YY
0731     }
0732     int abs_threecharge = static_cast<int>(std::round(
0733         abs_charge *
0734         3.));  // the multi-charged particles might have a fractional charge
0735                // that's not a multiple of 1/3, in that case round to the
0736                // closest multiple of 1/3 for charge3 and threecharge
0737     return copySign(abs_threecharge, p.pid());
0738   }
0739   for (auto r = p.second.rbegin() + 1; r != p.second.rbegin() + 1 + nq; ++r) {
0740     result += triple_charge.at(*r) * sign;
0741     sign *= signmult;
0742   }
0743   return copySign(result, p.pid());
0744 }
0745 double fractionalCharge(const DecodedPID& p) {
0746   if (!isGenericMultichargedParticle(p)) {
0747     return 1.0 * charge3(p) /
0748            3.0;  // this method is written for multi-charged particles, still
0749                  // make sure other cases are handled properly
0750   }
0751   double abs_charge = 0;
0752   if (p(0) == 1) {
0753     abs_charge = p(3) * 100. + p(4) * 10. + p(5) * 1 +
0754                  p(6) * 0.1;  // multi-charged particle PDG ID is +/-100XXXY0,
0755   }
0756   // where the charge is XXX.Y
0757   if (p(0) == 2) {
0758     abs_charge =
0759         (p(3) * 10. + p(4)) /
0760         (p(5) * 10.0 + p(6));  // multi-charged particle PDG ID is +/-200XXYY0,
0761   }
0762   // where the charge is XX/YY
0763   return copySign(abs_charge, p.pid());
0764 }
0765 
0766 // APID: Including Z' and Z'' as EM interacting.
0767 bool isEMInteracting(const DecodedPID& p) {
0768   return (isPhoton(p.pid()) || isZ(p.pid()) || p.pid() == ZPRIME ||
0769           p.pid() == ZDBLPRIME ||
0770           std::abs(charge(p)) > std::numeric_limits<double>::epsilon() ||
0771           isMonopole(p));
0772 }
0773 
0774 bool isRHadron(const DecodedPID& p) {
0775   return (isRBaryon(p) || isRMeson(p) || isRGlueball(p));
0776 }
0777 
0778 bool isStrongInteracting(const DecodedPID& p) {
0779   return (isGluon(p.pid()) || isQuark(p.pid()) || isDiquark(p) ||
0780           isGlueball(p) || isLeptoQuark(p.pid()) || isHadron(p) ||
0781           isRHadron(p));
0782 }  // APID: Glueballs and R-Hadrons are also strong-interacting
0783 
0784 }  // namespace
0785 
0786 // https://gitlab.cern.ch/atlas/athena/-/blob/b0898f93585c4eec97550a9e0a16f5b6e6b6b973/Generators/TruthUtils/TruthUtils/AtlasPID.h#L325
0787 bool ParticleIdHelper::isHadron(PdgParticle pdg) {
0788   DecodedPID p(pdg);
0789   return isMeson(p) || isBaryon(p) || isTetraquark(p) || isPentaquark(p);
0790 }
0791 
0792 // https://gitlab.cern.ch/atlas/athena/-/blob/b0898f93585c4eec97550a9e0a16f5b6e6b6b973/Generators/TruthUtils/TruthUtils/AtlasPID.h#L180
0793 bool ParticleIdHelper::isLepton(PdgParticle pdg) {
0794   auto sp = std::abs(pdg);
0795   return sp >= 11 && sp <= 18;
0796 }
0797 
0798 bool ParticleIdHelper::isMuon(PdgParticle pdg) {
0799   return std::abs(pdg) == MUON;
0800 }
0801 
0802 bool ParticleIdHelper::isElectron(PdgParticle pdg) {
0803   return std::abs(pdg) == ELECTRON;
0804 }
0805 
0806 bool ParticleIdHelper::isPhoton(PdgParticle pdg) {
0807   return std::abs(pdg) == PHOTON;
0808 }
0809 
0810 bool ParticleIdHelper::isTau(PdgParticle pdg) {
0811   return std::abs(pdg) == TAU;
0812 }
0813 
0814 HadronType ParticleIdHelper::hadronType(PdgParticle pdg) {
0815   DecodedPID p(pdg);
0816 
0817   using enum HadronType;
0818 
0819   if (isBBbarMeson(p)) {
0820     return BBbarMeson;
0821   }
0822   if (isCCbarMeson(p)) {
0823     return CCbarMeson;
0824   }
0825   if (isBottomMeson(p)) {
0826     return BottomMeson;
0827   }
0828   if (isCharmMeson(p)) {
0829     return CharmedMeson;
0830   }
0831   if (isBottomBaryon(p)) {
0832     return BottomBaryon;
0833   }
0834   if (isCharmBaryon(p)) {
0835     return CharmedBaryon;
0836   }
0837   if (isStrangeBaryon(p)) {
0838     return StrangeBaryon;
0839   }
0840   if (isLightBaryon(p)) {
0841     return LightBaryon;
0842   }
0843   if (isStrangeMeson(p)) {
0844     return StrangeMeson;
0845   }
0846   if (isLightMeson(p)) {
0847     return LightMeson;
0848   }
0849 
0850   return Unknown;
0851 }
0852 
0853 // https://gitlab.cern.ch/atlas/athena/-/blob/b0898f93585c4eec97550a9e0a16f5b6e6b6b973/Generators/TruthUtils/TruthUtils/AtlasPID.h#L159
0854 bool ParticleIdHelper::isQuark(PdgParticle pdg) {
0855   return pdg != 0 && (std::abs(pdg) <= 8 || std::abs(pdg) == MAVTOP);
0856 }
0857 
0858 // https://gitlab.cern.ch/atlas/athena/-/blob/b0898f93585c4eec97550a9e0a16f5b6e6b6b973/Generators/TruthUtils/TruthUtils/HepMCHelpers.h#L33
0859 bool ParticleIdHelper::isInteracting(PdgParticle pdg) {
0860   DecodedPID p(pdg);
0861   return isStrongInteracting(p) || isEMInteracting(p) || isGeantino(pdg);
0862 }
0863 
0864 std::ostream& operator<<(std::ostream& os, HadronType hadron) {
0865   switch (hadron) {
0866     using enum HadronType;
0867     case Hadron:
0868       return os << "Hadron";
0869     case BBbarMeson:
0870       return os << "BBbarMeson";
0871     case CCbarMeson:
0872       return os << "CCbarMeson";
0873     case BottomMeson:
0874       return os << "BottomMeson";
0875     case BottomBaryon:
0876       return os << "BottomBaryon";
0877     case CharmedMeson:
0878       return os << "CharmedMeson";
0879     case CharmedBaryon:
0880       return os << "CharmedBaryon";
0881     case StrangeMeson:
0882       return os << "StrangeMeson";
0883     case StrangeBaryon:
0884       return os << "StrangeBaryon";
0885     case LightMeson:
0886       return os << "LightMeson";
0887     case LightBaryon:
0888       return os << "LightBaryon";
0889     case Unknown:
0890       return os << "Unknown";
0891   }
0892   return os;
0893 }
0894 
0895 PdgParticle parsePdgParticle(const std::string& name) {
0896   if (name == "e-") {
0897     return eElectron;
0898   } else if (name == "e+") {
0899     return ePositron;
0900   } else if (name == "mu-") {
0901     return eMuon;
0902   } else if (name == "mu+") {
0903     return eAntiMuon;
0904   } else if (name == "tau-") {
0905     return eTau;
0906   } else if (name == "tau+") {
0907     return eAntiTau;
0908   } else if (name == "gamma") {
0909     return eGamma;
0910   } else if (name == "pi0") {
0911     return ePionZero;
0912   } else if (name == "pi+") {
0913     return ePionPlus;
0914   } else if (name == "pi-") {
0915     return ePionMinus;
0916   } else if (name == "K+") {
0917     return eKaonPlus;
0918   } else if (name == "K-") {
0919     return eKaonMinus;
0920   } else if (name == "n") {
0921     return eNeutron;
0922   } else if (name == "n~") {
0923     return eAntiNeutron;
0924   } else if (name == "p") {
0925     return eProton;
0926   } else if (name == "p~") {
0927     return eAntiProton;
0928   } else if (name == "Pb") {
0929     return eLead;
0930   } else {
0931     throw std::invalid_argument("Unknown particle name: " + name);
0932   }
0933 }
0934 }  // namespace Acts