Top

flavio.parameters module

Functions for parsing the parameter data files

"""Functions for parsing the parameter data files"""

import yaml
import pkgutil
from flavio.classes import *
from flavio.statistics.probability import *
from flavio._parse_errors import errors_from_string
import flavio
import re
from flavio.measurements import _fix_correlation_matrix
from math import sqrt
from particle import Particle, data as p_data


def _read_yaml_object_metadata(obj, constraints):
    parameters = yaml.safe_load(obj)
    for parameter_name, info in parameters.items():
        p = Parameter(parameter_name)
        if 'description' in info and info['description'] is not None:
            p.description = info['description']
        if 'tex' in info and info['tex'] is not None:
            p.tex = info['tex']

def read_file_metadata(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_metadata(f, constraints)

def _read_yaml_object_values(obj, constraints):
    parameters = yaml.safe_load(obj)
    for parameter_name, value in parameters.items():
        p = Parameter[parameter_name] # this will raise an error if the parameter doesn't exist!
        constraints.set_constraint(parameter_name, value)

def _read_yaml_object_new(obj):
    """Read parameter constraints from a YAML stream or file that are compatible
    with the format generated by the `get_yaml` method of
    `flavio.classes.ParameterConstraints`."""
    parameters = yaml.safe_load(obj)
    return ParameterConstraints.from_yaml_dict(parameters)

def _read_yaml_object_values_correlated(obj, constraints):
    list_ = yaml.safe_load(obj)
    for parameter_group in list_:
        parameter_names = []
        central_values = []
        errors = []
        for dict_list in parameter_group['values']:
            parameter_name, value = list(dict_list.items())[0]
            Parameter[parameter_name] # this will raise an error if the parameter doesn't exist!
            parameter_names.append(parameter_name)
            error_dict = errors_from_string(value)
            central_values.append(error_dict['central_value'])
            squared_error = 0.
            for sym_err in error_dict['symmetric_errors']:
                squared_error += sym_err**2
            for asym_err in error_dict['asymmetric_errors']:
                squared_error += asym_err[0]*asym_err[1]
            errors.append(sqrt(squared_error))
        correlation = _fix_correlation_matrix(parameter_group['correlation'], len(parameter_names))
        covariance = np.outer(np.asarray(errors), np.asarray(errors))*correlation
        if not np.all(np.linalg.eigvals(covariance) > 0):
            # if the covariance matrix is not positive definite, try a dirty trick:
            # multiply all the correlations by 0.99.
            n_dim = len(correlation)
            correlation = (correlation - np.eye(n_dim))*0.99 + np.eye(n_dim)
            covariance = np.outer(np.asarray(errors), np.asarray(errors))*correlation
            # if it still isn't positive definite, give up.
            assert np.all(np.linalg.eigvals(covariance) > 0), "The covariance matrix is not positive definite!" + str(covariance)
        constraints.add_constraint(parameter_names, MultivariateNormalDistribution(central_values, covariance))

def read_file(filename):
    """Read parameter values from a YAML file in the format generated by the
    `get_yaml` method of the `ParameterConstraints` class, returning a
    `ParameterConstraints` instance."""
    with open(filename, 'r') as f:
        return _read_yaml_object_new(f)

def read_file_values(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_values(f, constraints)

def read_file_values_correlated(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_values_correlated(f, constraints)

def write_file(filename, constraints):
    """Write parameter constraints to a YAML file."""
    with open(filename, 'w') as f:
        yaml.dump(constraints.get_yaml_dict(), f)

class FlavioParticle(Particle):
    """This class extends the `particle.Particle` class.

    Additional class methods
    ------------------------
    - from_flavio_name(flavio_name)
      returns a class instance for a given `flavio_name`
    - flavio_all()
      returns a set of all class instances used in flavio

    Additional properties
    ---------------------
    - flavio_name
      the particle name as used in flavio if defined, otherwise `None`
    - latex_name_simplified
      a simplified version of the latex name returned by `latex_name`
    - flavio_m
      a tuple with data on the particle mass as used in flavio, containing
      entries `name`, `tex`, `description`, `central`, `right`, `left`
    - flavio_tau
      a tuple with data on the particle lifetime as used in flavio, containing
      entries `name`, `tex`, `description`, `central`, `right`, `left`
    """

    PDG_PARTICLES = {
        'Bs': 531,
        'Bc': 541,
        'Bs*': 533,
        'B*+': 523,
        'B*0': 513,
        'B+': 521,
        'B0': 511,
        'Ds': 431,
        'Ds*': 433,
        'D+': 411,
        'D0': 421,
        'h': 25,
        'J/psi': 443,
        'KL': 130,
        'KS': 310,
        'K*+': 323,
        'K*0': 313,
        'K+': 321,
        'K0': 311,
        'Lambda': 3122,
        'Lambdab': 5122,
        'Lambdac': 4122,
        'omega': 223,
        'D*0': 423,
        'D*+': 413,
        'W': 24,
        'Z': 23,
        'e': 11,
        'eta': 221,
        'f0': 9010221,
        'mu': 13,
        'phi': 333,
        'pi+': 211,
        'pi0': 111,
        'psi(2S)': 100443,
        'rho+': 213,
        'rho0': 113,
        't': 6,
        'tau': 15,
        'u': 2,
        'p': 2212,
        'n': 2112,
    }
    _pdg_particles_inv = {v:k for k,v in PDG_PARTICLES.items()}
    _pdg_tex_regex = re.compile(
        r"^([A-Za-z\\/]+)" # latin or greek letters or slash
        r"(?:_\{(.*?)\})*" # _{...}
        r"(?:\^\{(.*?)\})*" # ^{...}
        r"(?:\((.*?)\))*" # (...)
        r"(?:\^\{(.*?)\})*" # ^{...}
    )

    @classmethod
    def from_flavio_name(cls, flavio_name):
        return cls.from_pdgid(cls.PDG_PARTICLES[flavio_name])

    @classmethod
    def flavio_all(cls):
        return {particle for particle in cls.all() if particle.flavio_name}

    @property
    def flavio_name(self):
        return self._pdg_particles_inv.get(self.pdgid, None)

    @property
    def latex_name_simplified(self):
        m = self._pdg_tex_regex.match(self.latex_name)
        if m is None:
            return self.latex_name
        name = m.group(1)
        sub = m.group(2)
        sup = (m.group(3) or '') + (m.group(5) or '')
        par = m.group(4)
        if sub or name in ('W', 'Z', 'H', 'e', '\\mu', '\\tau'):
            # remove superscripts +-0 and keep only *
            sup = '*' if '*' in sup else ''
        if not sub and par and not par.isdigit() and name != 'J/\\psi':
            # subscript absent and parantheses contain letter but not for 'J/\\psi'
            sub = par
        sub_tex = r'_{' + sub + r'}' if sub else ''
        sup_tex = r'^{' + sup + r'}' if sup else ''
        return name + sub_tex + sup_tex

    @property
    def flavio_m(self):
        name = 'm_' + self.flavio_name
        tex = r'$m_{' + self.latex_name_simplified + '}$'
        pole_mass = ' quark pole' if self.name == 't' else ''
        description = r'${}${} mass'.format(
            self.latex_name_simplified, pole_mass
        )
        central = self.mass*1e-3
        right = self.mass_upper*1e-3
        left = self.mass_lower*1e-3
        return name, tex, description, central, right, left

    @property
    def flavio_tau(self):
        if {self.width, self.width_upper, self.width_lower} & {None, 0}:
            return None
        name = 'tau_' + self.flavio_name
        tex = r'$\tau_{' + self.latex_name_simplified + '}$'
        description = r'${}$ lifetime'.format(self.latex_name_simplified)
        G_central = self.width*1e-3
        G_right = self.width_upper*1e-3
        G_left = self.width_lower*1e-3
        central = 1/G_central # life time = 1/width
        right = G_right/G_central**2
        left = G_left/G_central**2
        return name, tex, description, central, right, left

def read_pdg(year, constraints):
    """Read particle masses and widths from the PDG data file of a given year."""
    FlavioParticle.load_table(p_data.open_text(p_data, "particle{}.csv".format(year)))
    for particle in FlavioParticle.flavio_all():
        for data in (particle.flavio_m, particle.flavio_tau):
            if data is None:
                continue
            name, tex, description, central, right, left = data
            try:
                # if parameter already exists, remove existing constraints on it
                p = Parameter[name]
                constraints.remove_constraint(name)
            except KeyError:
                # otherwise, create it
                p = Parameter(name)
            p.tex = tex
            p.description = description
            if right == left:
                constraints.add_constraint([name],
                    NormalDistribution(central, right))
            else:
                constraints.add_constraint([name],
                    AsymmetricNormalDistribution(central,
                    right_deviation=right, left_deviation=left))



############### Read default parameters ###################

# Create the object
default_parameters = ParameterConstraints()

# read default parameters
default_parameters.read_default()

Module variables

var class_from_string

var config

var default_parameters

Functions

def read_file(

filename)

Read parameter values from a YAML file in the format generated by the get_yaml method of the ParameterConstraints class, returning a ParameterConstraints instance.

def read_file(filename):
    """Read parameter values from a YAML file in the format generated by the
    `get_yaml` method of the `ParameterConstraints` class, returning a
    `ParameterConstraints` instance."""
    with open(filename, 'r') as f:
        return _read_yaml_object_new(f)

def read_file_metadata(

filename, constraints)

Read parameter values from a YAML file.

def read_file_metadata(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_metadata(f, constraints)

def read_file_values(

filename, constraints)

Read parameter values from a YAML file.

def read_file_values(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_values(f, constraints)

def read_file_values_correlated(

filename, constraints)

Read parameter values from a YAML file.

def read_file_values_correlated(filename, constraints):
    """Read parameter values from a YAML file."""
    with open(filename, 'r') as f:
        _read_yaml_object_values_correlated(f, constraints)

def read_pdg(

year, constraints)

Read particle masses and widths from the PDG data file of a given year.

def read_pdg(year, constraints):
    """Read particle masses and widths from the PDG data file of a given year."""
    FlavioParticle.load_table(p_data.open_text(p_data, "particle{}.csv".format(year)))
    for particle in FlavioParticle.flavio_all():
        for data in (particle.flavio_m, particle.flavio_tau):
            if data is None:
                continue
            name, tex, description, central, right, left = data
            try:
                # if parameter already exists, remove existing constraints on it
                p = Parameter[name]
                constraints.remove_constraint(name)
            except KeyError:
                # otherwise, create it
                p = Parameter(name)
            p.tex = tex
            p.description = description
            if right == left:
                constraints.add_constraint([name],
                    NormalDistribution(central, right))
            else:
                constraints.add_constraint([name],
                    AsymmetricNormalDistribution(central,
                    right_deviation=right, left_deviation=left))

def write_file(

filename, constraints)

Write parameter constraints to a YAML file.

def write_file(filename, constraints):
    """Write parameter constraints to a YAML file."""
    with open(filename, 'w') as f:
        yaml.dump(constraints.get_yaml_dict(), f)

Classes

class FlavioParticle

This class extends the particle.Particle class.

Additional class methods

  • from_flavio_name(flavio_name) returns a class instance for a given flavio_name
  • flavio_all() returns a set of all class instances used in flavio

Additional properties

  • flavio_name the particle name as used in flavio if defined, otherwise None
  • latex_name_simplified a simplified version of the latex name returned by latex_name
  • flavio_m a tuple with data on the particle mass as used in flavio, containing entries name, tex, description, central, right, left
  • flavio_tau a tuple with data on the particle lifetime as used in flavio, containing entries name, tex, description, central, right, left
class FlavioParticle(Particle):
    """This class extends the `particle.Particle` class.

    Additional class methods
    ------------------------
    - from_flavio_name(flavio_name)
      returns a class instance for a given `flavio_name`
    - flavio_all()
      returns a set of all class instances used in flavio

    Additional properties
    ---------------------
    - flavio_name
      the particle name as used in flavio if defined, otherwise `None`
    - latex_name_simplified
      a simplified version of the latex name returned by `latex_name`
    - flavio_m
      a tuple with data on the particle mass as used in flavio, containing
      entries `name`, `tex`, `description`, `central`, `right`, `left`
    - flavio_tau
      a tuple with data on the particle lifetime as used in flavio, containing
      entries `name`, `tex`, `description`, `central`, `right`, `left`
    """

    PDG_PARTICLES = {
        'Bs': 531,
        'Bc': 541,
        'Bs*': 533,
        'B*+': 523,
        'B*0': 513,
        'B+': 521,
        'B0': 511,
        'Ds': 431,
        'Ds*': 433,
        'D+': 411,
        'D0': 421,
        'h': 25,
        'J/psi': 443,
        'KL': 130,
        'KS': 310,
        'K*+': 323,
        'K*0': 313,
        'K+': 321,
        'K0': 311,
        'Lambda': 3122,
        'Lambdab': 5122,
        'Lambdac': 4122,
        'omega': 223,
        'D*0': 423,
        'D*+': 413,
        'W': 24,
        'Z': 23,
        'e': 11,
        'eta': 221,
        'f0': 9010221,
        'mu': 13,
        'phi': 333,
        'pi+': 211,
        'pi0': 111,
        'psi(2S)': 100443,
        'rho+': 213,
        'rho0': 113,
        't': 6,
        'tau': 15,
        'u': 2,
        'p': 2212,
        'n': 2112,
    }
    _pdg_particles_inv = {v:k for k,v in PDG_PARTICLES.items()}
    _pdg_tex_regex = re.compile(
        r"^([A-Za-z\\/]+)" # latin or greek letters or slash
        r"(?:_\{(.*?)\})*" # _{...}
        r"(?:\^\{(.*?)\})*" # ^{...}
        r"(?:\((.*?)\))*" # (...)
        r"(?:\^\{(.*?)\})*" # ^{...}
    )

    @classmethod
    def from_flavio_name(cls, flavio_name):
        return cls.from_pdgid(cls.PDG_PARTICLES[flavio_name])

    @classmethod
    def flavio_all(cls):
        return {particle for particle in cls.all() if particle.flavio_name}

    @property
    def flavio_name(self):
        return self._pdg_particles_inv.get(self.pdgid, None)

    @property
    def latex_name_simplified(self):
        m = self._pdg_tex_regex.match(self.latex_name)
        if m is None:
            return self.latex_name
        name = m.group(1)
        sub = m.group(2)
        sup = (m.group(3) or '') + (m.group(5) or '')
        par = m.group(4)
        if sub or name in ('W', 'Z', 'H', 'e', '\\mu', '\\tau'):
            # remove superscripts +-0 and keep only *
            sup = '*' if '*' in sup else ''
        if not sub and par and not par.isdigit() and name != 'J/\\psi':
            # subscript absent and parantheses contain letter but not for 'J/\\psi'
            sub = par
        sub_tex = r'_{' + sub + r'}' if sub else ''
        sup_tex = r'^{' + sup + r'}' if sup else ''
        return name + sub_tex + sup_tex

    @property
    def flavio_m(self):
        name = 'm_' + self.flavio_name
        tex = r'$m_{' + self.latex_name_simplified + '}$'
        pole_mass = ' quark pole' if self.name == 't' else ''
        description = r'${}${} mass'.format(
            self.latex_name_simplified, pole_mass
        )
        central = self.mass*1e-3
        right = self.mass_upper*1e-3
        left = self.mass_lower*1e-3
        return name, tex, description, central, right, left

    @property
    def flavio_tau(self):
        if {self.width, self.width_upper, self.width_lower} & {None, 0}:
            return None
        name = 'tau_' + self.flavio_name
        tex = r'$\tau_{' + self.latex_name_simplified + '}$'
        description = r'${}$ lifetime'.format(self.latex_name_simplified)
        G_central = self.width*1e-3
        G_right = self.width_upper*1e-3
        G_left = self.width_lower*1e-3
        central = 1/G_central # life time = 1/width
        right = G_right/G_central**2
        left = G_left/G_central**2
        return name, tex, description, central, right, left

Ancestors (in MRO)

Class variables

var PDG_PARTICLES

Static methods

def __init__(

self, pdgid, pdg_name, mass=-1.0, mass_upper=-1.0, mass_lower=-1.0, width=-1.0, width_upper=-1.0, width_lower=-1.0, three_charge=<Charge.u: 50>, I=None, G=<Parity.u: 5>, P=<Parity.u: 5>, C=<Parity.u: 5>, anti_flag=<Inv.Same: 0>, rank=0, status=<Status.NotInPDT: 4>, quarks='', latex_name='Unknown')

Method generated by attrs for class Particle.

def __init__(self, pdgid, pdg_name, mass=attr_dict['mass'].default, mass_upper=attr_dict['mass_upper'].default, mass_lower=attr_dict['mass_lower'].default, width=attr_dict['width'].default, width_upper=attr_dict['width_upper'].default, width_lower=attr_dict['width_lower'].default, three_charge=attr_dict['_three_charge'].default, I=attr_dict['I'].default, G=attr_dict['G'].default, P=attr_dict['P'].default, C=attr_dict['C'].default, anti_flag=attr_dict['anti_flag'].default, rank=attr_dict['rank'].default, status=attr_dict['status'].default, quarks=attr_dict['quarks'].default, latex_name=attr_dict['latex_name'].default):
    self.pdgid = __attr_converter_pdgid(pdgid)
    self.pdg_name = pdg_name
    self.mass = __attr_converter_mass(mass)
    self.mass_upper = __attr_converter_mass_upper(mass_upper)
    self.mass_lower = __attr_converter_mass_lower(mass_lower)
    self.width = __attr_converter_width(width)
    self.width_upper = __attr_converter_width_upper(width_upper)
    self.width_lower = __attr_converter_width_lower(width_lower)
    self._three_charge = __attr_converter__three_charge(three_charge)
    self.I = __attr_converter_I(I)
    self.G = __attr_converter_G(G)
    self.P = __attr_converter_P(P)
    self.C = __attr_converter_C(C)
    self.anti_flag = __attr_converter_anti_flag(anti_flag)
    self.rank = rank
    self.status = __attr_converter_status(status)
    self.quarks = __attr_converter_quarks(quarks)
    self.latex_name = latex_name

def describe(

self)

Make a nice high-density string for a particle's properties.

def describe(self):
    # type: () -> str
    "Make a nice high-density string for a particle's properties."
    if self.pdgid == 0:
        return "Name: Unknown"
    val = """Name: {self!s:<14} ID: {self.pdgid:<12} Latex: {latex_name}
  = {mass}
th_or_lifetime}
harge)        = {Q:<6}  J (total angular) = {self.J!s:<7}  P (space parity) = {P}
harge parity) = {C:<6}  I (isospin)       = {self.I!s:<7}  G (G-parity)     = {G}
format(
        self=self,
        G=Parity_undo[self.G],
        C=Parity_undo[self.C],
        Q=self._str_charge(),
        P=Parity_undo[self.P],
        mass=self._str_mass(),
        width_or_lifetime=self._width_or_lifetime(),
        latex_name=self._repr_latex_(),
    )
    if self.spin_type != SpinType.Unknown:
        val += "    SpinType: {self.spin_type!s}\n".format(self=self)
    if self.quarks:
        val += "    Quarks: {self.quarks}\n".format(self=self)
    val += "    Antiparticle name: {iself.name} (antiparticle status: {self.anti_flag.name})".format(
        iself=self.invert(), self=self
    )
    return val

def invert(

self)

Get the antiparticle.

def invert(self):
    # type: () -> Particle
    "Get the antiparticle."
    if self.anti_flag == Inv.Barred or (
        self.anti_flag == Inv.ChargeInv and self.three_charge != Charge.o
    ):
        return self.from_pdgid(-self.pdgid)
    else:
        return copy(self)

Instance variables

var C

var G

var I

var J

The total spin J quantum number.

Note that the returned value corresponds to that effectively encoded in the particle PDG ID.

var L

The orbital angular momentum L quantum number (None if not a meson).

Note that the returned value corresponds to that effectively encoded in the particle PDG ID.

var P

var S

The spin S quantum number (None if not a meson).

Note that the returned value corresponds to that effectively encoded in the particle PDG ID.

var anti_flag

var charge

The particle charge, in units of the positron charge.

Design note: the particle charge can also be retrieved from the particle PDG ID. To allow for user-defined particles, it is necessary to rather store the particle charge in the data tables themselves. Consistency of both ways of retrieving the particle charge is guaranteed for all PDG table particles.

var ctau

The particle c*tau, in millimeters.

None is returned if the particle width (stored in the DB) is unknown.

var evtgen_name

This is the name used in EvtGen.

var flavio_m

var flavio_name

var flavio_tau

var html_name

This is the name using HTML instead of LaTeX.

var is_name_barred

Check to see if particle is inverted (hence is it an antiparticle) and has a bar in its name.

var is_self_conjugate

Is the particle self-conjugate, i.e. its own antiparticle?

var is_unflavoured_meson

Unflavoured mesons are self-conjugate (hence zero-charge) mesons with all their flavour (strange, charm, bottom and top) quantum numbers equal to zero.

var latex_name

var latex_name_simplified

var lifetime

The particle lifetime, in nanoseconds.

None is returned if the particle width (stored in the DB) is unknown.

var mass

var mass_lower

var mass_upper

var name

The nice name, with charge added, and a tilde for an antiparticle, if relevant.

var pdg_name

var pdgid

var programmatic_name

This name could be used for a variable name.

var quarks

var rank

var spin_type

Access the SpinType enum.

Note that this is relevant for bosons only. SpinType.NonDefined is returned otherwise.

var status

var three_charge

Three times the particle charge (charge * 3), in units of the positron charge.

var width

var width_lower

var width_upper

Methods

def all(

cls)

Access, hence get hold of, the internal particle data CSV table, loading it from the default location if no table has yet been loaded. All Particle (instances) are returned as a list.

@classmethod
def all(cls):
    # type: () -> Set[Particle]
    """
    Access, hence get hold of, the internal particle data CSV table,
    loading it from the default location if no table has yet been loaded.
    All `Particle` (instances) are returned as a list.
    """
    if not cls.table_loaded():
        cls.load_table()
    return cls._table if cls._table is not None else set()

def dump_table(

cls, exclusive_fields=(), exclude_fields=(), n_rows=-1, filter_fn=None, filename=None, tablefmt='simple', floatfmt='.12g', numalign='decimal')

Dump the internal particle data CSV table, loading it from the default location if no table has yet been loaded.

The table attributes are those of the class. By default all attributes are used as table fields. Their complete list is: pdgid pdg_name mass mass_upper mass_lower width width_upper width_lower three_charge I G P C anti_flag rank status quarks latex_name

Optionally dump to a file.

Parameters

exclusive_fields: list, optional, default is [] Exclusive list of fields to print out. exclude_fields: list, optional, default is [] List of table fields to exclude in the printout. Relevant only when exclusive_fields is not given. n_rows: int, optional, defaults to all rows Number of table rows to print out. filter_fn: function, optional, default is None Apply a filter to each particle. See findall(...) for typical use cases. filename: str, optional, default is None Name of file where to dump the table. By default the table is dumped to stdout. tablefmt: str, optional, default is 'simple' Table formatting option, see the tabulate's package tabulate function for a description of available options. The most common options are: 'plain', 'simple', 'grid', 'rst', 'html', 'latex'. floatfmt: str, optional, default is '.12g' Number formatting, see the tabulate's package tabulate function for a description of available options. numalign: str or None, oprional, default is 'decimal' Column alignment for numbers, see the tabulate's package tabulate function for a description of available options.

Returns

str or None if filename is None or not, respectively.

Note

Uses the tabulate package.

Examples

print(Particle.dump_table()) print(Particle.dump_table(n_rows=5)) print(Particle.dump_table(exclusive_fields=['pdgid', 'pdg_name'])) print(Particle.dump_table(filter_fn=lambda p: p.pdgid.has_bottom)) Particle.dump_table(filename='output.txt', tablefmt='rst')

@classmethod
def dump_table(
    cls,
    exclusive_fields=(),  # type: Iterable[str]
    exclude_fields=(),  # type: Iterable[str]
    n_rows=-1,
    filter_fn=None,  # type: Optional[Callable[[Particle], bool]]
    filename=None,  # type: Optional[str]
    tablefmt="simple",
    floatfmt=".12g",
    numalign="decimal",
):
    # type: (...) -> Optional[str]
    """
    Dump the internal particle data CSV table,
    loading it from the default location if no table has yet been loaded.
    The table attributes are those of the class. By default all attributes
    are used as table fields. Their complete list is:
        pdgid
        pdg_name
        mass
        mass_upper
        mass_lower
        width
        width_upper
        width_lower
        three_charge
        I
        G
        P
        C
        anti_flag
        rank
        status
        quarks
        latex_name
    Optionally dump to a file.
    Parameters
    ----------
    exclusive_fields: list, optional, default is []
        Exclusive list of fields to print out.
    exclude_fields: list, optional, default is []
        List of table fields to exclude in the printout.
        Relevant only when exclusive_fields is not given.
    n_rows: int, optional, defaults to all rows
        Number of table rows to print out.
    filter_fn: function, optional, default is None
        Apply a filter to each particle.
        See findall(...) for typical use cases.
    filename: str, optional, default is None
        Name of file where to dump the table.
        By default the table is dumped to stdout.
    tablefmt: str, optional, default is 'simple'
        Table formatting option, see the tabulate's package
        tabulate function for a description of available options.
        The most common options are:
        'plain', 'simple', 'grid', 'rst', 'html', 'latex'.
    floatfmt: str, optional, default is '.12g'
        Number formatting, see the tabulate's package
        tabulate function for a description of available options.
    numalign: str or None, oprional, default is 'decimal'
        Column alignment for numbers, see the tabulate's package
        tabulate function for a description of available options.
    Returns
    -------
    str or None if filename is None or not, respectively.
    Note
    ----
    Uses the `tabulate` package.
    Examples
    --------
    print(Particle.dump_table())
    print(Particle.dump_table(n_rows=5))
    print(Particle.dump_table(exclusive_fields=['pdgid', 'pdg_name']))
    print(Particle.dump_table(filter_fn=lambda p: p.pdgid.has_bottom))
    Particle.dump_table(filename='output.txt', tablefmt='rst')
    """
    from tabulate import tabulate
    if not cls.table_loaded():
        cls.load_table()
    # Get all table headers from the class attributes
    tbl_names = [a.name for a in Particle.__attrs_attrs__]  # type: ignore
    # ... and replace '_three_charge' with the better, public property
    tbl_names[tbl_names.index("_three_charge")] = "three_charge"
    if exclusive_fields:
        tbl_names = list(exclusive_fields)
    else:
        for fld in exclude_fields:
            try:
                tbl_names.remove(fld)
            except:
                pass
    # Start with the full table
    tbl_all = sorted(cls.all())
    # Apply a filter, if specified
    if filter_fn is not None:
        tbl_all = cls.findall(filter_fn)
    # In any case, only dump a given number of rows?
    if n_rows >= 0:
        tbl_all = tbl_all[:n_rows]
    # Build all table rows
    tbl = []
    for p in tbl_all:
        tbl.append([getattr(p, attr) for attr in tbl_names])
    if filename:
        filename = str(filename)  # Conversion to handle pathlib on Python < 3.6
        with open(filename, "w") as outfile:
            print(
                tabulate(
                    tbl,
                    headers=tbl_names,
                    tablefmt=tablefmt,
                    floatfmt=floatfmt,
                    numalign=numalign,
                ),
                file=outfile,
            )
        return None
    else:
        return tabulate(
            tbl,
            headers=tbl_names,
            tablefmt=tablefmt,
            floatfmt=floatfmt,
            numalign=numalign,
        )

def empty(

cls)

Make a new empty particle.

@classmethod
def empty(cls):
    # type: () -> Particle
    "Make a new empty particle."
    return cls(0, "Unknown", anti_flag=Inv.Same)

def find(

cls, *args, **search_terms)

Require that the search returns one and only one result. The method otherwise raises a ParticleNotFound or RuntimeError exception.

See findall for full listing of parameters.

Raises

ParticleNotFound If no matching particle is found in the loaded data table(s). RuntimeError If too many particles match the search criteria.

@classmethod
def find(cls, *args, **search_terms):
    # type: (...) -> Particle
    """
    Require that the search returns one and only one result.
    The method otherwise raises a ParticleNotFound or RuntimeError exception.
    See `findall` for full listing of parameters.
    Raises
    ------
    ParticleNotFound
        If no matching particle is found in the loaded data table(s).
    RuntimeError
        If too many particles match the search criteria.
    """
    results = cls.findall(*args, **search_terms)
    if len(results) == 1:
        return results[0]
    elif len(results) == 0:
        raise ParticleNotFound(
            "Did not find particle matching query: {}".format(search_terms)
        )
    else:
        raise RuntimeError("Found too many particles")

def findall(

cls, filter_fn=None, particle=None, **search_terms)

Search for a particle, returning a list of candidates.

The first and only positional argument is given each particle candidate, and returns True/False. Example:

>>> Particle.findall(lambda p: 'p' in p.name)    # doctest: +SKIP
# Returns list of all particles with p somewhere in name

You can pass particle=True/False to force a particle or antiparticle. If this is not callable, it will do a "fuzzy" search on the name. So this is identical:

>>> Particle.findall('p')    # doctest: +SKIP
# Returns list of all particles with p somewhere in name (same as example above)

You can also pass keyword arguments, which are either called with the matching property if they are callable, or are compared if they are not. This would do an exact search on the name, instead of a fuzzy search:

Returns proton and antiproton only

Particle.findall(pdg_name='p') # doctest: +NORMALIZE_WHITESPACE [, , , ]

Returns proton only

Particle.findall(pdg_name='p', particle=True) # doctest: +NORMALIZE_WHITESPACE [, ]

Versatile searches require a (lambda) function as argument:

Get all neutral beauty hadrons

Particle.findall(lambda p: p.pdgid.has_bottom and p.charge==0) # doctest: +SKIP

Trivially find all pseudoscalar charm mesons

Particle.findall(lambda p: p.pdgid.is_meson and p.pdgid.has_charm and p.spin_type==SpinType.PseudoScalar) # doctest: +SKIP

See also find, which throws an exception if the particle is not found or too many are found.

@classmethod
def findall(
    cls,
    filter_fn=None,  # type: Optional[Callable[[Particle], bool]]
    particle=None,  # type: Optional[bool]
    **search_terms  # type: Any
):
    # type: (...) -> List[Particle]
    """
    Search for a particle, returning a list of candidates.
    The first and only positional argument is given each particle
    candidate, and returns True/False. Example:
        >>> Particle.findall(lambda p: 'p' in p.name)    # doctest: +SKIP
        # Returns list of all particles with p somewhere in name
    You can pass particle=True/False to force a particle or antiparticle.
    If this is not callable, it will do a "fuzzy" search on the name. So this is identical:
        >>> Particle.findall('p')    # doctest: +SKIP
        # Returns list of all particles with p somewhere in name (same as example above)
    You can also pass keyword arguments, which are either called with the
    matching property if they are callable, or are compared if they are not.
    This would do an exact search on the name, instead of a fuzzy search:
       >>> # Returns proton and antiproton only
       >>> Particle.findall(pdg_name='p')    # doctest: +NORMALIZE_WHITESPACE
       [<Particle: name="p", pdgid=2212, mass=938.272081 ± 0.000006 MeV>,
        <Particle: name="p~", pdgid=-2212, mass=938.272081 ± 0.000006 MeV>,
        <Particle: name="p", pdgid=1000010010, mass=938.272081 ± 0.000006 MeV>,
        <Particle: name="p~", pdgid=-1000010010, mass=938.272081 ± 0.000006 MeV>]
       >>> # Returns proton only
       >>> Particle.findall(pdg_name='p', particle=True)    # doctest: +NORMALIZE_WHITESPACE
       [<Particle: name="p", pdgid=2212, mass=938.272081 ± 0.000006 MeV>,
       <Particle: name="p", pdgid=1000010010, mass=938.272081 ± 0.000006 MeV>]
    Versatile searches require a (lambda) function as argument:
    >>> # Get all neutral beauty hadrons
    >>> Particle.findall(lambda p: p.pdgid.has_bottom and p.charge==0)    # doctest: +SKIP
    >>>
    >>> # Trivially find all pseudoscalar charm mesons
    >>> Particle.findall(lambda p: p.pdgid.is_meson and p.pdgid.has_charm and p.spin_type==SpinType.PseudoScalar)  # doctest: +SKIP
    See also ``find``, which throws an exception if the particle is not found or too many are found.
    """
    # Note that particle can be called by position to keep compatibility with Python 2, but that behavior should
    # not be used and will be removed when support for Python 2.7 is dropped.
    results = set()
    # Filter out values
    for item in cls.all():
        # At this point, continue if a match fails
        # particle=True is particle, False is antiparticle, and None is both
        if particle is not None:
            if particle and int(item) < 0:
                continue
            elif (not particle) and int(item) > 0:
                continue
        # If a filter function is passed, evaluate and skip if False
        if filter_fn is not None:
            if callable(filter_fn):
                # Just skip exceptions, which are there for good reasons
                # Example: calls to Particle.ctau for particles given
                #          default negative, and hence invalid, widths
                try:
                    if not filter_fn(item):
                        continue
                except TypeError:  # skip checks such as 'lambda p: p.width > 0',
                    continue  # which fail when width=None
            else:
                if not (filter_fn in item.name):
                    continue
        # At this point, if you break, you will not add a match
        for term, value in search_terms.items():
            # If pvalue cannot be accessed, skip this particle
            # (invalid lifetime, for example)
            try:
                pvalue = getattr(item, term)
            except ValueError:
                break
            # Callables are supported
            if callable(value):
                try:
                    if not value(pvalue):
                        break
                except TypeError:  # catch issues with None values
                    break
            # And, finally, just compare if nothing else matched
            elif pvalue != value:
                break
        # If the loop was not broken
        else:
            results.add(item)
    # Matches are sorted so the top one is "best"
    return sorted(results)

def flavio_all(

cls)

@classmethod
def flavio_all(cls):
    return {particle for particle in cls.all() if particle.flavio_name}

def from_evtgen_name(

cls, name)

Get a particle from an EvtGen particle name, as in .dec decay files.

Raises

ParticleNotFound If from_pdgid returns no match. MatchingIDNotFound If the matching EvtGen name - PDG ID done internally is unsuccessful.

@classmethod
def from_evtgen_name(cls, name):
    # type: (str) -> Particle
    """
    Get a particle from an EvtGen particle name, as in .dec decay files.
    Raises
    ------
    ParticleNotFound
        If `from_pdgid` returns no match.
    MatchingIDNotFound
        If the matching EvtGen name - PDG ID done internally is unsuccessful.
    """
    return cls.from_pdgid(EvtGenName2PDGIDBiMap[name])

def from_flavio_name(

cls, flavio_name)

@classmethod
def from_flavio_name(cls, flavio_name):
    return cls.from_pdgid(cls.PDG_PARTICLES[flavio_name])

def from_pdgid(

cls, value)

Get a particle from a PDGID. Uses by default the package extended PDG data table.

Raises

InvalidParticle If the input PDG ID is an invalid identification code. ParticleNotFound If no matching PDG ID is found in the loaded data table(s).

@classmethod
def from_pdgid(cls, value):
    # type: (SupportsInt) -> Particle
    """
    Get a particle from a PDGID. Uses by default the package
    extended PDG data table.
    Raises
    ------
    InvalidParticle
        If the input PDG ID is an invalid identification code.
    ParticleNotFound
        If no matching PDG ID is found in the loaded data table(s).
    """
    if not is_valid(value):
        raise InvalidParticle("Input PDGID {0} is invalid!".format(value))
    for item in cls.all():
        if item.pdgid == value:
            return item
    else:
        raise ParticleNotFound("Could not find PDGID {0}".format(value))

def from_string(

cls, name)

Get a particle from a PDG style name - returns the best match.

@classmethod
def from_string(cls, name):
    # type: (str) -> Particle
    "Get a particle from a PDG style name - returns the best match."
    matches = cls.from_string_list(name)
    if matches:
        return matches[0]
    else:
        raise ParticleNotFound("{0} not found in particle table".format(name))

def from_string_list(

cls, name)

Get a list of particles from a PDG style name.

@classmethod
def from_string_list(cls, name):
    # type: (str) -> List[Particle]
    "Get a list of particles from a PDG 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 = cls.findall(name=name, particle=particle)
    if list_can:
        return list_can
    else:
        list_can = cls.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 cls._from_group_dict_list(mat)
    except ParticleNotFound:
        return []

def load_table(

cls, filename=None, append=False, _name=None)

Load a particle data CSV table. Optionally append to the existing data already loaded if append=True. As a special case, if this is called with append=True and the table is not loaded, the default will be loaded first before appending (set append=False if you don't want this behavior).

A parameter is also included that should be considered private for now. It is _name, which will override the filename for the stored filename in _table_names.

@classmethod
def load_table(cls, filename=None, append=False, _name=None):
    # type: (Union[None, str, TextIO], bool, Optional[str]) -> None
    """
    Load a particle data CSV table. Optionally append to the existing data already loaded if append=True.
    As a special case, if this is called with append=True and the table is not loaded, the default will
    be loaded first before appending (set append=False if you don't want this behavior).
    A parameter is also included that should be considered private for now. It is _name, which
    will override the filename for the stored filename in _table_names.
    """
    if append and not cls.table_loaded():
        cls.load_table(append=False)  # default load
    elif not append:
        cls._table = set()
        cls._table_names = []
    # Tell MyPy that this is true
    assert cls._table is not None
    assert cls._table_names is not None
    if filename is None:
        with data.open_text(data, "particle2020.csv") as f:
            cls.load_table(f, append=append, _name="particle2020.csv")
        with data.open_text(data, "nuclei2020.csv") as f:
            cls.load_table(f, append=True, _name="nuclei2020.csv")
        return
    elif not hasattr(filename, "read"):
        cls._table_names.append(str(filename))
        # Conversion to handle pathlib on Python < 3.6:
        open_file = open(str(filename))
    else:
        assert not isinstance(filename, str)  # Tell typing that this is true
        tmp_name = _name or getattr(filename, "name")
        cls._table_names.append(
            tmp_name or "{0!r} {1}".format(filename, len(cls._table_names))
        )
        open_file = filename
    with open_file as f:
        r = csv.DictReader(l for l in f if not l.startswith("#"))
        for v in r:
            try:
                value = int(v["ID"])
                # Replace the previous value if it exists
                # We can remove an int; ignore typing thinking we need a particle
                if value in cls._table:
                    cls._table.remove(value)  # type: ignore
                cls._table.add(
                    cls(
                        pdgid=value,
                        mass=float(v["Mass"]),
                        mass_upper=float(v["MassUpper"]),
                        mass_lower=float(v["MassLower"]),
                        width=float(v["Width"]),
                        width_upper=float(v["WidthUpper"]),
                        width_lower=float(v["WidthLower"]),
                        I=v["I"],
                        G=int(v["G"]),
                        P=int(v["P"]),
                        C=int(v["C"]),
                        anti_flag=int(v["Anti"]),
                        three_charge=int(v["Charge"]),
                        rank=int(v["Rank"]),
                        status=int(v["Status"]),
                        pdg_name=v["Name"],
                        quarks=v["Quarks"],
                        latex_name=v["Latex"],
                    )
                )
            except ValueError:
                pass

def table_loaded(

cls)

Check to see if the table is loaded.

@classmethod
def table_loaded(cls):
    # type: () -> bool
    """
    Check to see if the table is loaded.
    """
    return not cls._table is None

def table_names(

cls)

Return the list of names loaded.

Note

Calling this class method will load the default table, if no table has so far been loaded. Check with table_loaded() first if you don't want this loading to be triggered by the call.

@classmethod
def table_names(cls):
    # type: () -> Tuple[str, ...]
    """
    Return the list of names loaded.
    Note
    ----
    Calling this class method will load the default table,
    if no table has so far been loaded.
    Check with table_loaded() first if you don't want this loading
    to be triggered by the call.
    """
    if cls._table_names is None:
        cls.load_table()
    if cls._table_names is not None:
        return tuple(cls._table_names)  # make a copy to avoid user manipulation
    else:
        return tuple()