"""
.. module:: theory.crossSection
:synopsis: Encapsulates the result of the computation of the reference
cross section.
.. moduleauthor:: Wolfgang Waltenberger <wolfgang.waltenberger@gmail.com>
.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
"""
from smodels.tools.physicsUnits import TeV, pb
from smodels.theory import lheReader
import smodels.particles
import logging
import sys
logger = logging.getLogger(__name__)
[docs]class XSectionInfo(object):
"""
An instance of this class represents information regarding a cross-section.
This class is used to store information of a cross-section (center of
mass, order and label).
"""
def __init__(self):
self.sqrts = None
self.order = None
self.label = None
def __eq__(self, other):
if type(other) != type(self):
return False
if other.sqrts != self.sqrts:
return False
if other.order != self.order:
return False
return True
def __ne__(self, other):
if type(other) != type(XSectionInfo()):
return True
if other.sqrts != self.sqrts:
return True
if other.order != self.order:
return True
return False
[docs] def copy(self):
"""
Generate an independent copy of self.
Faster than deepcopy.
"""
newinfo = XSectionInfo()
newinfo.sqrts = self.sqrts
newinfo.order = self.order
newinfo.label = self.label[:]
return newinfo
[docs]class XSection(object):
"""
An instance of this class represents a cross-section.
This class is used to store the information of a single cross-section
(value, paritcle ids, center of mass, order and label).
order = 0 (LO), 1 (NLO) or 2 (NLL).
"""
def __init__(self):
"""
Initializes the object to store a cross-section value. All initial info is set to None.
"""
self.info = XSectionInfo()
self.value = None
self.pid = (None, None)
def __mul__(self, other):
"""
Multiplies the value of the cross-section by the factor other (should be a float).
"""
newXsec = self.copy()
if type(other) == type(1.):
newXsec.value = newXsec.value * other
else:
logger.error("Xsections can only be multiplied by floats")
sys.exit()
return newXsec
def __rmul__(self, other):
"""
Right multiplication (see left multiplication).
"""
return self * other
def __add__(self, other):
"""
Returns a copy of self with the value of other added to its value.
"""
if type(other) == type(XSection()):
if self.info == other.info:
res = self.copy()
res.value += other.value
return res
logger.error("Trying to add " + type(other) + " to a XSection object")
sys.exit()
def __eq__(self, other):
"""
Compare two XSection objects. Returns True if .info and type and value and pid are equal.
"""
if type(other) != type(XSection()):
return False
if other.info != self.info:
return False
if other.value != self.value:
return False
if other.pid != self.pid:
return False
return True
def __ne__(self, other):
"""
Compare two XSection objects. Returns True if .info or type or value or pid is not equal.
"""
if type(other) != type(XSection()):
return True
if other.info != self.info:
return True
if other.value != self.value:
return True
if other.pid != self.pid:
return True
return False
def __str__(self):
"""
Generate cross-section information in string format.
"""
st = self.info.label + ':' + str(self.value)
return st
[docs] def niceStr(self):
"""
Generates a more human readable string. The string format is:
Sqrts: self.info.sqrts, Weight: self.value
"""
st = 'Sqrts: '+str(self.info.sqrts) + ', Weight:' + str(self.value)
return st
[docs] def copy(self):
"""
Generates an independent copy of self.
Faster than deepcopy.
"""
newXsec = XSection()
newXsec.info = self.info.copy()
newXsec.value = self.value
newXsec.pid = tuple(list(self.pid)[:])
return newXsec
def _zeroXSec(self):
"""
Replace the cross-section value by zero.
"""
self.value = 0. * pb
[docs]class XSectionList(object):
"""
An instance of this class represents a list of cross-sections.
This class is used to store a list of cross-sections.
"""
def __init__(self, infoList=None):
"""
If infoList is defined, create entries with zero cross-sections
according to infoList. infoList must be a list of XSectionInfo objects.
"""
self.xSections = []
if infoList:
for info in infoList:
newentry = XSection()
newentry.value = 0. * pb
newentry.pid = (None, None)
newentry.info = info.copy()
self.add(newentry)
def __mul__(self, other):
newList = self.copy()
for ixsec, xsec in enumerate(newList):
newList[ixsec] = xsec * other
return newList
def __rmul__(self, other):
return self * other
def __add__(self,other):
newList = self.copy()
if type(other) != type(self):
logger.warning("Trying to add a XSectionList and a "+str(type(other)))
return self
newList.combineWith(other)
return newList
def __iter__(self):
return iter(self.xSections)
def __getitem__(self, index):
return self.xSections[index]
def __setitem__(self, index, xsec):
if type(xsec) != type(XSection()):
logger.error("Input object must be a XSection() object")
sys.exit()
else:
self.xSections[index] = xsec
def __len__(self):
return len(self.xSections)
def __str__(self):
return str([str(xsec) for xsec in self])
[docs] def niceStr(self):
st = ""
for xsec in self:
st += xsec.niceStr()+'\n'
return st
[docs] def copy(self):
"""
Generates an independent copy of itself. Faster than deepcopy.
"""
newList = XSectionList()
for xsec in self.xSections:
newList.xSections.append(xsec.copy())
return newList
[docs] def add(self, newxsec):
"""
Append a XSection object to the list.
"""
if type(newxsec) != type(XSection()):
logger.error("Input object must be a XSection() object")
sys.exit()
else:
self.xSections.append(newxsec.copy())
def _addValue(self, newxsec):
"""
Add a XSection object to the list.
If the XSection object already exists, add to its values, otherwise
append the object.
"""
if type(newxsec) != type(XSection()):
logger.error("Input object must be a XSection() object")
sys.exit()
else:
exists = False
for iXSec, xSec in enumerate(self.xSections):
if xSec.info == newxsec.info \
and sorted(xSec.pid) == sorted(newxsec.pid):
self.xSections[iXSec].value = xSec.value + newxsec.value
break
if not exists:
self.add(newxsec)
[docs] def getXsecsFor(self, item):
"""
Return a list of XSection objects for item (label, pid, sqrts).
"""
xsecList = XSectionList()
for xsec in self:
if type(item) == type(xsec.info.label) and item == xsec.info.label:
xsecList.add(xsec)
elif type(item) == type(xsec.info.sqrts) \
and item == xsec.info.sqrts:
xsecList.add(xsec)
elif type(item) == type(xsec.pid) and item == xsec.pid:
xsecList.add(xsec)
elif type(item) == type(1) and (item in xsec.pid):
xsecList.add(xsec)
return xsecList
def _zeroXSecs(self):
"""
Replace the cross-section values in the list by zero.
"""
for xsec in self:
xsec.value = 0. * pb
[docs] def delete(self, xSec):
"""
Delete the cross-section entry from the list.
"""
for ixsec, xsec in enumerate(self):
if xsec == xSec:
self.xSections.pop(ixsec)
[docs] def getInfo(self):
"""
Get basic info about the cross-sections appearing in the list (order,
value and label).
:returns: list of XSectionInfo objects
"""
allInfo = []
for xsec in self:
info = xsec.info
if not info in allInfo:
allInfo.append(info)
return allInfo
def _getLabels(self):
"""
Get all labels appearing in the list.
"""
allLabels = []
allInfo = self.getInfo()
for info in allInfo:
allLabels.append(info.label)
return list(set(allLabels))
[docs] def getPIDpairs(self):
"""
Get all particle ID pairs appearing in the list.
"""
allPidPairs = []
for xsec in self:
allPidPairs.append(xsec.pid)
return list(set(allPidPairs))
[docs] def getPIDs(self):
"""
Get all particle IDs appearing in the list.
"""
allPids = []
for xsec in self:
allPids.extend(xsec.pid)
return sorted(list(set(allPids)))
[docs] def getMaxXsec(self):
"""
Get the maximum cross-section value appearing in the list.
"""
maxxsec = 0. * pb
for xsec in self:
if xsec.value > maxxsec:
maxxsec = xsec.value
return maxxsec
[docs] def getMinXsec(self):
"""
Get minimum cross-section value appearing in the list.
"""
if len(self) > 0:
minxsec = self.xSections[0].value
else: return False
for xsec in self:
if xsec.value < minxsec:
minxsec = xsec.value
return minxsec
[docs] def getDictionary(self, groupBy="pids"):
"""
Convert the list of XSection objects to a nested dictionary.
First level keys are the particles IDs (if groupBy == pids) or labels
(if groupBy == labels) and values are the cross-section labels or
particle IDs and the cross-section value.
"""
xSecDictionary = {}
if groupBy == "pids":
allPids = self.getPIDpairs()
for pid in allPids:
xSecDictionary[pid] = {}
xSecs = self.getXsecsFor(pid)
for xsec in xSecs:
xSecDictionary[pid][xsec.info.label] = xsec.value
elif groupBy == "labels":
allLabels = self._getLabels()
for label in allLabels:
xSecDictionary[label] = {}
xSecs = self.getXsecsFor(label)
for xsec in xSecs:
xSecDictionary[label][xsec.pid] = xsec.value
return xSecDictionary
[docs] def combineWith(self, newXsecs):
"""
Add a new list of cross-sections.
If the new cross-sections already appear (have same order and sqrts),
add its value to the original value, otherwise append it to the list.
The particle IDs are ignored when adding cross-sections. Hence, they
are set to (None, None) if any cross-sections are combined.
"""
newList = newXsecs
if type(newXsecs) == type(XSection()):
newList = [newXsecs]
for newXsec in newList:
if not newXsec.info in self.getInfo():
self.add(newXsec)
else:
for oldXsec in self:
if newXsec.info == oldXsec.info:
oldXsec.value = oldXsec.value + newXsec.value
oldXsec.pid = (None, None)
[docs] def removeLowerOrder(self):
"""
Keep only the highest order cross-section for each process in the list.
Remove order information and set default labels.
"""
newList = XSectionList()
for pids in self.getPIDpairs():
xsecs = self.getXsecsFor(pids)
for i, ixsec in enumerate(xsecs):
newxsec = ixsec.copy()
removeXsec = False
isqrts = ixsec.info.sqrts
iorder = ixsec.info.order
# Check if the xsec appear with the same sqrts but at a higher
# order
for j, jxsec in enumerate(xsecs):
if i == j:
continue
jsqrts = jxsec.info.sqrts
jorder = jxsec.info.order
if jsqrts == isqrts and jorder > iorder:
removeXsec = True
break
if not removeXsec:
# Erase cross-section labels and information
newxsec.info.label = str(newxsec.info.sqrts)
newxsec.info.order = None
newList.add(newxsec)
if len(self) != len(newList):
logger.info("Ignoring %i lower order cross-sections",
(len(self) - len(newList)))
self.xSections = newList.xSections
[docs] def order(self):
"""
Order the cross-section in the list by their PDG pairs
"""
self.xSections = sorted(self.xSections, key=lambda xsec: xsec.pid)
[docs]def getXsecFromSLHAFile(slhafile, useXSecs=None, xsecUnit = pb):
"""
Obtain cross-sections for pair production of R-odd particles from input SLHA file.
The default unit for cross-section is pb.
:parameter slhafile: SLHA input file with cross-sections
:parameter useXSecs: if defined enables the user to select cross-sections to
use. Must be a XSecInfoList object
:parameter xsecUnit: cross-section unit in the input file (must be a Unum unit)
:returns: a XSectionList object
"""
# Store information about all cross-sections in the SLHA file
xSecsInFile = XSectionList()
slha = open(slhafile, 'r')
lines = slha.readlines()
xsecblock = False
for l in lines:
skipXsec = False
if l.startswith("#") or len(l) < 2 or not l.strip():
continue
if 'XSECTION' in l:
xsecblock = True
# Values in the SLHA file are in GeV
sqrtS = eval(l.split()[1]) / 1000.
pids = (eval(l.split()[5]), eval(l.split()[6]))
continue
if not xsecblock:
# Ignore other entries
continue
for pid in pids:
if not pid in smodels.particles.rOdd.keys():
skipXsec = True
logger.warning("Ignoring cross-section for "+str(pids)+" production") #Ignore production of R-Even particles
break
csOrder = eval(l.split()[1])
cs = eval(l.split()[6]) * xsecUnit
wlabel = str(int(sqrtS)) + ' TeV'
if csOrder == 0:
wlabel += ' (LO)'
elif csOrder == 1:
wlabel += ' (NLO)'
elif csOrder == 2:
wlabel += ' (NLL)'
else:
logger.error("Unknown QCD order in XSECTION line " + l)
sys.exit()
xsec = XSection()
xsec.info.sqrts = sqrtS * TeV
xsec.info.order = csOrder
xsec.info.label = wlabel
xsec.value = cs
xsec.pid = pids
# Do not add xsecs which do not match the user required ones:
if (useXSecs and not xsec.info in useXSecs) or skipXsec:
continue
else: xSecsInFile.add(xsec)
slha.close()
return xSecsInFile
[docs]def getXsecFromLHEFile(lhefile, addEvents=True):
"""
Obtain cross-sections from input LHE file.
:parameter lhefile: LHE input file with unweighted MC events
:parameter addEvents: if True, add cross-sections with the same mothers,
otherwise return the event weight for each pair of mothers
:returns: a XSectionList object
"""
# Store information about all cross-sections in the LHE file
xSecsInFile = XSectionList()
reader = lheReader.LheReader(lhefile)
if not type ( reader.metainfo["totalxsec"] ) == type ( pb) :
logger.error("Cross-section information not found in LHE file.")
sys.exit()
elif not reader.metainfo["nevents"]:
logger.error("Total number of events information not found in LHE " +
"file.")
sys.exit()
elif not type ( reader.metainfo["sqrts"] ) == type ( TeV ):
logger.error("Center-of-mass energy information not found in LHE " +
"file.")
sys.exit()
# Common cross-section info
totxsec = reader.metainfo["totalxsec"]
nevts = reader.metainfo["nevents"]
sqrtS = reader.metainfo["sqrts"]
eventCs = totxsec / float(nevts)
# Get all mom pids
allpids = []
for event in reader:
allpids.append(tuple(sorted(event.getMom())))
pids = set(allpids)
# Generate zero cross-sections for all independent pids
for pid in pids:
xsec = XSection()
xsec.info.sqrts = sqrtS
if 'cs_order' in reader.metainfo:
xsec.info.order = reader.metainfo["cs_order"]
else:
# Assume LO xsecs, if not defined in the reader
xsec.info.order = 0
wlabel = str( sqrtS / TeV ) + ' TeV'
if xsec.info.order == 0:
wlabel += ' (LO)'
elif xsec.info.order == 1:
wlabel += ' (NLO)'
elif xsec.info.order == 2:
wlabel += ' (NLL)'
xsec.info.label = wlabel
xsec.value = 0. * pb
xsec.pid = pid
# If addEvents = False, set cross-section value to event weight
if not addEvents:
xsec.value = eventCs
xSecsInFile.add(xsec)
# If addEvents = True, sum up event weights with same mothers
if addEvents:
for pid in allpids:
for ixsec, xsec in enumerate(xSecsInFile.xSections):
if xsec.pid == pid:
xSecsInFile.xSections[ixsec].value += eventCs
reader.close()
return xSecsInFile