Source code for decaylanguage.utils.particleutils

# Copyright (c) 2018-2026, Eduardo Rodrigues and Henry Schreiner.
#
# Distributed under the 3-clause BSD license, see accompanying file LICENSE
# or https://github.com/scikit-hep/decaylanguage for details.


from __future__ import annotations

import re
from functools import lru_cache
from typing import Any

from particle import Particle, ParticleNotFound
from particle.converters import (
    EvtGen2PDGNameMap,
    EvtGenName2PDGIDBiMap,
    PDG2EvtGenNameMap,
)
from particle.exceptions import MatchingIDNotFound
from particle.particle.enums import Charge_mapping

cacher = lru_cache(maxsize=None)


[docs] @cacher def charge_conjugate_name(name: str, pdg_name: bool = False) -> str: """ Return the charge-conjugate particle name matching the given name. If no matching is found, return "ChargeConj(pname)". Note ---- Search/match in order: 1) Trivial case - does the name correspond to a self-conjugate particle? Only works for particles in the DB. 2) Try to match the antiparticle looking for the opposite PDG ID in the list of PDG IDs - EvtGen names. This can deal with specific particles or badly-known particles not in the DB but not so rare in decay files. Parameters ---------- name: str Input particle name. pdg_name: str, optional, default=False Input particle name is the PDG name, not the (default) EvtGen name. Returns ------- out: str Either the EvtGen or PDG charge-conjugate particle name depending on the value of parameter ``pdg_name``. """ if pdg_name: try: ccname = charge_conjugate_name(PDG2EvtGenNameMap[name]) # Convert the EvtGen name back to a PDG name, to match input type return EvtGen2PDGNameMap[ccname] except MatchingIDNotFound: # Catch issue in PDG2EvtGenNameMap matching return f"ChargeConj({name})" # Dealing only with EvtGen names at this stage try: return Particle.from_evtgen_name(name).invert().evtgen_name except Exception: try: return EvtGenName2PDGIDBiMap[-EvtGenName2PDGIDBiMap[name]] except Exception: return f"ChargeConj({name})"
[docs] def particle_from_string_name(name: str) -> Particle: """ Get a particle from an AmpGen style name. Note: the best match is returned. """ matches = particle_list_from_string_name(name) if matches: return matches[0] raise ParticleNotFound( f"Particle with AmpGen style name {name!r} not found in particle table" )
[docs] def particle_list_from_string_name(name: str) -> list[Particle]: "Get a list of particles from an AmpGen style name." # Forcible override particle = None short_name = name if "~" in name: short_name = name.replace("~", "") particle = False # Try the simplest searches first list_can = Particle.findall(name=name, particle=particle) if list_can: return list_can list_can = Particle.findall(pdg_name=short_name, particle=particle) if list_can: return list_can mat_str = _getname.match(short_name) if mat_str is None: return [] mat = mat_str.groupdict() if particle is False: mat["bar"] = "bar" try: return _from_group_dict_list(mat) except ParticleNotFound: return []
def _from_group_dict_list(mat: dict[str, Any]) -> list[Particle]: """ Internal helper class for the functions ``from_string`` and ``from_string_list`` for fuzzy finding of particle names used by AmpGen. """ kw: dict[str, Any] = { "particle": ( False if mat["bar"] is not None else True if mat["charge"] == "0" else None ) } name = mat["name"] if mat["family"]: if "_" in mat["family"]: mat["family"] = mat["family"].strip("_") name += f"({mat['family']})" if mat["state"]: name += f"({mat['state']})" if mat["star"]: name += "*" if mat["state"] is not None: kw["J"] = float(mat["state"]) maxname = name + f"({mat['mass']})" if mat["mass"] else name if "charge" in mat and mat["charge"] is not None: kw["three_charge"] = Charge_mapping[mat["charge"]] vals = Particle.findall(name=lambda x: maxname in x, **kw) if not vals: vals = Particle.findall(name=lambda x: name in x, **kw) if not vals: raise ParticleNotFound(f"Could not find particle {maxname} or {name}") if len(vals) > 1 and mat["mass"] is not None: vals = [val for val in vals if mat["mass"] in val.latex_name] if len(vals) > 1: return sorted(vals) return vals _getname = re.compile( r""" ^ # Beginning of string (?P<name> \w+? ) # One or more characters, non-greedy (?:\( (?P<family> [udsctb][\w]*) \) )? # Optional family like (s) (?:\( (?P<state> \d+ ) \) # Optional state in () (?= \*? \( ) )? # - lookahead for mass (?P<star> \* )? # Optional star (?:\( (?P<mass> \d+ ) \) )? # Optional mass in () (?P<bar> (bar|~) )? # Optional bar (?P<charge> [0\+\-][+-]?) # Required 0, -, --, or +, ++ $ # End of string """, re.VERBOSE, )