"""
.. module:: element
:synopsis: Module holding the Element class and its methods.
.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
"""
from smodels.theory.particleNames import elementsInStr
from smodels.theory.branch import Branch,InclusiveBranch
from smodels.theory import crossSection
from smodels.theory.exceptions import SModelSTheoryError as SModelSError
from smodels.tools.smodelsLogging import logger
[docs]class Element(object):
"""
An instance of this class represents an element.
This class possesses a pair of branches and the element weight
(cross-section * BR).
:ivar branches: list of branches (Branch objects)
:ivar weight: element weight (cross-section * BR)
:ivar motherElements: only for elements generated from a parent element
by mass compression, invisible compression,etc.
Holds a pair of (whence, mother element), where
whence describes what process generated the element
"""
def __init__(self, info=None, finalState=None):
"""
Initializes the element. If info is defined, tries to generate
the element using it.
:parameter info: string describing the element in bracket notation
(e.g. [[[e+],[jet]],[[e-],[jet]]])
:parameter finalState: list containing the final state labels for each branch
(e.g. ['MET', 'HSCP'] or ['MET','MET'])
"""
self.branches = [Branch(), Branch()]
self.weight = crossSection.XSectionList()
self.motherElements = []
self.elID = 0
self.covered = False
self.tested = False
if info:
# Create element from particle string
if type(info) == type(str()):
elements = elementsInStr(info)
if not elements or len(elements) > 1:
nel = 0
if elements:
nel = len(elements)
logger.error("Malformed input string. Number of elements "
"is %d (expected 1) in: ``%s''", nel, info)
return None
else:
el = elements[0]
branches = elementsInStr(el[1:-1])
if not branches or len(branches) != 2:
logger.error("Malformed input string. Number of "
"branches is %d (expected 2) in: ``%s''",
len(branches), info)
return None
self.branches = []
for branch in branches:
if branch == '[*]':
self.branches.append(InclusiveBranch())
else:
self.branches.append(Branch(branch))
# Create element from branch pair
elif type(info) == type([]) and type(info[0]) == type(Branch()):
for ib, branch in enumerate(info):
self.branches[ib] = branch.copy()
if finalState is None:
self.setFinalState([finalState]*len(self.branches))
else:
self.setFinalState(finalState)
def __cmp__(self,other):
"""
Compares the element with other.
The comparison is made based on branches.
OBS: The elements and the branches must be sorted!
:param other: element to be compared (Element object)
:return: -1 if self < other, 0 if self == other, +1, if self > other.
"""
if not isinstance(other,Element):
return -1
#Compare branches:
if self.branches != other.branches:
comp = self.branches > other.branches
if comp: return 1
else: return -1
else:
return 0
def __eq__(self,other):
return self.__cmp__(other)==0
def __lt__(self,other):
return self.__cmp__(other)<0
def __hash__(self):
return object.__hash__(self)
def __str__(self):
"""
Create the element bracket notation string, e.g. [[[jet]],[[jet]].
:returns: string representation of the element (in bracket notation)
"""
elStr = "["+",".join([str(br) for br in self.branches])+"]"
elStr = elStr.replace(" ", "").replace("'", "")
return elStr
[docs] def toStr(self):
"""
Returns a string with the element represented in bracket notation,
including the final states, e.g. [[[jet]],[[jet]] (MET,MET)
"""
elStr = str(self)+' '+str(tuple(self.getFinalStates())).replace("'","")
return elStr
[docs] def sortBranches(self):
"""
Sort branches. The smallest branch is the first one.
See the Branch object for definition of branch size and comparison
"""
#First make sure each branch is individually sorted
#(particles in each vertex are sorted)
for br in self.branches:
br.sortParticles()
#Now sort branches
self.branches = sorted(self.branches)
[docs] def setFinalState(self,finalStates):
"""
If finalStates = None, define the element final states according to the PID of the
last R-odd particle appearing in the cascade decay.
Else set the final states according to the finalStates list (must
match the branch ordering)
:parameter finalStates: List with final state labels (must match the branch ordering)
"""
for i,br in enumerate(self.branches):
br.setFinalState(finalStates[i])
[docs] def particlesMatch(self, other, branchOrder=False):
"""
Compare two Elements for matching particles only.
Allow for inclusive particle labels (such as the ones defined in particles.py)
and includes final state comparison.
If branchOrder = False, check both branch orderings.
:parameter other: element to be compared (Element object)
:parameter branchOrder: If False, check both orderings, otherwise
check the same branch ordering
:returns: True, if particles match; False, else;
"""
if not isinstance(other,Element):
return False
if len(self.branches) != len(other.branches):
return False
#Check if particles inside each branch match in the correct order
branchMatches = []
for ib,br in enumerate(self.branches):
branchMatches.append(br.particlesMatch(other.branches[ib]))
if sum(branchMatches) == 2:
return True
elif branchOrder:
return False
else:
#Now check for opposite order
for ib,br in enumerate(self.switchBranches().branches):
if not br.particlesMatch(other.branches[ib]):
return False
return True
[docs] def copy(self):
"""
Create a copy of self.
Faster than deepcopy.
:returns: copy of element (Element object)
"""
newel = Element()
newel.branches = []
for branch in self.branches:
newel.branches.append(branch.copy())
newel.weight = self.weight.copy()
newel.motherElements = self.motherElements[:]
newel.elID = self.elID
return newel
[docs] def setMasses(self, mass, sameOrder=True, opposOrder=False):
"""
Set the element masses to the input mass array.
:parameter mass: list of masses ([[masses for branch1],[masses for branch2]])
:parameter sameOrder: if True, set the masses to the same branch ordering
If True and opposOrder=True, set the masses to the
smaller of the two orderings.
:parameter opposOrder: if True, set the masses to the opposite branch ordering.
If True and sameOrder=True, set the masses to the
smaller of the two orderings.
"""
if sameOrder and opposOrder:
newmass = sorted(mass)
elif sameOrder:
newmass = mass
elif opposOrder:
newmass = [mass[1], mass[0]]
else:
logger.error("Called with no possible ordering")
raise SModelSError()
if len(newmass) != len(self.branches):
logger.error("Called with wrong number of mass branches")
raise SModelSError()
for i, mass in enumerate(newmass):
self.branches[i].masses = mass[:]
[docs] def switchBranches(self):
"""
Switch branches, if the element contains a pair of them.
:returns: element with switched branches (Element object)
"""
newEl = self.copy()
if len(self.branches) == 2:
newEl.branches = [newEl.branches[1], newEl.branches[0]]
return newEl
[docs] def getParticles(self):
"""
Get the array of particles in the element.
:returns: list of particle strings
"""
ptcarray = []
for branch in self.branches:
ptcarray.append(branch.particles)
return ptcarray
[docs] def getFinalStates(self):
"""
Get the array of particles in the element.
:returns: list of particle strings
"""
fsarray = []
for branch in self.branches:
fsarray.append(branch.finalState)
return fsarray
[docs] def getMasses(self):
"""
Get the array of masses in the element.
:returns: list of masses (mass array)
"""
massarray = []
for branch in self.branches:
massarray.append(branch.masses)
return massarray
[docs] def getPIDs(self):
"""
Get the list of IDs (PDGs of the intermediate states appearing the cascade decay), i.e.
[ [[pdg1,pdg2,...],[pdg3,pdg4,...]] ].
The list might have more than one entry if the element combines different pdg lists:
[ [[pdg1,pdg2,...],[pdg3,pdg4,...]], [[pdg1',pdg2',...],[pdg3',pdg4',...]], ...]
:returns: list of PDG ids
"""
pids = []
for ipid,PIDlist in enumerate(self.branches[0].PIDs):
for ipid2,PIDlist2 in enumerate(self.branches[1].PIDs):
pids.append([self.branches[0].PIDs[ipid],self.branches[1].PIDs[ipid2]])
return pids
[docs] def getDaughters(self):
"""
Get a pair of daughter IDs (PDGs of the last intermediate
state appearing the cascade decay), i.e. [ [pdgLSP1,pdgLSP2] ]
Can be a list, if the element combines several daughters:
[ [pdgLSP1,pdgLSP2], [pdgLSP1',pdgLSP2']]
:returns: list of PDG ids
"""
pids = self.getPIDs()
daughterPIDs = []
for pidlist in pids:
daughterPIDs.append([pidlist[0][-1],pidlist[1][-1]])
return daughterPIDs
[docs] def getMothers(self):
"""
Get a pair of mother IDs (PDGs of the first intermediate
state appearing the cascade decay), i.e. [ [pdgMOM1,pdgMOM2] ]
Can be a list, if the element combines several mothers:
[ [pdgMOM1,pdgMOM2], [pdgMOM1',pdgMOM2']]
:returns: list of PDG ids
"""
pids = self.getPIDs()
momPIDs = []
for pidlist in pids:
momPIDs.append([pidlist[0][0],pidlist[1][0]])
return momPIDs
[docs] def getEinfo(self):
"""
Get element topology info from branch topology info.
:returns: dictionary containing vertices and number of final states information
"""
vertnumb = []
vertparts = []
for branch in self.branches:
bInfo = branch.getInfo()
vertparts.append(bInfo['vertparts'])
vertnumb.append(bInfo['vertnumb'])
return {"vertnumb" : vertnumb, "vertparts" : vertparts}
def _getLength(self):
"""
Get the maximum of the two branch lengths.
:returns: maximum length of the element branches (int)
"""
return max(self.branches[0].getLength(), self.branches[1].getLength())
[docs] def checkConsistency(self):
"""
Check if the particles defined in the element exist and are consistent
with the element info.
:returns: True if the element is consistent. Print error message
and exits otherwise.
"""
info = self.getEinfo()
for ib, branch in enumerate(self.branches):
for iv, vertex in enumerate(branch.particles):
if len(vertex) != info['vertparts'][ib][iv]:
logger.error("Wrong syntax")
raise SModelSError()
for ptc in vertex:
if not ptc in rEven.values() and not ptc in ptcDic:
logger.error("Unknown particle. Add " + ptc + " to smodels/particle.py")
raise SModelSError()
return True
[docs] def compressElement(self, doCompress, doInvisible, minmassgap):
"""
Keep compressing the original element and the derived ones till they
can be compressed no more.
:parameter doCompress: if True, perform mass compression
:parameter doInvisible: if True, perform invisible compression
:parameter minmassgap: value (in GeV) of the maximum
mass difference for compression
(if mass difference < minmassgap, perform mass compression)
:returns: list with the compressed elements (Element objects)
"""
if not doCompress and not doInvisible:
return []
added = True
newElements = [self]
# Keep compressing the new topologies generated so far until no new
# compressions can happen:
while added:
added = False
# Check for mass compressed topologies
if doCompress:
for element in newElements:
newel = element.massCompress(minmassgap)
# Avoids double counting (conservative)
if newel and not newel.hasTopInList(newElements):
newElements.append(newel)
added = True
# Check for invisible compressed topologies (look for effective
# LSP, such as LSP + neutrino = LSP')
if doInvisible:
for element in newElements:
newel = element.invisibleCompress()
# Avoids double counting (conservative)
if newel and not newel.hasTopInList(newElements):
newElements.append(newel)
added = True
newElements.pop(0) # Remove original element
return newElements
[docs] def massCompress(self, minmassgap):
"""
Perform mass compression.
:parameter minmassgap: value (in GeV) of the maximum
mass difference for compression
(if mass difference < minmassgap -> perform mass compression)
:returns: compressed copy of the element, if two masses in this
element are degenerate; None, if compression is not possible;
"""
masses = self.getMasses()
massDiffs = []
#Compute mass differences in each branch
for massbr in masses:
massDiffs.append([massbr[i]-massbr[i+1] for i in range(len(massbr)-1)])
#Compute list of vertices to be compressed in each branch
compVertices = []
for ibr,massbr in enumerate(massDiffs):
compVertices.append([])
for iv,massD in enumerate(massbr):
if massD < minmassgap: compVertices[ibr].append(iv)
if not sum(compVertices,[]): return None #Nothing to be compressed
else:
newelement = self.copy()
newelement.motherElements = [ ("mass", self) ]
for ibr,compbr in enumerate(compVertices):
if compbr:
new_branch = newelement.branches[ibr]
ncomp = 0
for iv in compbr:
new_branch.masses.pop(iv-ncomp)
new_branch.particles.pop(iv-ncomp)
ncomp +=1
new_branch.setInfo()
newelement.sortBranches()
return newelement
[docs] def hasTopInList(self, elementList):
"""
Check if the element topology matches any of the topologies in the
element list.
:parameter elementList: list of elements (Element objects)
:returns: True, if element topology has a match in the list, False otherwise.
"""
if type(elementList) != type([]) or len(elementList) == 0:
return False
for element in elementList:
if type(element) != type(self):
continue
info1 = self.getEinfo()
info2 = element.getEinfo()
info2B = element.switchBranches().getEinfo()
if info1 == info2 or info1 == info2B:
return True
return False
[docs] def invisibleCompress(self):
"""
Perform invisible compression.
:returns: compressed copy of the element, if element ends with invisible
particles; None, if compression is not possible
"""
newelement = self.copy()
newelement.motherElements = [ ("invisible", self) ]
# Loop over branches
for ib, branch in enumerate(self.branches):
particles = branch.particles
if not branch.particles:
continue # Nothing to be compressed
#Go over the branch starting at the end and remove invisible vertices:
for ivertex in reversed(range(len(particles))):
if particles[ivertex].count('nu') == len(particles[ivertex]):
newelement.branches[ib].masses.pop(ivertex+1)
newelement.branches[ib].particles.pop(ivertex)
else:
break
newelement.branches[ib].setInfo()
newelement.sortBranches()
if newelement == self:
return None
else:
return newelement
[docs] def combineMotherElements ( self, el2 ):
"""
Combine mother elements from self and el2 into self
:parameter el2: element (Element Object)
"""
self.motherElements += el2.motherElements
[docs] def combinePIDs(self,el2):
"""
Combine the PIDs of both elements. If the PIDs already appear in self,
do not add them to the list.
:parameter el2: element (Element Object)
"""
elPIDs = self.getPIDs()
newelPIDs = el2.getPIDs()
for pidlist in newelPIDs:
if not pidlist in elPIDs:
self.branches[0].PIDs.append(pidlist[0])
self.branches[1].PIDs.append(pidlist[1])