Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions agents/skills/particle/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
name: particle-usage
description: Use the Scikit-HEP `particle` library from Python code — PDG particle data lookups and searches (`Particle`), PDG ID queries (`PDGID` and the `particle.pdgid` functions), particle literals, MC generator ID classes (`PythiaID`, `Geant3ID`, `Corsika7ID`), EvtGen/LHCb name conversion, nuclei, and width/lifetime helpers. Use when writing application code that needs particle masses, widths, quantum numbers, antiparticle relations, or MC-ID conversions.
---

# Using the `particle` library

## Overview

`particle` is a pure-Python Scikit-HEP package providing a pythonic interface to the
Particle Data Group (PDG) particle data tables and the standard PDG particle
identification (ID) numbering scheme. It has two central objects:

- **`Particle`** — a full record for a particle (mass, width, lifetime, quantum
numbers, quark content, PDG/LaTeX/EvtGen/LHCb names, status), with powerful
search and look-up utilities.
- **`PDGID`** — an `int` subclass adding queries and classification on a PDG ID
(is it a meson/baryon/lepton/nucleus? which quarks? what charge and spin?),
mirrored by free-standing functions in `particle.pdgid`.

It also provides classes for the identification codes used by MC generators
(`PythiaID`, `Geant3ID`, `Corsika7ID`), name conversion maps (EvtGen, LHCb),
and `Particle`/`PDGID` literals for common particles.

All data tables ship inside the package and are loaded lazily: importing
`particle` does not read any data. The table is loaded on the first lookup
(`from_pdgid`, `findall`, `all`, …).

## When to use it

- You need PDG numbers — mass, width, lifetime, `c*tau` — in analysis/plotting code.
- You need to interpret or classify PDG IDs: meson/baryon/lepton/nucleus, quark
content, charge, spin.
- You need antiparticle relations or standardized names (PDG, LaTeX, HTML,
EvtGen, LHCb, programmatic).
- You need to convert between PDG IDs and MC generator IDs (Pythia, Geant3,
Corsika7).
- You want ready-made constants for common particles (literals).

## Core concepts

- **Two objects, one ID scheme.** `PDGID` wraps an integer; `Particle` wraps one
row of the PDG data tables. A negative PDG ID is the antiparticle; `-pid`
returns another `PDGID`.
- **The PDG ID encodes quantum numbers.** Classification, spin and quark content
are decoded from the ID digits; other properties (`charge`, `I`, `P`, `C`,
`G`, `rank`, `status`) come from the data tables.
- **Names.** `name` is display-ready (`'B+'`, `'B~0'`, `'K0'`), `pdg_name` is the
PDG table name _without_ charge (`'B'`, `'K'`); LaTeX/HTML/EvtGen/programmatic
variants exist too.
- **Search semantics.** A bare string is a _fuzzy_ substring search on `name`;
keyword arguments match _exactly_; values may be callables;
`particle=True/False` filters to particles/antiparticles.
- **Units.** Mass and width in MeV, `lifetime` in ns, `ctau` in mm. `charge`,
`J` and `I` are `Fraction`s, not floats.
- **Lazy table.** `Particle.all()`, `table_names()` and `table_loaded()` inspect
the loaded table; lookups trigger loading automatically.

## Essential patterns

```python
from particle import Particle, PDGID
from particle.pdgid import is_meson, has_charm

p = Particle.from_pdgid(521) # look up by ID (see references/particle-properties.md)
Particle.findall("pi", charge=-1) # fuzzy name + exact charge
Particle.findall(lambda q: q.pdgid.has_bottom and q.charge == 0)
is_meson(211) # classify a bare number
has_charm(p.pdgid) # pass the PDGID, not the Particle
```

For literals (`Particle`/`PDGID` aliases) and MC-ID conversions, see the
reference files below.

## Major gotchas

- **`pdg_name` ≠ `name`** — `pdg_name` has no charge; `from_name` needs an
exact, unique match (`from_name("pi")` raises).
- **Exceptions** — `from_pdgid` raises `InvalidParticle` (malformed ID) or
`ParticleNotFound` (valid but not in the table); MC ID conversions raise
`MatchingIDNotFound` (a `ValueError`, imported from `particle.exceptions`).
- **`None` means unknown** — quarks/neutrinos have `mass=None`, quarks have
`width=None`; `lifetime`/`ctau` are `None` when the width is unknown, but
`inf` for stable particles (`width == 0`).
- **Fractions** — `charge`, `J`, `I` return `fractions.Fraction`;
`three_charge` is the int charge × 3.
- **`particle.pdgid` functions take numbers**, not `Particle` objects
(`is_meson(p.pdgid)`, not `is_meson(p)`).
- **Proton/neutron have two PDG IDs** (baryon form `2112`/`2212` and nucleus
form `1000000010`/`1000010010`); equality considers both, `from_name("p")`
returns the baryon form.
- **Converters** — the `particle.converters` BiMaps (`Pythia2PDGIDBiMap`, …)
are deprecated since 1.0.0; use `X.to_pdgid()`/`X.from_pdgid()` instead.

## Reference files

| File | Contents |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [references/pdgid.md](references/pdgid.md) | `PDGID` queries, ID classification, validity, antiparticles, `particle.pdgid` functions |
| [references/particle-properties.md](references/particle-properties.md) | Particle lookups, properties, searches, literals, names, mass/width/lifetime, custom particles and data tables |
| [references/quantum-numbers.md](references/quantum-numbers.md) | Charge, spin, parity, isospin, flavour quantum numbers, and the related enums |
| [references/interoperability.md](references/interoperability.md) | MC generator IDs, EvtGen/LHCb names, `hepunits`, pandas/tabulate |

## Documentation

The repository's `README.md` is a full usage tutorial; `notebooks/ParticleDemo.ipynb`
contains worked examples. Docstrings on `Particle`, `PDGID`, `MCParticleID`,
`Corsika7ID` and the `particle.pdgid` functions carry precise semantics (units,
exceptions). `docs/CHANGELOG.md` lists user-visible changes and
`src/particle/data/README.rst` documents the bundled data files.
123 changes: 123 additions & 0 deletions agents/skills/particle/references/interoperability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Interoperability: MC IDs, names, hepunits, pandas

## MC generator ID classes

`PythiaID`, `Geant3ID` and `Corsika7ID` convert to and from `PDGID`
(`MatchingIDNotFound` is raised when no conversion exists; import it from
`particle.exceptions`). Geant4 uses the standard PDG IDs.

```python
from particle import PDGID, PythiaID, Geant3ID, Corsika7ID, Particle

pyid = PythiaID.from_pdgid(PDGID(9010221)) # <PythiaID: 10221>
pyid.to_pdgid() # <PDGID: 9010221>
pdg = Geant3ID(8).to_pdgid() # <PDGID: 211>
Particle.from_pdgid(pdg) # pi+
cid = Corsika7ID(5).to_pdgid() # <PDGID: -13>
```

**Corsika7 specifics** (Corsika8 uses Geant3 IDs):

```python
Corsika7ID.from_particle_description(6000) # (Corsika7ID(6), False) -> mu-
Corsika7ID.from_particle_description(
-6000
) # (Corsika7ID(6), True) — a (grand)mother particle
Corsika7ID(76).is_particle() # False — additional info, not a particle
Corsika7ID(76).name() # 'μ− add. info.'
```

Note: `Corsika7ID` arithmetic (e.g. `-Corsika7ID(5)`) returns a plain `int`,
unlike `-PDGID(...)`, which returns a `PDGID`.

**Custom ID classes** are easy if you know the mapping:

```python
from particle.mcid import MCParticleID


class MyGeneratorID(MCParticleID):
_to_pdg_map = {1: 11, 2: -11, 3: 22}


MyGeneratorID(3).to_pdgid() # <PDGID: 22>
MyGeneratorID.from_pdgid(PDGID(-11)) # <MyGeneratorID: 2>
```

The older converter objects in `particle.converters` (`Pythia2PDGIDBiMap`,
`Geant2PDGIDBiMap`, `Corsika72PDGIDBiMap`) are **deprecated since 1.0.0** — use
`to_pdgid()`/`from_pdgid()` instead.

## EvtGen and LHCb names

```python
Particle.from_evtgen_name("J/psi").pdgid # <PDGID: 443>
Particle.from_pdgid(443).evtgen_name # 'J/psi'
from particle import lhcb

lhcb.to_lhcb_name(Particle.from_pdgid(-531)) # 'B_s~0'
lhcb.from_lhcb_name("B_s~0") # <Particle: name="B(s)~0", ...>
```

The `particle.lhcb` module also exposes `LHCbName2PDGIDBiMap` for direct
bidirectional lookups. EvtGen names are the ones used in `.dec` decay files.

## hepunits

`particle` returns plain floats in HEP standard units (MeV, ns, mm). The
`hepunits` package (a strict dependency) provides unit-aware quantities that
compare naturally:

```python
from particle import Particle
from hepunits import GeV, meter

p = Particle.from_pdgid(521)
5.2 * GeV < p.mass < 5.3 * GeV # masses in MeV
p.ctau > 1 * meter # ctau in mm
```

## pandas and tabulate

`Particle.to_list()` and `Particle.to_dict()` render a search as tabular data.
`to_list` returns rows with a header row first; `to_dict` is transposed (field
name → tuple of values). They accept the same filter/keyword arguments as
`findall`, plus `exclusive_fields`, `exclude_fields` and `n_rows`.

```python
rows = Particle.to_list(
exclusive_fields=["pdgid", "name", "mass", "charge"],
filter_fn=lambda p: p.pdgid.is_lepton and p.charge != 0,
particle=True,
)
# rows[0] is the header; use with tabulate, or:
import pandas as pd

df = pd.DataFrame(rows[1:], columns=rows[0])
```

## NumPy and awkward arrays

`p.pdgid` is a `PDGID` (an `int` subclass). Convert with `int(p.pdgid)` when
you need a plain Python integer for NumPy/awkward arrays:

```python
import numpy as np
from particle import Particle

p = Particle.from_pdgid(521)
np.int64(int(p.pdgid)) # works fine
```

## Gotchas

- **Deprecated BiMaps** — do not use `Pythia2PDGIDBiMap` etc. from
`particle.converters`; use the ID-class methods.
- **`MatchingIDNotFound`** import path is `particle.exceptions` (it subclasses
`ValueError`); it is not re-exported at the top level.
- **Corsika7ID is not an antiparticle-preserving int** — arithmetic returns
plain `int`s; negative Corsika IDs in files indicate mother particles, so use
`from_particle_description`.
- **`Particle` has no decay data** — for branching fractions and decay chains
use the separate `decaylanguage` package (see
[particle-properties.md](particle-properties.md)).
Loading