PDG Python API: decay query

PDG Python API: decay query#

import pdg

PDG = pdg.connect()

This creates a PdgApi instance containing the following type of objects:

{type(obj) for obj in PDG.get_all()}
{pdg.data.PdgLifetime,
 pdg.data.PdgMass,
 pdg.data.PdgProperty,
 pdg.data.PdgWidth,
 pdg.decay.PdgBranchingFraction,
 pdg.particle.PdgParticleList}

In this example, we ask the question which particles can decay to a final state with three equal particles? For this, we use PdgBranchingFractions, which contain information about particle decays in their description:

jpsi_decay = PDG.get("M070.313/2023")
type(jpsi_decay)
pdg.decay.PdgBranchingFraction
jpsi_decay.description
'J/psi(1S) --> rho(1700) pi --> pi+ pi- pi0'

So, let’s pull all the decay descriptions from the PDG and do some clean up with str.strip() and set:

from pdg.decay import PdgBranchingFraction

all_decays = {obj for obj in PDG.get_all() if isinstance(obj, PdgBranchingFraction)}
decay_descriptions = {dec.description.strip() for dec in all_decays}
len(decay_descriptions)
7245

To get more insight into the decay products, we create a new set of decay descriptions, but now describe each item as an initial state with a tuple of decay products. We again have to do a bit of cleaning here. The final state description sometimes contains digits, like "3pi0", which we want to be rendered as ("pi0", "pi0", "pi0").

Note that we decay all state descriptions in the decay chain into account. For example,

"J/psi(1S) --> rho(1700) pi --> pi+ pi- pi0"

has two ‘final’ states:

("rho(1700)", "pi")
("pi+", "pi-", "pi0")
Hide code cell source
def create_final_state(description: str) -> tuple[str, ...]:
    items = []
    for particle in description.split():
        particle = particle.strip()
        if particle in {"", ","}:
            continue
        multiplier = particle[0]
        if multiplier.isdigit():
            particles = int(multiplier) * particle[1:]
            items.extend(particles)
        else:
            items.append(particle)
    return tuple(sorted(items))


decays: set[tuple[str, tuple[str, ...]]] = set()
for description in decay_descriptions:
    initial_state, *final_states = description.split(" --> ")
    initial_state = initial_state.strip()
    decays.update(
        (initial_state, create_final_state(final_state)) for final_state in final_states
    )
len(decays)
7359

Now selecting the three-body decays is an easy matter using filters on comprehensions.

three_body_decays = {
    (initial_state, final_state)
    for initial_state, final_state in decays
    if len(final_state) == 3
}
len(three_body_decays)
2194
equal_state_3body_decays = {
    (initial_state, final_state)
    for initial_state, final_state in three_body_decays
    if len(set(final_state)) == 1
}
sorted(equal_state_3body_decays)
[('B0', ('K0S', 'K0S', 'K0S')),
 ('B0', ('a', 'a', 'a')),
 ('B_s()0', ('a', 'a', 'a')),
 ('B_s()0', ('phi', 'phi', 'phi')),
 ('J/psi(1S)', ('g', 'g', 'g')),
 ('J/psi(1S)', ('gamma', 'gamma', 'gamma')),
 ('Upsilon(1S)', ('g', 'g', 'g')),
 ('Upsilon(2S)', ('g', 'g', 'g')),
 ('Upsilon(3S)', ('g', 'g', 'g')),
 ('Z', ('g', 'g', 'g')),
 ('Z', ('gamma', 'gamma', 'gamma')),
 ('a_1(1260)', ('pi0', 'pi0', 'pi0')),
 ('a_1(1640)', ('pi', 'pi', 'pi')),
 ('pi_1(1600)', ('pi', 'pi', 'pi')),
 ('pi_2(1670)', ('pi0', 'pi0', 'pi0')),
 ('psi(2S)', ('g', 'g', 'g'))]

Finally, and optionally, we can filter out final states that are not well defined, such as g g g, by checking whether they are defined in the PDG database.

Hide code cell source
from pdg.errors import PdgAmbiguousValueError, PdgNoDataError

for initial_state, final_state in sorted(equal_state_3body_decays):
    try:
        for name in (initial_state, *final_state):
            PDG.get_particle_by_name(name)
    except (PdgAmbiguousValueError, PdgNoDataError):
        pass
    else:
        print(f"{initial_state:>20}{' '.join(final_state)}")
           J/psi(1S) → g g g
           J/psi(1S) → gamma gamma gamma
         Upsilon(1S) → g g g
         Upsilon(2S) → g g g
         Upsilon(3S) → g g g
                   Z → g g g
                   Z → gamma gamma gamma
             psi(2S) → g g g

Warning

Not all final state in the descriptions can be programmatically deciphered as individual particles. One could try to use regular expressions, but it’s hard to cover all cases. Consider for instance the following case which contains \(S\) and \(D\) waves.

[dec for dec in decay_descriptions if dec.startswith("a_1(1260)")]
['a_1(1260) --> K^*(892) K',
 'a_1(1260) --> ( rho(1450) pi )(S-wave) , rho --> pi pi',
 'a_1(1260) --> f_2(1270) pi , f_2() --> pi pi',
 'a_1(1260) --> f_0(500) pi , f_0() --> pi pi',
 'a_1(1260) --> f_0(980) pi , f_0() --> pi pi',
 'a_1(1260) --> pi0 pi0 pi0',
 'a_1(1260) --> pi+ pi- pi0',
 'a_1(1260) --> ( rho(1450) pi )(D-wave) , rho --> pi pi',
 'a_1(1260) --> pi gamma',
 'a_1(1260) --> ( rho pi )(D-wave) , rho --> pi pi',
 'a_1(1260) --> 3 pi',
 'a_1(1260) --> f_0(1370) pi , f_0() --> pi pi',
 'a_1(1260) --> K K pi',
 'a_1(1260) --> ( rho pi )(S-wave) , rho --> pi pi']

Additionally, not all decays seem to be included. Here is an attempt to find \(J/\psi \to \pi^0 \pi^0 \pi^0\).

Hide code cell source
import re

sorted(
    decay
    for decay in decay_descriptions
    if decay.startswith("J/psi") and re.match(r".*(3 ?pi|pi.*pi.*pi).*", decay)
)
['J/psi(1S) --> 2(pi+ pi- pi0)',
 'J/psi(1S) --> 2(pi+ pi- pi0) eta',
 'J/psi(1S) --> 2(pi+ pi-) 3pi0',
 'J/psi(1S) --> 2(pi+ pi-) pi0',
 'J/psi(1S) --> 3(pi+ pi-) pi0',
 'J/psi(1S) --> 4(pi+ pi-) pi0',
 'J/psi(1S) --> K+ K- pi0 pi0 pi0',
 'J/psi(1S) --> K0S K+- pi-+ pi+ pi-',
 'J/psi(1S) --> K0S K+- pi-+ pi0 pi0',
 'J/psi(1S) --> K^*(892)+ K0S pi- + c.c. --> K0S K0S pi+ pi-',
 'J/psi(1S) --> K^*(892)0 K- pi+ + c.c. --> K+ K- pi+ pi-',
 'J/psi(1S) --> K_2^*(1430)0 K- pi+ + c.c. --> K+ K- pi+ pi-',
 'J/psi(1S) --> a_2(1320)+ pi- pi0 + c.c --> 2 (pi+ pi- ) pi0',
 'J/psi(1S) --> a_2(1320)0 pi+ pi- --> 2 (pi+ pi- ) pi0',
 'J/psi(1S) --> eta pi+ pi- 3 pi0',
 'J/psi(1S) --> eta pi+ pi- pi0',
 'J/psi(1S) --> gamma pi+ pi- 2pi0',
 'J/psi(1S) --> omega 3 pi0',
 'J/psi(1S) --> omega pi+ pi+ pi- pi-',
 'J/psi(1S) --> omega pi+ pi- 2pi0',
 'J/psi(1S) --> omega pi+ pi- pi0',
 'J/psi(1S) --> omega pi0 --> pi+ pi- pi0',
 'J/psi(1S) --> p pbar pi+ pi- pi0',
 'J/psi(1S) --> phi f_1(1285) --> phi pi0 f_0(980) --> phi 3pi0',
 'J/psi(1S) --> phi f_1(1285) --> phi pi0 f_0(980) --> phi pi0 pi+ pi-',
 'J/psi(1S) --> phi pi0 f_0(980) --> phi pi0 p0 pi0',
 'J/psi(1S) --> phi pi0 f_0(980) --> phi pi0 pi+ pi-',
 'J/psi(1S) --> pi+ pi- 3pi0',
 'J/psi(1S) --> pi+ pi- 4 pi0',
 'J/psi(1S) --> pi+ pi- pi0',
 'J/psi(1S) --> pi+ pi- pi0 K+ K-',
 'J/psi(1S) --> pi+ pi- pi0 pi0 eta',
 'J/psi(1S) --> rho(1450) pi --> pi+ pi- pi0',
 'J/psi(1S) --> rho(1700) pi --> pi+ pi- pi0',
 'J/psi(1S) --> rho(2150) pi --> pi+ pi- pi0',
 'J/psi(1S) --> rho+ K+ K- pi- + c.c --> K+ K- pi+ pi- pi0',
 'J/psi(1S) --> rho+ rho- pi+ pi- pi0',
 'J/psi(1S) --> rho+- pi-+ pi+ pi- 2pi0',
 'J/psi(1S) --> rho+- pi-+ pi0 pi0',
 'J/psi(1S) --> rho_3(1690) pi --> pi+ pi- pi0']