Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 09:07:56

0001 // -*- C++ -*-
0002 //
0003 // This file is part of MCUtils -- https://gitlab.com/hepcedar/mcutils/
0004 // Copyright (C) 2013-2025 Andy Buckley <andy.buckley@cern.ch>
0005 //
0006 // Embedding of MCUtils code in other projects is permitted provided this
0007 // notice is retained and the MCUtils namespace and include path are changed.
0008 //
0009 #ifndef RIVET_PARTICLEIDUTILS_HH
0010 #define RIVET_PARTICLEIDUTILS_HH
0011 
0012 /// @file Utility functions for querying PDG ID codes (many from HepPID)
0013 /// @author Andy Buckley <andy.buckley@cern.ch>
0014 
0015 #include "Rivet/Tools/ParticleName.hh"
0016 #include "Rivet/Math/MathUtils.hh"
0017 #include <cassert>
0018 
0019 namespace Rivet {
0020   namespace PID {
0021 
0022 
0023     /// @defgroup mcutils_utils Utility functions
0024     /// @{
0025 
0026     /// Absolute value
0027     /// @deprecated Just use abs()!
0028     inline int abspid(int pid) {
0029       return abs(pid);
0030     }
0031 
0032 
0033     // /// Compile-time int^int power-raising function
0034     // template <size_t N>
0035     // inline int _intpow(int x) { return x * _intpow<N-1>(x); }
0036     // template <>
0037     // inline int _intpow<0>(int x) { return 1; }
0038 
0039     /// Raise 10 to an integer power (terrible)
0040     // inline size_t _pow10(unsigned int power) {
0041     //   return (size_t) std::pow(10.0, power);
0042     // }
0043     /// Raise 10 to an integer power (LUT, tested and found faster than constexpr version)
0044     inline size_t _pow10(unsigned int power) {
0045       //assert(power >= 0 && "_pow10 only defined for positive powers");
0046       assert(power < 16 && "_pow10 only defined for powers < 16");
0047       static const size_t POWS10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000, 1000000000000, 10000000000000, 100000000000000, 1000000000000000, 10000000000000000};
0048       return POWS10[power];
0049     }
0050     // /// Raise 10 to an integer power (constexpr)
0051     // inline constexpr size_t _pow10(unsigned int power) {
0052     //   assert(power >= 0 && "_pow10 only defined for positive powers");
0053     //   return (power > 0) ? 10*_pow10(power-1) : 1;
0054     // }
0055 
0056     ///  PID digits (base 10) are: n nr nl nq1 nq2 nq3 nj
0057     ///  The Location enum provides a convenient index into the PID.
0058     enum Location { nj=1, nq3, nq2, nq1, nl, nr, n, n8, n9, n10 };
0059 
0060     /// Split the PID into constituent integers
0061     ///
0062     ///  PID digits (base 10) are: n nr nl nq1 nq2 nq3 nj (cf. Location)
0063     ///
0064     /// @note Right-to-left nunbering, RHS-digit index = 1
0065     inline unsigned short _digit(Location loc, int pid) {
0066       const int div = _pow10(loc-1);
0067       return (abs(pid)/div) % 10;
0068     }
0069 
0070     /// Returns everything beyond the 7th digit (e.g. outside the numbering scheme)
0071     inline int _extraBits(int pid) {
0072       return abs(pid)/10000000;
0073     }
0074 
0075     /// @brief Return the first two digits if this is a "fundamental" particle
0076     /// @note ID = 100 is a special case (internal generator ID's are 81-100)
0077     inline int _fundamentalID(int pid) {
0078       if (_extraBits(pid) > 0) return 0;
0079       if (_digit(nq2,pid) == 0 && _digit(nq1,pid) == 0) {
0080         return abs(pid) % 10000;
0081       } else if (abs(pid) <= 100) {
0082         return abs(pid);
0083       } else {
0084         return 0;
0085       }
0086     }
0087 
0088     /// @}
0089 
0090 
0091     // Forward declaration
0092     inline bool isBSM(int pid);
0093 
0094 
0095     /// @defgroup mcutils_nucleus_ion Nucleus/ion functions
0096     /// @{
0097 
0098     /// @brief Is this a nucleus PID?
0099     ///
0100     /// This implements the 2006 Monte Carlo nuclear code scheme.
0101     /// Ion numbers are +/- 10LZZZAAAI.
0102     /// AAA is A - total baryon number
0103     /// ZZZ is Z - total charge
0104     /// L is the total number of strange quarks.
0105     /// I is the isomer number, with I=0 corresponding to the ground state.
0106     inline bool isNucleus(int pid) {
0107       // A proton can also be a Hydrogen nucleus
0108       if (abs(pid) == 2212) return true;
0109       // New standard: +/- 10LZZZAAAI
0110       if (_digit(n10,pid) == 1 && _digit(n9,pid) == 0) {
0111         // charge should always be less than or equal to baryon number
0112         // the following line is A >= Z
0113         if ((abs(pid)/10) % 1000 >= (abs(pid)/10000) % 1000) return true;
0114       }
0115       return false;
0116     }
0117 
0118     /// Get the atomic number (number of protons) in a nucleus/ion
0119     /// @note Ion numbers are +/- 10LZZZAAAI.
0120     inline int nuclZ(int pid) {
0121       // A proton can also be a Hydrogen nucleus
0122       if (abs(pid) == 2212) {
0123         return 1;
0124       }
0125       if (isNucleus(pid)) return (abs(pid)/10000) % 1000;
0126       return 0;
0127     }
0128     /// @deprecated Use nuclZ()
0129     inline int Z(int pid) {
0130       return nuclZ(pid);
0131     }
0132 
0133     /// Get the atomic weight (number of nucleons) in a nucleus/ion
0134     /// @note Ion numbers are +/- 10LZZZAAAI.
0135     inline int nuclA(int pid) {
0136       // a proton can also be a Hydrogen nucleus
0137       if (abs(pid) == 2212) {
0138         return 1;
0139       }
0140       if (isNucleus(pid)) return (abs(pid)/10) % 1000;
0141       return 0;
0142     }
0143     /// @deprecated Use nuclA()
0144     inline int A(int pid) {
0145       return nuclA(pid);
0146     }
0147 
0148     /// If this is a nucleus (ion), get nLambda
0149     /// @note Ion numbers are +/- 10LZZZAAAI.
0150     inline int nuclNlambda(int pid) {
0151       // a proton can also be a Hydrogen nucleus
0152       if (abs(pid) == 2212) {
0153         return 0;
0154       }
0155       if (isNucleus(pid)) return _digit(n8,pid);
0156       return 0;
0157     }
0158     /// @deprecated Use nuclNlambda()
0159     inline int lambda(int pid) {
0160       return nuclNlambda(pid);
0161     }
0162 
0163     /// @}
0164 
0165 
0166     /// @defgroup mcutils_fundamental Fundamental particles
0167     /// @{
0168 
0169     /// Determine if the PID is that of a quark
0170     inline bool isQuark(int pid) {
0171       return in_closed_range(abs(pid), 1, 8);
0172     }
0173 
0174     /// Determine if the PID is that of a gluon
0175     inline bool isGluon(int pid) {
0176       return pid == GLUON;
0177     }
0178 
0179     /// Determine if the PID is that of a parton (quark or gluon)
0180     inline bool isParton(int pid) {
0181       return isGluon(pid) || isQuark(pid);
0182     }
0183 
0184 
0185     /// Determine if the PID is that of a photon
0186     inline bool isPhoton(int pid) {
0187       return pid == PHOTON;
0188     }
0189 
0190     /// Determine if the PID is that of an electron or positron
0191     inline bool isElectron(int pid) {
0192       return abs(pid) == ELECTRON;
0193     }
0194 
0195     /// Determine if the PID is that of an muon or antimuon
0196     inline bool isMuon(int pid) {
0197       return abs(pid) == MUON;
0198     }
0199 
0200     /// Determine if the PID is that of an tau or antitau
0201     inline bool isTau(int pid) {
0202       return abs(pid) == TAU;
0203     }
0204 
0205     /// Determine if the PID is that of a charged lepton
0206     inline bool isChargedLepton(int pid) {
0207       const long apid = abs(pid);
0208       return apid == 11 || apid == 13 || apid == 15 || apid == 17;
0209     }
0210     /// Alias for isChargedLepton
0211     /// @deprecated Prefer isChargedLepton
0212     inline bool isChLepton(int pid) {
0213       return isChargedLepton(pid);
0214     }
0215 
0216     /// Determine if the PID is that of a neutrino
0217     inline bool isNeutrino(int pid) {
0218       const long apid = abs(pid);
0219       return apid == 12 || apid == 14 || apid == 16 || apid == 18;
0220     }
0221 
0222 
0223     /// Determine if the PID is that of a W+
0224     inline bool isWplus(int pid) {
0225       return pid == WPLUSBOSON;
0226     }
0227 
0228     /// Determine if the PID is that of a W-
0229     inline bool isWminus(int pid) {
0230       return pid == WMINUSBOSON;
0231     }
0232 
0233     /// Determine if the PID is that of a W+-
0234     inline bool isW(int pid) {
0235       return abs(pid) == WPLUSBOSON;
0236     }
0237 
0238     /// Determine if the PID is that of a Z0
0239     inline bool isZ(int pid) {
0240       return pid == Z0BOSON;
0241     }
0242 
0243     /// Determine if the PID is that of an SM/lightest SUSY Higgs
0244     inline bool isHiggs(int pid) {
0245       return pid == HIGGSBOSON || pid == 26; //< @todo Check on 26 still needed? (used in HERWIG SUSY, for example)
0246     }
0247 
0248     /// @todo isSUSYHiggs?
0249 
0250 
0251     /// Is this a graviton?
0252     inline bool isGraviton(int pid) {
0253       return pid == GRAVITON;
0254     }
0255 
0256 
0257     // /// Determine if the PID is that of a d/dbar
0258     // inline bool isDown(int pid) { return abs(pid) == DQUARK; }
0259 
0260     // /// Determine if the PID is that of a u/ubar
0261     // inline bool isUp(int pid) { return abs(pid) == UQUARK; }
0262 
0263     /// Determine if the PID is that of an s/sbar
0264     inline bool isStrange(int pid) {
0265       return abs(pid) == SQUARK;
0266     }
0267 
0268     /// Determine if the PID is that of a c/cbar
0269     inline bool isCharm(int pid) {
0270       return abs(pid) == CQUARK;
0271     }
0272 
0273     /// Determine if the PID is that of a b/bbar
0274     inline bool isBottom(int pid) {
0275       return abs(pid) == BQUARK;
0276     }
0277 
0278     /// Determine if the PID is that of a t/tbar
0279     inline bool isTop(int pid) {
0280       return abs(pid) == TQUARK;
0281     }
0282 
0283     /// @}
0284 
0285 
0286     /// @defgroup mcutils_qcomp Quark composite functions
0287     /// @{
0288 
0289     /// Is this a pomeron, odderon, or generic reggeon?
0290     inline bool isReggeon(int pid) {
0291       return pid == 110 || pid == 990 || pid == 9990;
0292     }
0293 
0294     /// Check to see if this is a valid meson
0295     inline bool isMeson(int pid) {
0296       if (_extraBits(pid) > 0) return false;
0297       if (isBSM(pid)) return false;
0298       const int aid = abs(pid);
0299       if (aid == 130 || aid == 310 || aid == 210) return true; //< special cases for kaons
0300       if (aid <= 100) return false;
0301       if (_digit(nq1,pid) != 0) return false;
0302       if (_digit(nq2,pid) == 0) return false;
0303       if (_digit(nq3,pid) == 0) return false;
0304       if (_digit(nq2,pid) < _digit(nq3,pid)) return false;
0305       // EvtGen uses some odd numbers
0306       /// @todo Remove special-casing for EvtGen
0307       if (aid == 150 || aid == 350 || aid == 510 || aid == 530) return true;
0308       // Pomeron, Reggeon, etc.
0309       if (isReggeon(pid)) return false; //true; //< WTF?
0310       // Check for illegal antiparticles
0311       if (_digit(nj,pid) > 0 && _digit(nq3,pid) > 0 && _digit(nq2,pid) > 0 && _digit(nq1,pid) == 0) {
0312         return !(_digit(nq3,pid) == _digit(nq2,pid) && pid < 0);
0313       }
0314       return false;
0315     }
0316 
0317     /// Check to see if this is a valid baryon
0318     inline bool isBaryon(int pid) {
0319       if (_extraBits(pid) > 0) return false;
0320       if (isBSM(pid)) return false;
0321       if (abs(pid) <= 100) return false;
0322       if (_fundamentalID(pid) <= 100 && _fundamentalID(pid) > 0) return false;
0323       if (abs(pid) == 2110 || abs(pid) == 2210) return true; ///< @todo Why this special case with nJ = 0? What are these? Not listed in RPP MC doc...
0324       if (_digit(nj,pid) == 0) return false;
0325       if (_digit(nq1,pid) == 0 || _digit(nq2,pid) == 0 || _digit(nq3,pid) == 0) return false;
0326       return true;
0327       /// @todo This is more correct by the definition, but the PDG's entries 1212, 1214, 1216, 1218 and 2122, 2124, 2126, 2128 come out as invalid
0328       // if ((_digit(nq1,pid) >= _digit(nq2,pid) && _digit(nq2,pid) >= _digit(nq3,pid)) ||
0329       //     (_digit(nq1,pid) > _digit(nq3,pid) && _digit(nq3,pid) > _digit(nq2,pid)) || //< case 6b for lighter quarks in J=1
0330       //     (_digit(nq3,pid) > _digit(nq1,pid) && _digit(nq1,pid) > _digit(nq2,pid))) //< case 6e for extra states in excited multiplets
0331       //   return true;
0332       // return false;
0333     }
0334 
0335     // Check to see if this is a valid diquark
0336     inline bool isDiquark(int pid) {
0337       if (_extraBits(pid) > 0) return false;
0338       if (isBSM(pid)) return false;
0339       if (abs(pid) <= 100) return false;
0340       if (_fundamentalID(pid) <= 100 && _fundamentalID(pid) > 0) return false;
0341       if (_digit(nq1,pid) == 0) return false;
0342       if (_digit(nq2,pid) == 0) return false;
0343       if (_digit(nq3,pid) != 0) return false;
0344       if (_digit(nq1,pid) < _digit(nq2,pid)) return false;
0345       if (_digit(nj,pid) > 0  && _digit(nq3,pid) == 0 && _digit(nq2,pid) > 0 && _digit(nq1,pid) > 0) return true; // diquark signature
0346       // EvtGen uses the diquarks for quark pairs, so, for instance, 5501 is a valid "diquark" for EvtGen
0347       // if (_digit(nj) == 1 && _digit(nq2) == _digit(nq1)) {    // illegal
0348       //    return false;
0349       // } else {
0350       //  return true;
0351       // }
0352       return false;
0353     }
0354     /// @deprecated Use the nicer capitalisation isDiquark(pid)
0355     inline bool isDiQuark(int pid) {
0356       return isDiquark(pid);
0357     }
0358 
0359     /// Check to see if this is a valid pentaquark
0360     inline bool isPentaquark(int pid) {
0361       // a pentaquark is of the form 9abcdej,
0362       // where j is the spin and a, b, c, d, and e are quarks
0363       if (_extraBits(pid) > 0) return false;
0364       if (isBSM(pid)) return false;
0365       if (_digit(n,pid) != 9)  return false;
0366       if (_digit(nr,pid) == 9 || _digit(nr,pid) == 0)  return false;
0367       if (_digit(nj,pid) == 9 || _digit(nl,pid) == 0)  return false;
0368       if (_digit(nq1,pid) == 0)  return false;
0369       if (_digit(nq2,pid) == 0)  return false;
0370       if (_digit(nq3,pid) == 0)  return false;
0371       if (_digit(nj,pid) == 0)  return false;
0372       // check ordering
0373       if (_digit(nq2,pid) > _digit(nq1,pid))  return false;
0374       if (_digit(nq1,pid) > _digit(nl,pid))  return false;
0375       if (_digit(nl,pid) > _digit(nr,pid))  return false;
0376       return true;
0377     }
0378 
0379     /// Is this a heavy-flavour quarkonium meson?
0380     ///
0381     /// @note Original by LHCb in Rivet analysis LHCB_2016_I1504058
0382     ///
0383     /// @note phi = s,sbar is not considered quarkonium
0384     inline bool isQuarkonium(int pid) {
0385       if (!isMeson(pid)) return false; //< all quarkonia are mesons
0386       if (_digit(nq1, pid) != 0) return false;
0387       const int fq = _digit(nq2, pid);
0388       return (fq > 3 && _digit(nj, pid) > 0 && _digit(nq3, pid) == fq);
0389     }
0390 
0391     /// Is this a valid hadron ID?
0392     ///
0393     /// @note BSM hadrons, e.g. R-hadrons, don't count
0394     inline bool isHadron(int pid) {
0395       if (_extraBits(pid) > 0) return false;
0396       if (isBSM(pid) > 0) return false;
0397       if (isMeson(pid)) return true;
0398       if (isBaryon(pid)) return true;
0399       if (isPentaquark(pid)) return true;
0400       return false;
0401     }
0402 
0403     /// @}
0404 
0405 
0406     /// @defgroup mcutils_idclasses More general particle class identification functions
0407     /// @{
0408 
0409     /// Is this a valid lepton ID?
0410     ///
0411     /// @note BSM "leptons" don't count
0412     inline bool isLepton(int pid) {
0413       if (_extraBits(pid) > 0) return false;
0414       if (isBSM(pid) > 0) return false;
0415       if (_fundamentalID(pid) >= 11 && _fundamentalID(pid) <= 18) return true;
0416       return false;
0417     }
0418 
0419     /// Is this a valid BSM boson (SUSY Higgs, W', Z')?
0420     inline bool isBSMBoson(int pid) {
0421       return in_closed_range(abs(pid), 32, 37);
0422     }
0423 
0424     /// Is this an SM fundamental particle?
0425     inline bool isSMFundamental(int pid) {
0426       return isQuark(pid) || isLepton(pid) ||
0427         isGluon(pid) || isPhoton(pid) || isW(pid) || isZ(pid) || isHiggs(pid) ||
0428         isBSMBoson(pid) || isGraviton(pid);
0429     }
0430 
0431     /// @brief Is this a fundamental SUSY particle?
0432     ///
0433     /// The MSSM extended Higgs sector is not counted as 'SUSY' particles, since they are not superpartners.
0434     inline bool isSUSY(int pid) {
0435       // Fundamental SUSY particles have n = 1 or 2
0436       if (_extraBits(pid) > 0) return false;
0437       if (_digit(n,pid) != 1 && _digit(n,pid) != 2)  return false;
0438       if (_digit(nr,pid) != 0)  return false;
0439       // Check fundamental part for SM PID on which it is based
0440       const int fundId = _fundamentalID(pid);
0441       if (fundId == 0) return false;
0442       if (_digit(n,pid) == 1) { // most superpartners, incl LH sfermions
0443         return isSMFundamental(fundId);
0444       } else if (_digit(n,pid) == 2) { // RH sfermions
0445         return isQuark(fundId) || isChargedLepton(fundId);
0446       }
0447       return true;
0448     }
0449 
0450     /// Is this an R-hadron?
0451     inline bool isRHadron(int pid) {
0452       // An R-hadron is of the form 10abcdj,
0453       // where j is the spin and a, b, c, and d are quarks or gluons
0454       if (_extraBits(pid) > 0) return false;
0455       if (_digit(n,pid) != 1)  return false;
0456       if (_digit(nr,pid) != 0)  return false;
0457       // Make sure this isn't a SUSY particle
0458       if (isSUSY(pid)) return false;
0459       // All R-hadrons have at least 3 core digits
0460       if (_digit(nq2,pid) == 0)  return false;
0461       if (_digit(nq3,pid) == 0)  return false;
0462       if (_digit(nj,pid) == 0)  return false;
0463       return true;
0464     }
0465     /// Alias
0466     inline bool isRhadron(int pid) {
0467       return isRHadron(pid);
0468     }
0469 
0470     /// Is this a technicolor particle?
0471     inline bool isTechnicolor(int pid) {
0472       if (_extraBits(pid) > 0) return false;
0473       return _digit(n,pid) == 3;
0474     }
0475 
0476     /// Is this an excited (composite) quark or lepton?
0477     inline bool isExcited(int pid) {
0478       if (_extraBits(pid) > 0) return false;
0479       return _digit(n,pid) == 4 && _digit(nr,pid) == 0;
0480     }
0481 
0482     /// Is this a Kaluza-Klein excitation?
0483     inline bool isKK(int pid) {
0484       if (_extraBits(pid) > 0) return false;
0485       const int ndigit = _digit(n,pid);
0486       return ndigit == 5 || ndigit == 6;
0487     }
0488 
0489     /// Is this a lepto-quark?
0490     inline bool isLeptoQuark(int pid) {
0491       // Many UFO models are extending the PDG standards... is this going to be official?
0492       return abs(pid) == 42;
0493     }
0494 
0495     /// Is this a generic Dark Matter particle?
0496     /// @note DM particles, including mediators, get the range 51-60
0497     /// @note Also covers other cases: Heavy neutral leptons (50), Light pseudo-scalar A in 2HDM (55), Z' scalar UFO models (56)
0498     /// @todo Give a more explicit name to clarify that this does not cover all DM particles, e.g. LSP?
0499     inline bool isDarkMatter(int pid) {
0500       const int ndigit = _digit(n,pid);
0501       const int nrdigit = _digit(nr,pid);
0502       if ((ndigit == 0 && nrdigit == 0) || (ndigit == 5 && nrdigit == 9))
0503         return in_closed_range(abs(_fundamentalID(pid)),50,60);
0504       return false;
0505     }
0506     /// Convenience alias
0507     inline bool isDM(int pid) {
0508       return isDarkMatter(pid);
0509     }
0510 
0511     /// Is this a Hidden Valley particle?
0512     inline bool isHiddenValley(int pid) {
0513       return (_digit(n,pid) == 4 && _digit(nr,pid) == 9);
0514     }
0515 
0516     /// Is this an exotic particle?
0517     inline bool isExotic(int pid) {
0518       // From the PDG definition, 40-80 reserved for exotic particles
0519       // Some overlap with ranges from other functions (e.g. isDM)
0520       // Also covers R0 (41)
0521       return in_closed_range(abs(pid),40,80);
0522     }
0523 
0524     /// Is this a 4th generation particle?
0525     inline bool isFourthGen(int pid) {
0526       return abs(pid) == BPRIME || abs(pid) == TPRIME || abs(pid) == LPRIME || abs(pid) == NUPRIME;
0527     }
0528 
0529     /// Is this from a magnetic monopole or dyon?
0530     inline bool isMagMonopole(int pid) {
0531       if (_digit(n,pid) != 4) return false;
0532       if (_digit(nr,pid) != 1) return false;
0533       if (_digit(nl,pid) != 1 && _digit(nl,pid) != 2) return false;
0534       // Require at least 1 core digit
0535       // NOT TRUE! Electrically neutral monopoles are possible
0536       // if (_digit(nq3,pid) == 0)  return false;
0537       // Always have spin zero for now
0538       if (_digit(nj,pid) != 0) return false;
0539       return true;
0540     }
0541     /// Just treat a dyon as an alias for magmonopole for now
0542     inline bool isDyon(int pid) {
0543       return isMagMonopole(pid);
0544     }
0545 
0546     /// Is this a Q-ball?
0547     /// @note Ad-hoc numbering for such particles is 100xxxx0, where xxxx is the charge in tenths.
0548     inline bool isQBall(int pid) {
0549       if (_extraBits(pid) != 1) return false;
0550       if (_digit(n,pid) != 0) return false;
0551       if (_digit(nr,pid) != 0) return false;
0552       // Check the core number
0553       if ((abs(pid)/10) % 10000 == 0) return false;
0554       // These particles have spin zero for now
0555       if (_digit(nj,pid) != 0) return false;
0556       return true;
0557     }
0558     /// Alias
0559     inline bool isQball(int pid) {
0560       return isQBall(pid);
0561     }
0562 
0563     /// Is this an excited lepton?
0564     inline bool isExcitedLepton(int pid) {
0565       if (!isExcited(pid)) return false;
0566       return isLepton( _fundamentalID(pid) );
0567     }
0568 
0569     /// Is this a black hole?
0570     inline bool isBlackHole(int pid) {
0571       if (_digit(n,pid) != 5 && _digit(n,pid) != 6) return false;
0572       if (_digit(nl,pid) != 0) return false;
0573       return _fundamentalID(pid)==40;
0574     }
0575 
0576     /// Is this an anomalously electrically charged particle (AECO)?
0577     inline bool isAECO(int pid) {
0578       if (_digit( n,pid) != 1) return false;
0579       if (_digit(nr,pid) != 0) return false;
0580       if (_digit(nl,pid) != 0) return false;
0581       if (_digit(nj,pid) != 0) return false;
0582       return true;
0583     }
0584 
0585     /// Is this a BSM particle (including graviton)?
0586     inline bool isBSM(int pid) {
0587       return isSUSY(pid) || isRHadron(pid) || isTechnicolor(pid) ||
0588         isExcited(pid) || isKK(pid) || isGraviton(pid) ||
0589         isBSMBoson(pid) || isLeptoQuark(pid) || isDM(pid) || isHiddenValley(pid) ||
0590         isExotic(pid) || isFourthGen(pid) || isBlackHole(pid) ||
0591         isDyon(pid) || isQball(pid) || isAECO(pid);
0592     }
0593 
0594     /// Check to see if this is a valid PID (i.e. matches any known scheme)
0595     inline bool _isValid(int pid) {
0596       // Starting with 99 means anything goes (but nothing is known)
0597       if (_digit(n, pid) == 9 && _digit(nr, pid) == 9) return true;
0598       // Check that extra bits are only used for nuclei
0599       if (_extraBits(pid) > 0) return (isNucleus(pid) || isQball(pid));
0600       // Check that it fits into a standard non-nucleus convention
0601       if (isBSM(pid)) return true;
0602       if (isHadron(pid)) return true;
0603       if (_digit(n,pid) == 9 && _digit(nr,pid) == 0) return false; // could only have been a tentative hadron, but !isHadron
0604       if (isDiquark(pid)) return true;
0605       if (isPentaquark(pid)) return true;
0606       if (isReggeon(pid)) return true;
0607       // // Quark digit orderings required by the standard
0608       // if (_digit(nq1,pid) != 0 && _digit(nq1,pid) < _digit(nq2,pid)) return false;
0609       // if (_digit(nq2,pid) != 0 && _digit(nq2,pid) < _digit(nq3,pid)) return false;
0610       // Final check on fundamental ID
0611       return (_fundamentalID(pid) > 0);
0612     }
0613     inline bool isValid(int pid) {
0614       return _isValid(pid);
0615     }
0616 
0617     /// @}
0618 
0619 
0620     /// @defgroup mcutils_partoncontent Parton content functions
0621     /// @{
0622 
0623     inline bool _hasQ(int pid, int q) {
0624       if (abs(pid) == q) return true; //< trivial case!
0625       if (!_isValid(pid)) return false;
0626       // if (_extraBits(pid) > 0) return false;
0627       // if (_fundamentalID(pid) > 0) return false;
0628       if (isMagMonopole(pid)) return false;
0629       if (isRHadron(pid)) {
0630         int iz = 7;
0631         for (int i = 6; i > 1; --i) {
0632           if (_digit(Location(i), pid) == 0) {
0633             iz = i;
0634           } else if ( i == iz-1 ) {
0635             // ignore squark or gluino
0636           } else {
0637             if (_digit(Location(i),pid) == q) return true;
0638           }
0639         }
0640         return false;
0641       }
0642       if (_digit(nq3,pid) == q || _digit(nq2,pid) == q || _digit(nq1,pid) == q ) return true;
0643       if (isPentaquark(pid)) {
0644         if (_digit(nl,pid) == q || _digit(nr,pid) == q) return true;
0645       }
0646       return false;
0647     }
0648 
0649     /// Does this particle contain a down quark?
0650     inline bool hasDown(int pid) {
0651       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 1);
0652     }
0653     /// Does this particle contain an up quark?
0654     inline bool hasUp(int pid) {
0655       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 2);
0656     }
0657     /// Does this particle contain a strange quark?
0658     inline bool hasStrange(int pid) {
0659       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 3);
0660     }
0661     /// Does this particle contain a charm quark?
0662     inline bool hasCharm(int pid) {
0663       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 4);
0664     }
0665     /// Does this particle contain a bottom quark?
0666     inline bool hasBottom(int pid) {
0667       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 5);
0668     }
0669     /// Does this particle contain a top quark?
0670     inline bool hasTop(int pid) {
0671       return (isHadron(pid) || isQuark(pid)) && _hasQ(pid, 6);
0672     }
0673 
0674     /// @}
0675 
0676 
0677     /// @defgroup mcutils_parton_classes Parton content classification
0678     /// @{
0679 
0680     /// Determine if the particle is a heavy flavour hadron or parton
0681     inline bool isHeavyFlavor(int pid) {
0682       if (!isHadron(pid) && !isQuark(pid)) return false;
0683       return hasCharm(pid) || hasBottom(pid) || hasTop(pid);
0684     }
0685     /// British-spelling alias for isHeavyFlavor
0686     inline bool isHeavyFlavour(int pid) {
0687       return isHeavyFlavor(pid);
0688     }
0689 
0690 
0691     // /// Determine if the particle is a light-flavour hadron or parton
0692     // inline bool isLightFlavor(int pid) {
0693     //   return !isHeavyFlavor();
0694     // }
0695 
0696 
0697     /// Determine if the PID is that of a heavy parton (c,b,t)
0698     inline bool isHeavyParton(int pid) {
0699       return isParton(pid) && isHeavyFlavor(pid);
0700     }
0701 
0702     /// Determine if the PID is that of a light parton (u,d,s)
0703     inline bool isLightParton(int pid) {
0704       return isParton(pid) && !isHeavyFlavor(pid);
0705     }
0706 
0707 
0708     /// Determine if the PID is that of a heavy flavour (b or c) meson
0709     inline bool isHeavyMeson(int pid) {
0710       return isMeson(pid) && isHeavyFlavor(pid);
0711     }
0712 
0713     /// Determine if the PID is that of a heavy flavour (b or c) baryon
0714     inline bool isHeavyBaryon(int pid) {
0715       return isBaryon(pid) && isHeavyFlavor(pid);
0716     }
0717 
0718     /// Determine if the PID is that of a heavy flavour (b or c) hadron
0719     inline bool isHeavyHadron(int pid) {
0720       return isHadron(pid) && isHeavyFlavor(pid);
0721     }
0722 
0723     /// Determine if the PID is that of a light flavour (not b or c) meson
0724     inline bool isLightMeson(int pid) {
0725       return isMeson(pid) && !isHeavyFlavor(pid);
0726     }
0727 
0728     /// Determine if the PID is that of a light flavour (not b or c) baryon
0729     inline bool isLightBaryon(int pid) {
0730       return isBaryon(pid) && !isHeavyFlavor(pid);
0731     }
0732 
0733     /// Determine if the PID is that of a light flavour (not b or c) hadron
0734     inline bool isLightHadron(int pid) {
0735       return isHadron(pid) && !isHeavyFlavor(pid);
0736     }
0737 
0738 
0739     /// Determine if the PID is that of a b-meson.
0740     inline bool isBottomMeson(int pid) {
0741       return hasBottom(pid) && isMeson(pid);
0742     }
0743 
0744     /// Determine if the PID is that of a b-baryon.
0745     inline bool isBottomBaryon(int pid) {
0746       return hasBottom(pid) && isBaryon(pid);
0747     }
0748 
0749     /// Determine if the PID is that of a b-hadron.
0750     inline bool isBottomHadron(int pid) {
0751       return hasBottom(pid) && isHadron(pid);
0752     }
0753 
0754 
0755     /// @brief Determine if the PID is that of a c-meson.
0756     ///
0757     /// @note Specifically, the _heaviest_ quark is a c: a B_c is a b-meson and NOT a c-meson.
0758     /// Charmonia (closed charm) are counted as c-mesons here.
0759     inline bool isCharmMeson(int pid) {
0760       return isMeson(pid) && hasCharm(pid) &&
0761         !hasBottom(pid);
0762     }
0763 
0764     /// @brief Determine if the PID is that of a c-baryon.
0765     ///
0766     /// @note Specifically, the _heaviest_ quark is a c: a baryon containing a b & c
0767     /// is a b-baryon and NOT a c-baryon. To test for the simpler case, just use
0768     /// a combination of hasCharm() and isBaryon().
0769     inline bool isCharmBaryon(int pid) {
0770       return isBaryon(pid) && hasCharm(pid) &&
0771         !hasBottom(pid);
0772     }
0773 
0774     /// Determine if the PID is that of a c-hadron.
0775     ///
0776     /// @note Specifically, the _heaviest_ quark is a c: a baryon containing a b & c
0777     /// is a b-baryon and NOT a c-baryon. To test for the simpler case, just use
0778     /// a combination of hasCharm() and isBaryon().
0779     inline bool isCharmHadron(int pid) {
0780       return isHadron(pid) && hasCharm(pid) &&
0781         !hasBottom(pid);
0782     }
0783 
0784 
0785     /// Determine if the PID is that of a strange meson
0786     ///
0787     /// @note Specifically, the _heaviest_ quark is an s: if it also contains
0788     /// either charm or bottom, it is not considered to be a strange hadron.
0789     inline bool isStrangeMeson(int pid) {
0790       return isMeson(pid) && hasStrange(pid) &&
0791         !(hasBottom(pid) || hasCharm(pid));
0792     }
0793 
0794     /// Determine if the PID is that of a strange baryon
0795     ///
0796     /// @note Specifically, the _heaviest_ quark is an s: if it also contains
0797     /// either charm or bottom, it is not considered to be a strange hadron.
0798     inline bool isStrangeBaryon(int pid) {
0799       return isBaryon(pid) && hasStrange(pid) &&
0800         !(hasBottom(pid) || hasCharm(pid));
0801     }
0802 
0803     /// Determine if the PID is that of a strange hadron
0804     ///
0805     /// @note Specifically, the _heaviest_ quark is an s: if it also contains
0806     /// either charm or bottom, it is not considered to be a strange hadron.
0807     inline bool isStrangeHadron(int pid) {
0808       return isHadron(pid) && hasStrange(pid) &&
0809         !(hasBottom(pid) || hasCharm(pid));
0810     }
0811 
0812     /// @}
0813 
0814 
0815 
0816     /// @defgroup mcutils_angmom Angular momentum functions
0817     /// @{
0818 
0819     /// jSpin returns 2J+1, where J is the total spin
0820     inline int jSpin(int pid) {
0821       const int fund = _fundamentalID(pid);
0822       if (fund > 0) {
0823         // some of these are known
0824         if (fund > 0 && fund < 7) return 2;
0825         if (fund == 9) return 3;
0826         if (fund > 10 && fund < 17) return 2;
0827         if (fund > 20 && fund < 25) return 3;
0828         return 0;
0829       } else if (_extraBits(pid) > 0) {
0830         return 0;
0831       }
0832       return abs(pid) % 10;
0833     }
0834 
0835     /// sSpin returns 2S+1, where S is the spin
0836     inline int sSpin(int pid) {
0837       // Handle invalid cases first
0838       if (!isMeson(pid)) return 0;
0839       if (_digit(n,pid) == 9 && _digit(nr,pid) == 0) return 0; // tentative ID
0840       // Special generic DM particles with defined spins
0841       const int fund = _fundamentalID(pid);
0842       if (fund == 51 || fund == 54) return 1;
0843       if (fund == 52) return 2;
0844       if (fund == 53 || fund == 55) return 3;
0845       // Calculate from nl and nj digits
0846       const int inl = _digit(nl,pid);
0847       const int js = _digit(nj,pid);
0848       if (inl == 0 && js >= 3) return 1;
0849       else if (inl == 0  && js == 1) return 0;
0850       else if (inl == 1  && js >= 3) return 0;
0851       else if (inl == 2  && js >= 3) return 1;
0852       else if (inl == 1  && js == 1) return 1;
0853       else if (inl == 3  && js >= 3) return 1;
0854       // Default to zero
0855       return 0;
0856     }
0857 
0858     /// lSpin returns 2L+1, where L is the orbital angular momentum
0859     inline int lSpin(int pid) {
0860       // Handle invalid cases first
0861       if (!isMeson(pid)) return 0;
0862       if (_digit(n,pid) == 9 && _digit(nr,pid) == 0) return 0; // tentative ID
0863       // Calculate from nl and nj digits
0864       const int inl = _digit(nl,pid);
0865       const int js = _digit(nj,pid);
0866       if (inl == 0 && js == 3) return 0;
0867       else if (inl == 0 && js == 5) return 1;
0868       else if (inl == 0 && js == 7) return 2;
0869       else if (inl == 0 && js == 9) return 3;
0870       else if (inl == 0 && js == 1) return 0;
0871       else if (inl == 1 && js == 3) return 1;
0872       else if (inl == 1 && js == 5) return 2;
0873       else if (inl == 1 && js == 7) return 3;
0874       else if (inl == 1 && js == 9) return 4;
0875       else if (inl == 2 && js == 3) return 1;
0876       else if (inl == 2 && js == 5) return 2;
0877       else if (inl == 2 && js == 7) return 3;
0878       else if (inl == 2 && js == 9) return 4;
0879       else if (inl == 1 && js == 1) return 1;
0880       else if (inl == 3 && js == 3) return 2;
0881       else if (inl == 3 && js == 5) return 3;
0882       else if (inl == 3 && js == 7) return 4;
0883       else if (inl == 3 && js == 9) return 5;
0884       // Default to zero
0885       return 0;
0886     }
0887 
0888     /// @}
0889 
0890 
0891     /// @defgroup mcutils_charge Charge functions
0892     /// @{
0893 
0894     /// Three times the EM charge (as integer)
0895     inline int charge3(int pid) {
0896       static int ch100[100] = { -1,  2, -1, 2, -1, 2, -1, 2, 0, 0,
0897         -3,  0, -3, 0, -3, 0, -3, 0, 0, 0,
0898         0,  0,  0, 3,  0, 0,  0, 0, 0, 0,
0899         0,  0,  0, 3,  0, 0,  3, 0, 0, 0,
0900         0, -1,  0, 0,  0, 0,  0, 0, 0, 0,
0901         0,  6,  3, 6,  0, 0,  0, 0, 0, 0,
0902         0,  0,  0, 0,  0, 0,  0, 0, 0, 0,
0903         0,  0,  0, 0,  0, 0,  0, 0, 0, 0,
0904         0,  0,  0, 0,  0, 0,  0, 0, 0, 0,
0905         0,  0,  0, 0,  0, 0,  0, 0, 0, 0
0906       };
0907       const int ida = abs(pid);
0908       // Shortcuts for common particles
0909       if (pid == 21 || pid == 22) return 0; // gluon and photon
0910       if (ida == 211) return std::signbit(pid) ? -3 : 3; // charged pion
0911       if (pid == 111) return 0; // neutral pion
0912       // if (ida == 12 || ida == 14 || ida == 16) return 0; // neutrinos
0913       // if (ida == 11 || ida == 13 || ida == 15) return std::signbit(pid) ? +3 : -3; // leptons
0914       // if (ida == 1 || ida == 3 || ida == 5) return std::signbit(pid) ? +1 : -1; // quarks
0915       // if (ida == 2 || ida == 4 || ida == 6) return std::signbit(pid) ? -2 : +2; // quarks
0916       // Standard decoding
0917       const unsigned short q1 = _digit(nq1,pid);
0918       const unsigned short q2 = _digit(nq2,pid);
0919       const unsigned short q3 = _digit(nq3,pid);
0920       const unsigned short ql = _digit(nl,pid);
0921       const int sid = _fundamentalID(pid);
0922       int ch3 = 0;
0923       if (ida == 0 || _extraBits(pid) > 0) { // ion or illegal
0924         return 0;
0925       } else if (sid > 0 && sid <= 100) { // Use table
0926         if (ida == 1000017 || ida == 1000018 || ida == 1000034) ch3 = 0;
0927         else if (ida > 1000050 && ida <= 1000060) ch3 = 0; // ?
0928         else if (ida > 50 && ida <= 60) ch3 = 0; // Generic DM
0929         else if (ida == 5100061 || ida == 5100062) ch3 = 6;
0930         else ch3 = ch100[sid-1];
0931       } else if (_digit(nj,pid) == 0) { // KL, Ks, or undefined
0932         return 0;
0933       } else if (isMeson(pid)) { // Mesons
0934         ch3 = ((q2 == 3 || q2 == 5) ? -1 : 1) * (ch100[q2-1] - ch100[q3-1]);
0935       } else if (isBaryon(pid)) { // Baryons
0936         ch3 = ch100[q3-1] + ch100[q2-1] + ch100[q1-1];
0937       } else if (isQBall(pid) ) { // QBall
0938         ch3 = 3*( (ida/10) % 10000);
0939       } else if (isHiddenValley(pid) ) { // Hidden Valley
0940         return 0;
0941       } else if (isDyon(pid) ) { // Dyon
0942         ch3 = 3*( (ida/10) %  1000) * (ql == 2 ? -1 : 1); //< NB. charge is flipped at the end if pid < 0
0943       } else if (isRHadron(pid) ) { // R-hadron
0944         /// @todo Is this sufficiently general? Why only gluino in g+q+qbar? Better to recurse to the related SM hadron code?
0945         if (q1 == 0 || q1 == 9) { //< gluino+q+qbar
0946           if (q2 == 3 || q2 == 5) {
0947             ch3 = ch100[q3-1] - ch100[q2-1];
0948           } else {
0949             ch3 = ch100[q2-1] - ch100[q3-1];
0950           }
0951         } else if (ql == 0) { //< squark+q+q
0952           ch3 = ch100[q3-1] + ch100[q2-1] + ch100[q1-1];
0953         } else if (_digit(nr,pid) == 0) { //< squark+q+q+q
0954           ch3 = ch100[q3-1] + ch100[q2-1] + ch100[q1-1] + ch100[ql-1];
0955         }
0956       } else if (isDiQuark(pid)) { // Diquarks
0957         ch3 = ch100[q2-1] + ch100[q1-1];
0958       } else { // Unknown
0959         return 0;
0960       }
0961       if (pid < 0) ch3 *= -1;
0962       return ch3;
0963     }
0964 
0965     /// Alias for charge3
0966     /// @deprecated Prefer charge3
0967     inline int threeCharge(int pid) {
0968       return charge3(pid);
0969     }
0970 
0971     /// Return the absolute value of 3 times the EM charge
0972     inline int abscharge3(int pid) {
0973       return std::abs(charge3(pid));
0974     }
0975 
0976     /// Return the EM charge (as floating point)
0977     inline double charge(int pid) {
0978       return charge3(pid)/3.0;
0979     }
0980 
0981     /// Return the EM charge (as floating point)
0982     inline double abscharge(int pid) {
0983       return std::abs(charge(pid));
0984     }
0985 
0986     /// @}
0987 
0988 
0989     /// @defgroup mcutils_charge_classes General PID-based classifier functions
0990     /// @{
0991 
0992     /// Determine if the particle is electrically charged
0993     inline bool isCharged(int pid) {
0994       return charge3(pid) != 0;
0995     }
0996 
0997     /// Determine if the particle is electrically neutral
0998     inline bool isNeutral(int pid) {
0999       return charge3(pid) == 0;
1000     }
1001 
1002     /// @}
1003 
1004 
1005     /// @defgroup mcutils_interactions Interaction classifiers
1006     /// @{
1007 
1008     /// Determine if the PID is that of a strongly interacting particle
1009     inline bool isStrongInteracting(int pid) {
1010       return isParton(pid) || isHadron(pid);
1011     }
1012 
1013     /// Determine if the PID is that of a electromagnetically interacting particle
1014     inline bool isEMInteracting(int pid) {
1015       return isCharged(pid) || isPhoton(pid);
1016     }
1017 
1018     /// Determine if the PID is that of a weakly interacting particle
1019     ///
1020     /// @note Photons are considered weak-interacting, as are all hadrons and
1021     /// leptons (we can't distinguish between L and R fermions at physical particle level).
1022     inline bool isWeakInteracting(int pid) {
1023       return !isGluon(pid) && !isGraviton(pid);
1024     }
1025 
1026     /// @}
1027 
1028 
1029     /// @defgroup mcutils_other Other classifiers
1030     /// @{
1031 
1032     /// Determine if the PID is in the generator-specific range
1033     inline bool isGenSpecific(int pid) {
1034       return in_range(pid, 80, 101);
1035     }
1036 
1037     /// Determine if the PID is that of an EW scale resonance
1038     ///
1039     /// @todo Also include SUSY, technicolor, etc. etc.? Maybe via a isStandardModel(pid) function, but there are stable BSM particles (in principle)
1040     inline bool isResonance(int pid) {
1041       return isW(pid) || isZ(pid) || isHiggs(pid) || isTop(pid);
1042     }
1043 
1044     /// Check the PID for usability in transport codes like Geant4
1045     ///
1046     /// @todo Should exclude neutrinos/LSP, since the ATLAS G4 interface deletes them immediately?
1047     /// @todo What about long-lived particles... could be BSM but need to be transported
1048     inline bool isTransportable(int pid) {
1049       // return !isResonance(pid) && !isParton(pid) && !isGenSpecific(pid);
1050       return isPhoton(pid) || isHadron(pid) || isLepton(pid);
1051     }
1052 
1053     /// @}
1054 
1055 
1056 
1057 
1058     /// @defgroup ppair_class Particle pair classifiers
1059     /// @todo Make versions that work on PdgIdPair?
1060     /// @{
1061 
1062     inline bool isSameSign(PdgId a, PdgId b) { return a*b >= 0; }
1063     inline bool isOppSign(PdgId a, PdgId b) { return !isSameSign(a, b); }
1064     inline bool isSameFlav(PdgId a, PdgId b) { return abs(a) == abs(b); }
1065     inline bool isOppFlav(PdgId a, PdgId b) { return !isSameFlav(a, b); }
1066 
1067     inline bool isOSSF(PdgId a, PdgId b) { return isOppSign(a, b) && isSameFlav(a, b); }
1068     inline bool isSSSF(PdgId a, PdgId b) { return isSameSign(a, b) && isSameFlav(a, b); }
1069     inline bool isOSOF(PdgId a, PdgId b) { return isOppSign(a, b) && isOppFlav(a, b); }
1070     inline bool isSSOF(PdgId a, PdgId b) { return isSameSign(a, b) && isOppFlav(a, b); }
1071 
1072     /// @}
1073 
1074 
1075   }
1076 }
1077 
1078 #endif