Belle II Software  release-05-02-19
pdg.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 """
5 pdg - access particle definitions
6 ---------------------------------
7 
8 This module helps to access particle definitions. When the software is loaded a
9 list of known particles is read from the EvtGen particle definition file
10 :file:`framework/particledb/data/evt.pdl`. This file contains all well-known
11 standard-model particles and their properties: mass, width or lifetime, charge,
12 spin.
13 ...
14 
15 This module allows to easily access this information (see `get`) or if necessary
16 add new particles using `add_particle` and even replace the whole particle
17 definition list using `load`.
18 
19 It also provides simple getters to convert `PDG codes`_ into particle names and
20 vice versa for use with modules which require a list of PDG codes for the
21 particles to generate. See `from_name`, `from_names`, `to_name` and `to_names`
22 
23 .. _PDG codes: http://pdg.lbl.gov/2020/reviews/rpp2020-rev-monte-carlo-numbering.pdf
24 """
25 
26 import re
27 import basf2
28 import ROOT
29 from ROOT.Belle2 import EvtGenDatabasePDG
30 
31 # the particle database (filled from evt.pdl by framework)
32 _database = EvtGenDatabasePDG.Instance()
33 
34 
35 def get(name):
36  """
37  Function to return particle information (TParticlePDG) from ROOT Database.
38 
39  'name' can be either the name of the particle, or a pdg code.
40  Will throw an LookupError of no such particle exists.
41  """
42 
43  p = _database.GetParticle(name)
44  if not p:
45  raise LookupError("No particle with name '%s'" % name)
46 
47  return p
48 
49 
50 def from_name(name):
51  """
52  Function to return pdg code for the given particle name.
53 
54  >>> pdg.from_name("pi+")
55  211
56  """
57 
58  return get(name).PdgCode()
59 
60 
61 def from_names(names):
62  """
63  for a list/tuple of particle names, return list of pdg codes.
64 
65  >>> pdg.from_names(["e+","e-","gamma"])
66  [-11, 11, 22]
67  """
68 
69  assert not isinstance(names, str), 'Argument is not a list!'
70 
71  return [from_name(n) for n in names]
72 
73 
74 def to_name(pdg):
75  """
76  Return particle name for given pdg code.
77 
78  >>> pdg.to_name(321)
79  K+
80  """
81 
82  return get(pdg).GetName()
83 
84 
85 def to_names(pdg_codes):
86  """
87  for a list/tuple of pdg codes, return list of paricle names.
88 
89  >>> pdg.to_names([11, -11, -211, 3212])
90  ['e-', 'e+', 'pi-', 'Sigma0']
91  """
92 
93  assert not isinstance(pdg_codes, int), 'Argument is not a list!'
94 
95  return [to_name(pdg) for pdg in pdg_codes]
96 
97 
98 def conjugate(name):
99  """
100  Function to return name of conjugated particle
101  """
102 
103  try:
104  return to_name(-from_name(name))
105  except LookupError as e:
106  return name
107 
108 
109 def load(filename):
110  """
111  Read particle database from given evtgen pdl file
112  """
113  _database.ReadEvtGenTable(filename)
114 
115 
116 def load_default():
117  """Read default evt.pdl file"""
118  _database.ReadEvtGenTable()
119 
120 
121 def add_particle(name, pdgCode, mass, width, charge, spin, max_width=None, lifetime=0, pythiaID=0):
122  """
123  Add a new particle to the list of known particles.
124 
125  The name cannot contain any whitespace character.
126 
127  Args:
128  name (str): name of the particle
129  pdgCode (int): pdg code identifiert for the particle
130  mass (float): mass of the particle in GeV
131  width (float): width of the particle in GeV
132  charge (float): charge of the particle in e
133  sping (float): spin of the particle
134  max_width (float): max width, if omitted 3*width will be used
135  lifetime (float): lifetime in ns, should be 0 as geant4 cannot handle it correctly otherwise
136  pythiaID (int): pythiaID of the particle (if any), if omitted 0 will be used
137  """
138  if lifetime > 0:
139  basf2.B2WARNING("Userdefined particle with non-zero lifetime will not be simulated correctly")
140 
141  if max_width is None:
142  # FIXME: is 3 a good default?
143  max_width = width * 3
144 
145  particle = _database.AddParticle(name, name, mass, False, width, charge * 3, "userdefined",
146  pdgCode, 0, 0, lifetime, spin, max_width, pythiaID)
147  if particle:
148  basf2.B2INFO("Adding new particle '%s' (pdg=%d, mass=%.3g GeV, width=%.3g GeV, charge=%d, spin=%d)" %
149  (name, pdgCode, mass, width, charge, spin))
150  return True
151 
152  return False
153 
154 
155 def search(name=None, min_mass=None, max_mass=None, name_regex=False, include_width=False):
156  """
157  Search for a particles by name or mass or both.
158 
159  This function allows to search for particle by name or mass and will return
160  a list of all particles which match the given criteria.
161 
162  By default all searches for the name are case insensitive but if ``name``
163  starts with "~" the search will be case sensitive. The "~" will not be part
164  of the search.
165 
166  If ``name_regex=True`` the name will be interpreted as a python
167  :py:mod:`regular expression <re>` and the function will return all particles
168  whose names match the expression. If ``name_regex=False`` the function will
169  return a list of all particles containing the given pattern as substring
170  ignoring case with two special cases:
171 
172  - if ``name`` begins with "^", only particles beginning with the pattern
173  will be searched. The "^" will not be part of the search.
174  - if ``name`` ends with "$" the pattern will only be matched to the end
175  of the particle name. The "$" will not be part of the search.
176 
177  If ``include_width=True`` the search will include all particles if their
178  (mass ± width) is within the given limit. If ``include_width`` is a positive
179  number then the particle will be returned if :math:`m ± n*\Gamma` is within the
180  required range where n is the value of ``include_width`` and :math:`\Gamma` the
181  width of the particle.
182 
183  Examples:
184  Return a list of all particles
185 
186  >>> search()
187 
188  Search for all particles containing a "pi" somewhere in the name and ignore the case
189 
190  >>> search("pi")
191 
192  Search for all particles beginning with K or k
193 
194  >>> search("^K")
195 
196  Search for all particles ending with "+" and having a maximal mass of 3 GeV:
197 
198  >>> search("+$", max_mass=3.0)
199 
200  Search for all particles which contain a capital D and have a minimal mass of 1 GeV
201 
202  >>> search("~D", min_mass=1.0)
203 
204  Search for all partiles which contain a set of parenthesis containing a number
205 
206  >>> search(".*\(\d*\).*", name_regex=True)
207 
208  Search all particles whose mass ± width covers 1 to 1.2 GeV
209 
210  >>> search(min_mass=1.0, max_mass=1.2, include_width=True)
211 
212  Search all particles whose mass ± 3*width touches 1 GeV
213 
214  >>> search(min_mass=1.0, max_mass=1.0, include_width=3)
215 
216 
217  Parameters:
218  name (str): Search pattern which will either be matched as a substring
219  or as regular expression if ``name_regex=True``
220  min_mass (float): minimal mass for all returned particles or None for no limit
221  max_mass (float): maximal mass for all returned particles or None for no limit
222  name_regex (bool): if True then ``name`` will be treated as a regular expression
223  include_width (float or bool): if True or >0 include the particles if
224  (mass ± include_width*width) falls within the mass limits
225  """
226 
227  pattern = None
228  if name:
229  options = re.IGNORECASE
230  if name[0] == "~":
231  name = name[1:]
232  options = 0
233 
234  if not name_regex:
235  if name[0] == "^" and name[-1] == "$":
236  name = "^{}$".format(re.escape(name[1:-1]))
237  elif name[0] == "^":
238  name = "^{}.*".format(re.escape(name[1:]))
239  elif name[-1] == "$":
240  name = ".*{}$".format(re.escape(name[:-1]))
241  else:
242  name = ".*{}.*".format(re.escape(name))
243 
244  pattern = re.compile(name, options)
245 
246  if include_width is True:
247  include_width = 1
248 
249  if include_width < 0:
250  include_width = 0
251 
252  result = []
253  for p in _database.ParticleList():
254  if pattern is not None and not pattern.match(p.GetName()):
255  continue
256  m = p.Mass()
257  w = p.Width() * include_width
258  if min_mass is not None and min_mass > (m+w):
259  continue
260  if max_mass is not None and max_mass < (m-w):
261  continue
262  result.append(p)
263 
264  return result