| name | particle |
| description | Use when looking up particle properties (mass, charge, width, PDG ID, lifetime, spin, parity) from the PDG tables in Python: converting between particle names and PDG IDs, filtering decay modes, checking if a particle is stable, querying quantum numbers (J, P, C, G, I), working with MC generator output where particle codes need to be identified or converted (Geant3, Pythia, Corsika7, EvtGen), or using particle/PDGID literals in analysis code. |
particle
Overview
The particle library provides the full PDG particle table in Python. It wraps
PDG IDs, masses, widths, charges, lifetimes, decay modes, quantum numbers, and
particle names in a queryable object model. It is the standard Scikit-HEP tool
for particle identification tasks that would otherwise require hardcoding PDG ID
tables.
Two main classes:
PDGID — wraps an integer PDG ID with classification queries. Useful for fast
truth-particle filtering without loading the full table.
Particle — wraps a full PDG table entry with mass, width, quantum numbers,
decay modes, and name formatting.
When to Use
- Identifying particles in Monte Carlo truth records by PDG ID
- Looking up masses or widths for four-vector construction or selection cuts
- Filtering generator-level events by particle type, stability, or quantum
numbers
- Converting between generator-specific IDs (Geant3, Pythia, Corsika7, EvtGen)
and PDG IDs
- Checking antiparticle relations and charge conjugation
- Displaying human-readable or LaTeX particle names in plots
Key Concepts
| Attribute / method | Notes |
|---|
Particle.from_pdgid(id) | Look up by integer PDG ID (e.g. 211 = π+) |
Particle.from_name(name) | Look up by name string (e.g. "K+", "B0") |
Particle.from_evtgen_name(n) | Look up by EvtGen name (e.g. "J/psi") |
Particle.findall(fn, **kw) | Filter PDG table; callable, name-glob, or keyword args |
Particle.finditer(fn, **kw) | Same as findall but returns an iterator |
p.mass | Mass in MeV (float or None) — divide by 1000 for GeV |
p.width | Decay width in MeV (float or None) |
p.charge | Charge in units of e (float) |
p.lifetime | Lifetime in ns; inf for stable particles |
p.ctau | c·τ in mm |
p.is_stable | True if lifetime is effectively infinite |
p.is_self_conjugate | True for particles that are their own antiparticle (e.g. π0) |
p.J, p.P, p.C, p.G | Spin, parity, charge-conjugation, G-parity quantum numbers |
p.I | Isospin quantum number |
p.spin_type | SpinType enum (Scalar, Vector, …) |
p.pdgid | PDGID object — see PDGID properties below |
p.invert() | Returns the antiparticle |
p.describe() | Full human-readable property summary |
p.latex_name | LaTeX string (e.g. "$\\pi^{+}$") |
p.programmatic_name | Python identifier-safe name (e.g. "pi_plus") |
PDGID properties (on p.pdgid or standalone PDGID(id)):
| Property | Meaning |
|---|
.is_valid | PDG ID is a recognised particle |
.is_meson | Is a meson |
.is_baryon | Is a baryon |
.is_lepton | Is a lepton |
.is_hadron | Is a hadron (meson or baryon) |
.has_bottom | Contains a b quark |
.has_charm | Contains a c quark |
.has_strange | Contains an s quark |
.is_nucleus | Is a nuclear PDG ID |
.is_generator_specific | Generator-internal code (e.g. 9999999) |
.J | Total spin (float or None) |
.charge | Charge in units of e |
Canonical Patterns
Look up by PDG ID
from particle import Particle
p = Particle.from_pdgid(211)
print(p.name)
print(p.mass)
print(p.width)
print(p.charge)
print(p.lifetime)
print(p.ctau)
print(p.is_stable)
print(p.J, p.P)
print(p.latex_name)
Look up by name or EvtGen name
p = Particle.from_name("K+")
print(p.pdgid, p.mass)
p = Particle.from_evtgen_name("J/psi")
print(p.name, p.pdgid)
Search / filter with keyword args
Particle.findall(lambda p: p.pdgid.has_bottom and p.charge == 0, particle=True)
Particle.findall(pdg_name="pi")
Particle.findall(J=1, P=-1)
Particle.findall("K*")
from hepunits import meter
Particle.findall(
lambda p: p.pdgid.is_meson and p.pdgid.has_strange and p.ctau > 1 * meter,
particle=True,
)
for p in Particle.finditer(lambda p: p.pdgid.has_charm):
print(p.name, p.mass)
Antiparticles
pi_plus = Particle.from_name("pi+")
pi_minus = pi_plus.invert()
print(pi_minus.pdgid)
print(pi_plus.is_self_conjugate)
print(Particle.from_name("pi0").is_self_conjugate)
PDGID standalone (fast, no full table load)
from particle import PDGID
pid = PDGID(211)
print(pid.is_meson, pid.has_strange)
print(pid.is_valid)
bad = PDGID(99999999)
print(bad.is_valid)
from particle.pdgid import is_meson, has_bottom
print(is_meson(211))
print(has_bottom(5122))
Particle and PDGID literals
from particle import literals as lp
print(lp.pi_plus)
print(lp.Lambda_b_0.J)
from particle.pdgid import literals as lid
print(lid.pi_plus)
print(lid.Lambda_b_0.has_bottom)
MC generator ID converters
from particle import Particle, Geant3ID, PythiaID, Corsika7ID
g3id = Geant3ID(8)
p = Particle.from_pdgid(g3id.to_pdgid())
print(p.name)
pythiaid = PythiaID(211)
p = Particle.from_pdgid(pythiaid.to_pdgid())
cid = Corsika7ID(5)
p = Particle.from_pdgid(cid.to_pdgid())
print(p.name)
from particle.converters import Pythia2PDGIDBiMap
from particle import PDGID, PythiaID
pyid = Pythia2PDGIDBiMap[PDGID(9010221)]
pdgid = Pythia2PDGIDBiMap[PythiaID(10221)]
Decay modes (if available)
b0 = Particle.from_name("B0")
for mode in b0.decay_modes:
print(mode)
Use with generator truth (e.g. from pyhepmc)
from particle import PDGID
def classify_truth_particle(pdgid: int) -> str:
pid = PDGID(pdgid)
if not pid.is_valid:
return "generator_specific"
if pid.is_lepton:
return "lepton"
if pid.has_bottom:
return "b_hadron"
if pid.has_charm:
return "c_hadron"
return "other"
Describe a particle
p = Particle.from_pdgid(321)
print(p.describe())
Gotchas
- Masses in MeV, not GeV:
p.mass returns MeV — divide by 1000 for GeV.
This is consistent with all other ATLAS/Scikit-HEP tools.
pdgid is not a plain int: it's a PDGID object. Use int(p.pdgid) or
p.pdgid.numerator if you need a plain integer for numpy/awkward comparisons.
- Unknown PDG IDs raise
ParticleNotFound: wrap lookups with
try/except ParticleNotFound when processing MC output, where
generator-specific codes (e.g. 9999999) appear. PDGID.is_valid and
PDGID.is_generator_specific let you pre-filter without exceptions.
- Decay mode coverage is incomplete: not all particles have decay modes in
the PDG table;
b0.decay_modes may be an empty list.
findall vs finditer: findall returns a sorted list; finditer
returns a lazy iterator — prefer finditer for large scans to avoid
materialising the full result.
particle=True/False kwarg: without it, findall returns both particles
and antiparticles. Pass particle=True to restrict to particles only.
- Width of stable particles:
p.width returns None or negative sentinel
for particles where no width is meaningful. Check p.is_stable first.
Interop
- pyhepmc: Extract PDG IDs from
GenParticle.pid in HepMC3 truth records;
classify with PDGID or Particle.
- decaylanguage: Uses
particle internally for decay descriptor parsing;
shares the same particle name conventions.
- hepunits: Use alongside
particle for unit-safe comparisons —
p.ctau > 1 * meter works because ctau is in hepunits-native mm.
- vector / awkward: Convert
p.mass (MeV) to GeV before passing to
four-vector constructors that expect GeV.
Docs
https://github.com/scikit-hep/particle#readme