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 the usual workflow, you make use of the StateTransitionManager
. By default, if you do not specify the particles
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_particle_definitions.yml
.
Here, we call this method directly to illustrate what happens (we use io.load_pdg
, which loads a subset):
[2]:
from expertsystem import io
particle_db = io.load_pdg()
print("Number of loaded particles:", len(particle_db))
Number of loaded particles: 512
In the following, we illustrate how to use the methods of the ParticleCollection
class to find and ‘modify’ Particle
s 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
):
[3]:
particle_db.find(333)
[3]:
Particle(name='phi(1020)', pid=333, spin=1.0, mass=1.019461, width=0.004248999999999999, charge=0, isospin=Spin(0.0, 0.0), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=0, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(-1), c_parity=Parity(-1), g_parity=Parity(-1))
With ParticleCollection.filter
, you can perform more sophisticated searches. This is done by either passing a function or lambda.
[4]:
subset = particle_db.filter(lambda p: p.name.startswith("f(2)"))
subset.names
[4]:
{"f(2)'(1525)",
'f(2)(1270)',
'f(2)(1950)',
'f(2)(2010)',
'f(2)(2300)',
'f(2)(2340)'}
[5]:
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
[5]:
{'K(2)(1820)+',
'K(2)(1820)0',
'Lambda(1820)~',
'Lambda(1830)~',
'Lambda(1890)~'}
[6]:
subset = particle_db.filter(lambda p: p.is_lepton())
subset.names
[6]:
{'e+',
'e-',
'mu+',
'mu-',
'nu(e)',
'nu(e)~',
'nu(mu)',
'nu(mu)~',
'nu(tau)',
'nu(tau)~',
'tau+',
'tau-'}
In each of these examples, we call the names
property. This is just to only display the names, sorted alphabetically. The full representation of a ParticleCollection
looks like this:
[7]:
particle_db.filter(lambda p: p.name.startswith("pi") and len(p.name) == 3)
[7]:
ParticleCollection({Particle(name='pi-', pid=-211, spin=0.0, mass=0.13957039000000002, width=2.5284e-17, charge=-1, isospin=Spin(1.0, -1.0), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=0, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(-1), c_parity=None, g_parity=Parity(-1)), Particle(name='pi+', pid=211, spin=0.0, mass=0.13957039000000002, width=2.5284e-17, charge=1, isospin=Spin(1.0, 1.0), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=0, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(-1), c_parity=None, g_parity=Parity(-1)), Particle(name='pi0', pid=111, spin=0.0, mass=0.1349768, width=7.73e-09, charge=0, isospin=Spin(1.0, 0.0), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=0, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(-1), c_parity=Parity(+1), g_parity=Parity(-1))})
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:
[8]:
N1650_plus = particle_db["N(1650)+"]
N1650_plus
[8]:
Particle(name='N(1650)+', pid=32212, spin=0.5, mass=1.65, width=0.125, charge=1, isospin=Spin(0.5, 0.5), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=1, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(-1), c_parity=None, g_parity=None)
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
:
[9]:
from expertsystem.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
WARNING:root:Particle with PID 32212 already exists: "N(1650)+"
[9]:
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.
[11]:
from expertsystem.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)-"]
[11]:
Particle(name='Modified N(1650)-', pid=-32212, spin=0.5, mass=1.65, width=0.2, charge=-1, isospin=Spin(0.5, -0.5), strangeness=0, charmness=0, bottomness=0, topness=0, baryon_number=-1, electron_lepton_number=0, muon_lepton_number=0, tau_lepton_number=0, parity=Parity(+1), c_parity=None, g_parity=None)
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:
[12]:
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)
[12]:
518
[13]:
particle_db.filter(lambda p: "EpEm" in p.name).names
[13]:
{'EpEm (4180 MeV)', 'EpEm (4220 MeV)', 'EpEm (4420 MeV)', 'EpEm (4600 MeV)'}
Loading custom definitions from a YAML file¶
It’s also possible to add particles from a config file, with load_particle_collection
. 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:
[14]:
from expertsystem.io import load_particle_collection
particle_db += load_particle_collection("additional_particles.yml")
Writing to XML or YAML¶
You can also dump the existing particle lists to either YAML or XML. You do this with the expertsystem.io.write
function.
[15]:
io.write(instance=particle_db, filename="dumped_particle_list.xml")
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.
[16]:
from expertsystem.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-"]
io.write(output, "particle_list_selection.yml")
output.names
[16]:
{'J/psi(1S)',
'f(0)(1370)',
'f(0)(1500)',
'f(0)(1710)',
'f(0)(500)',
'f(0)(980)',
'gamma',
'pi+',
'pi-',
'pi0'}
As a side note, the expertsystem
provides JSON schemas (e.g. yaml/particle-list.json
) to validate your particle list files (see also jsonschema.validate
):
[17]:
import yaml
with open("dumped_particle_list.yaml") as stream:
definition = yaml.load(stream, Loader=yaml.SafeLoader)
io._yaml.validation.particle_list(definition)
In addition, if you have installed the expertsystem
in Development mode and use VSCode, your YAML particle list are checked automatically in the GUI.