ATLAS Offline Software
PDGHelpers.py
Go to the documentation of this file.
1 # Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
2 
3 """Parser for the PDGTABLE.MeV file
4 
5  Creates a compact object that holds configuration
6  for the ExtraParticlesPhysicsTool.
7 
8  Gaudi cannot parse nested std::map objects, so the extra
9  particles are converted into a
10 
11  std::map< std::string, std::vector< double > >
12 
13  object and ordering of the values is therefore important.
14 
15  Rows are:
16  name
17  mass [MeV]
18  width [MeV]
19  pdg
20  lifetime [ns]
21 
22  Charge, spin, parity, and isospin3 are set to zero.
23 
24  AUTHOR: miha muskinja
25  DATE: August 2019
26 
27 """
28 
29 import os
30 
31 from AthenaCommon.SystemOfUnits import MeV, joule
32 from AthenaCommon.PhysicalConstants import hbar_Planck, h_Planck
33 from AthenaCommon.Logging import logging
34 from functools import lru_cache
35 
36 @lru_cache
37 def getPDGTABLE(table):
38  # Delete a local file if present
39  if os.path.isfile(table):
40  os.remove(table)
41  # Grab the file
42  os.system('get_files -data %s' % table)
43  return True
44 
45 
46 @lru_cache
48  # Delete a local file if present
49  if os.path.isfile(acceptlist):
50  os.remove(acceptlist)
51  #create blank file
52  blank = open('G4particle_acceptlist_ExtraParticles.txt', 'x')
53  blank.close()
54  return True
55 
56 
57 def updateExtraParticleAcceptList(listName='G4particle_acceptlist_ExtraParticles.txt', pdgcodes=[]):
58  if getExtraParticleAcceptList(listName):
59  import shutil
60  shutil.copy(listName, listName+'.org')
61  existingpdgcodes = [int(x) for x in open(listName).readlines()]
62  newpdgcodes = list(set(pdgcodes).difference(existingpdgcodes))
63  # update the acceptlist for GenParticleSimAcceptList
64  with open(listName, 'a') as writer:
65  for pdg in newpdgcodes:
66  writer.write('%s\n' % pdg)
67 
68 
70  name = ""
71  mass = -1
72  width = -1
73  charge = 0
74  pdg = 0
75  lifetime = h_Planck / joule
76 
77  def __init__(self, **kwargs):
78  self.__dict__.update(kwargs)
79 
80  def __str__(self):
81  string = ''
82  string += 'name: %s\n' % self.name
83  string += ' mass: %s\n' % self.mass
84  string += ' width: %s\n' % self.width
85  string += ' charge: %s\n' % self.charge
86  string += ' pdg: %s\n' % self.pdg
87  string += ' lifetime: %s\n' % self.lifetime
88  return string
89 
90  def __neg__(self):
91  antiParticle = ExtraParticle(**self.__dict__)
92  antiParticle.name = self.getAntiName()
93  antiParticle.charge *= -1
94  antiParticle.pdg *= -1
95  return antiParticle
96 
97  def getAntiName(self):
98  if self.name[-1] == '0':
99  if 'anti_' in self.name:
100  return self.name.replace('anti_', '')
101  else:
102  return 'anti_' + self.name
103  elif self.name[-1] == '+':
104  return self.name.replace('+', '-')
105  elif self.name[-1] == '-':
106  return self.name.replace('-', '+')
107 
108 
110 
111  def __init__(self, table, ranges):
112  self.log = logging.getLogger(__name__)
113  self.table = table
114  self.ranges = ranges
115  self.extraParticles = {}
116  self.parsePDGTABLE()
117 
118  def accept(self, pdg):
119  """Function to determine which extra particles are added
120 
121  Function checks the ranges member variable
122  and evaluates whether the particle should be accepted.
123 
124  TODO Consider adding a Sim.ExtraParticlesRanges ConfigFlag
125 
126  For example, '111-556,1112-9090226' matches everything from
127  111 to 555 and 1112 to 9090225.
128  """
129  ranges = [r.split("-") for r in self.ranges.split(",")]
130  for r in ranges:
131  if int(r[0]) <= pdg < int(r[1]):
132  return True
133 
134  def parsePDGTABLE(self):
135 
136  # parse the PDGTABLE
137  with open(self.table, 'r') as f:
138  for line in f:
139  if line.startswith('*'):
140  # Comments start with '*'
141  continue
142  splitLine = line.split()
143 
144  # Name of the particle
145  baseName = splitLine[-2]
146 
147  # Number of particle entries
148  charges = splitLine[-1].split(',')
149 
150  # Mass or Width
151  prop = ''
152  symbol = splitLine[0]
153  if symbol == 'M':
154  prop = 'mass'
155  elif symbol == 'W':
156  prop = 'width'
157  else:
158  raise ValueError(
159  'Unidentified symbol %s for particle %s' % (
160  symbol, baseName))
161 
162  pdgs = splitLine[1:1+len(charges)]
163  value = float(splitLine[1+len(charges)])
164 
165  for pdg, charge in zip(pdgs, charges):
166  if not self.accept(int(pdg)):
167  continue
168  name = self.formatName(baseName, charge)
169  kwargs = dict()
170  kwargs.setdefault('name', name)
171  kwargs.setdefault(prop, value * MeV)
172  kwargs.setdefault('pdg', int(pdg))
173  kwargs.setdefault('charge', self.formatCharge(charge))
174  if name not in self.extraParticles.keys():
175  self.extraParticles[name] = ExtraParticle(**kwargs)
176  else:
177  if getattr(self.extraParticles[name], prop) != -1:
178  self.log.warning(
179  "Property %s is already"
180  "set for particle %s."
181  "Current value is %s and"
182  "incoming value is %s.",
183  prop, name,
184  getattr(self.extraParticles[name], prop),
185  value)
186  continue
187  setattr(self.extraParticles[name], prop, value)
188 
189  for name in self.extraParticles:
190  # calculate lifetime
191  width = self.extraParticles[name].width
192  if width > 0:
193  self.extraParticles[name].lifetime = hbar_Planck/width
194 
195  # create anti-particles
196  for name in [x for x in self.extraParticles.keys()]:
197  antiParticle = -self.extraParticles[name]
198  self.extraParticles.update({antiParticle.name: antiParticle})
199 
200  def formatName(self, name, charge):
201  nameOut = name
202  nameOut = nameOut.replace("*", "_star").replace("'", "_prime")
203  nameOut += charge
204  return nameOut
205 
206  def formatCharge(self, charge):
207  # check that all characters are the same
208  # e.g.: '0', '+', '--', ...
209  if not (charge == len(charge) * charge[0]):
210  raise ValueError('Unexpected charge %s' % charge)
211 
212  # parse string
213  if '+' in charge:
214  return len(charge)
215  elif '-' in charge:
216  return -len(charge)
217  elif '0' in charge:
218  return 0
219  else:
220  raise ValueError('Unexpected charge %s' % charge)
221 
222  def createList(self):
223  pdgcodes = [ self.extraParticles[name].pdg for name in self.extraParticles ]
224  updateExtraParticleAcceptList('G4particle_acceptlist_ExtraParticles.txt', pdgcodes)
225 
226  # generate output in correct format
227  outDict = dict()
228  for name in self.extraParticles:
229  outDict.update({name: [
230  self.extraParticles[name].mass,
231  self.extraParticles[name].width,
232  self.extraParticles[name].charge,
233  self.extraParticles[name].pdg,
234  self.extraParticles[name].lifetime
235  ]})
236 
237  return outDict
PDGHelpers.PDGParser.__init__
def __init__(self, table, ranges)
Definition: PDGHelpers.py:111
PDGHelpers.PDGParser
Definition: PDGHelpers.py:109
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
PDGHelpers.updateExtraParticleAcceptList
def updateExtraParticleAcceptList(listName='G4particle_acceptlist_ExtraParticles.txt', pdgcodes=[])
Definition: PDGHelpers.py:57
SystemOfUnits
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
PDGHelpers.PDGParser.ranges
ranges
Definition: PDGHelpers.py:114
PDGHelpers.PDGParser.parsePDGTABLE
def parsePDGTABLE(self)
Definition: PDGHelpers.py:134
PDGHelpers.getPDGTABLE
def getPDGTABLE(table)
Definition: PDGHelpers.py:37
PDGHelpers.getExtraParticleAcceptList
def getExtraParticleAcceptList(acceptlist)
Definition: PDGHelpers.py:47
PDGHelpers.PDGParser.formatCharge
def formatCharge(self, charge)
Definition: PDGHelpers.py:206
PDGHelpers.ExtraParticle.__init__
def __init__(self, **kwargs)
Definition: PDGHelpers.py:77
PDGHelpers.ExtraParticle.__neg__
def __neg__(self)
Definition: PDGHelpers.py:90
PDGHelpers.ExtraParticle
Definition: PDGHelpers.py:69
PDGHelpers.PDGParser.createList
def createList(self)
Definition: PDGHelpers.py:222
PDGHelpers.ExtraParticle.getAntiName
def getAntiName(self)
Definition: PDGHelpers.py:97
PDGHelpers.ExtraParticle.__str__
def __str__(self)
Definition: PDGHelpers.py:80
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:232
PDGHelpers.PDGParser.formatName
def formatName(self, name, charge)
Definition: PDGHelpers.py:200
PDGHelpers.ExtraParticle.lifetime
lifetime
Definition: PDGHelpers.py:75
PDGHelpers.ExtraParticle.pdg
int pdg
Definition: PDGHelpers.py:74
Trk::open
@ open
Definition: BinningType.h:40
PDGHelpers.ExtraParticle.charge
int charge
Definition: PDGHelpers.py:73
PDGHelpers.ExtraParticle.name
string name
Definition: PDGHelpers.py:70
PDGHelpers.PDGParser.accept
def accept(self, pdg)
Definition: PDGHelpers.py:118
pickleTool.object
object
Definition: pickleTool.py:30
python.Bindings.keys
keys
Definition: Control/AthenaPython/python/Bindings.py:798
PDGHelpers.PDGParser.extraParticles
extraParticles
Definition: PDGHelpers.py:115
PDGHelpers.PDGParser.table
table
Definition: PDGHelpers.py:113
PDGHelpers.PDGParser.log
log
Definition: PDGHelpers.py:112
PDGHelpers.ExtraParticle.mass
int mass
Definition: PDGHelpers.py:71
PDGHelpers.ExtraParticle.width
int width
Definition: PDGHelpers.py:72
WriteBchToCool.update
update
Definition: WriteBchToCool.py:67
readCCLHist.float
float
Definition: readCCLHist.py:83
Trk::split
@ split
Definition: LayerMaterialProperties.h:38