Particle database#

In PWA, you usually want to search for special resonances, possibly even some not listed in the PDG. In this notebook, we go through a few ways to add or overwrite Particle instances in the database with your own particle definitions.

Loading the default database#

In Generate transitions, we made use of the StateTransitionManager. By default, if you do not specify the particle_db argument, the StateTransitionManager calls the function load_default_particles(). This functions returns a ParticleCollection instance with Particle definitions from the PDG, along with additional definitions that are provided in the file additional_definitions.yml.

Here, we call this method directly to illustrate what happens (we use load_pdg(), which loads a subset):

from qrules.particle import load_pdg

particle_db = load_pdg()
print("Number of loaded particles:", len(particle_db))
Number of loaded particles: 537

In the following, we illustrate how to use the methods of the ParticleCollection class to find and ‘modify’ Particles and add() them back to the ParticleCollection.

Finding particles#

The ParticleCollection class offers some methods to search for particles by name or by PID (see find()):

particle_db.find(333)
Particle(
  name='phi(1020)',
  pid=333,
  latex='\\phi(1020)',
  spin=1.0,
  mass=1.019461,
  width=0.004248999999999999,
  isospin=Spin(0, 0),
  parity=-1,
  c_parity=-1,
  g_parity=-1,
)

With filter(), you can perform more sophisticated searches. This is done by either passing a function or lambda.

subset = particle_db.filter(lambda p: p.name.startswith("f(2)"))
subset.names
['f(2)(1270)',
 "f(2)'(1525)",
 'f(2)(1950)',
 'f(2)(2010)',
 'f(2)(2300)',
 'f(2)(2340)']
subset = particle_db.filter(
    lambda p: p.strangeness == 1 and p.spin >= 1 and p.mass > 1.8 and p.mass < 1.9
)
subset.names
['K(2)(1820)0',
 'K(2)(1820)+',
 'Lambda(1820)~',
 'Lambda(1830)~',
 'Lambda(1890)~']
subset = particle_db.filter(lambda p: p.is_lepton())
subset.names
['e-',
 'e+',
 'mu-',
 'mu+',
 'nu(mu)~',
 'nu(e)~',
 'nu(tau)~',
 'nu(tau)',
 'nu(e)',
 'nu(mu)',
 'tau-',
 'tau+']

Note that in each of these examples, we call the names property. This is just to only display the names, sorted alphabetically, otherwise the output becomes a bit of a mess:

particle_db.filter(lambda p: p.name.startswith("pi") and len(p.name) == 3)
ParticleCollection({
  Particle(
    name='pi-',
    pid=-211,
    latex='\\pi^{-}',
    spin=0.0,
    mass=0.13957039000000002,
    width=2.5284e-17,
    charge=-1,
    isospin=Spin(1, -1),
    parity=-1,
    g_parity=-1,
  ),
  Particle(
    name='pi0',
    pid=111,
    latex='\\pi^{0}',
    spin=0.0,
    mass=0.1349768,
    width=7.81e-09,
    isospin=Spin(1, 0),
    parity=-1,
    c_parity=+1,
    g_parity=-1,
  ),
  Particle(
    name='pi+',
    pid=211,
    latex='\\pi^{+}',
    spin=0.0,
    mass=0.13957039000000002,
    width=2.5284e-17,
    charge=1,
    isospin=Spin(1, +1),
    parity=-1,
    g_parity=-1,
  ),
})

LaTeX representation#

Particles also contain a latex tag. Here, we use ipython to render them nicely as mathematical symbols:

from IPython.display import Math

sigmas = particle_db.filter(lambda p: p.name.startswith("Sigma") and p.charmness == 1)
Math(", ".join(p.latex for p in sigmas))
\[\displaystyle \Sigma_{c}(2520)^{0}, \Sigma_{c}(2455)^{++}, \Sigma_{c}(2520)^{+}, \Sigma_{c}(2455)^{+}, \Sigma_{c}(2520)^{++}, \Sigma_{c}^{0}\]

Adding custom particle definitions through Python#

A quick way to modify or overwrite particles, is through your Python script or notebook. Notice that the instances in the database are Particle instances:

N1650_plus = particle_db["N(1650)+"]
N1650_plus
Particle(
  name='N(1650)+',
  pid=32212,
  latex='N(1650)^{+}',
  spin=0.5,
  mass=1.65,
  width=0.125,
  charge=1,
  isospin=Spin(1/2, +1/2),
  baryon_number=1,
  parity=-1,
)

The instances in the database are immutable. Therefore, if you want to modify, say, the width, you have to create a new Particle instance from the particle you want to modify and add() it back to the database. You can do this with create_particle():

from qrules.particle import create_particle

new_N1650_plus = create_particle(
    template_particle=N1650_plus, name="Modified N(1650)+", width=0.2
)

particle_db.add(new_N1650_plus)
particle_db["Modified N(1650)+"].width
0.2

You often also want to add the antiparticle of the particle you modified to the database. Using create_antiparticle(), it is easy to create the corresponding antiparticle object.

from qrules.particle import create_antiparticle

new_N1650_minus = create_antiparticle(new_N1650_plus, new_name="Modified N(1650)-")

particle_db.add(new_N1650_minus)
particle_db["Modified N(1650)-"]
Particle(
  name='Modified N(1650)-',
  pid=-32212,
  latex='\\overline{N(1650)^{+}}',
  spin=0.5,
  mass=1.65,
  width=0.2,
  charge=-1,
  isospin=Spin(1/2, -1/2),
  baryon_number=-1,
  parity=+1,
)

When adding additional particles you may need for your research, it is easiest to work with an existing particle as template. Let’s say we want to study \(e^+e^-\) collisions of several energies:

energies_mev = {4180, 4220, 4420, 4600}
template_particle = particle_db["J/psi(1S)"]
for energy_mev in energies_mev:
    energy_gev = energy_mev / 1e3
    new_particle = create_particle(
        template_particle,
        name=f"EpEm ({energy_mev} MeV)",
        mass=energy_gev,
    )
    particle_db.add(new_particle)
len(particle_db)
543
particle_db.filter(lambda p: "EpEm" in p.name).names
['EpEm (4180 MeV)', 'EpEm (4220 MeV)', 'EpEm (4420 MeV)', 'EpEm (4600 MeV)']

Of course, it’s also possible to add any kind of custom Particle, as long as its quantum numbers comply with the gellmann_nishijima() rule:

from qrules.particle import Particle

custom = Particle(
    name="custom",
    pid=99999,
    latex=R"p_\mathrm{custom}",
    spin=1.0,
    mass=1,
    charge=1,
    isospin=(1.5, 0.5),
    charmness=1,
)
custom
Particle(
  name='custom',
  pid=99999,
  latex='p_\\mathrm{custom}',
  spin=1.0,
  mass=1.0,
  charge=1,
  isospin=Spin(3/2, +1/2),
  charmness=1,
)
particle_db += custom
len(particle_db)
544

Loading custom definitions from a YAML file#

It’s also possible to add particles from a config file, with io.load(). Existing entries remain and if the imported file of particle definitions contains a particle with the same name, it is overwritten in the database.

It’s easiest to work with YAML. Here, we use the provided additional_particles.yml example file:

from qrules import io

particle_db += io.load("additional_particles.yml")

Writing to YAML#

You can also dump the existing particle lists to YAML. You do this with the io.write() function.

io.write(instance=particle_db, filename="dumped_particle_list.yaml")

Note that the function write can dump any ParticleCollection to an output file, also a specific subset.

from qrules.particle import ParticleCollection

output = ParticleCollection()
output += particle_db["J/psi(1S)"]
output += particle_db.find(22)  # gamma
output += particle_db.filter(lambda p: p.name.startswith("f(0)"))
output += particle_db["pi0"]
output += particle_db["pi+"]
output += particle_db["pi-"]
output += particle_db["custom"]
io.write(output, "particle_list_selection.yml")
output.names
['custom',
 'f(0)(500)',
 'f(0)(980)',
 'f(0)(1370)',
 'f(0)(1500)',
 'f(0)(1710)',
 'f(0)(2020)',
 'gamma',
 'J/psi(1S)',
 'pi0',
 'pi-',
 'pi+']

As a side note, qrules provides JSON schemas (reaction/particle-validation.json) to validate your particle list files (see also jsonschema.validators.validate()). If you have installed qrules as an Editable installation and use VSCode, your YAML particle list are checked automatically in the GUI.