#!/usr/bin/env python
"""
.. module:: coverage
:synopsis: Definitions of classes used to find, format missing topologies
.. moduleauthor:: Ursula Laa <ursula.laa@lpsc.in2p3.fr>
.. moduleauthor:: Suchita Kulkarni <suchita.kulkarni@gmail.com>
"""
import copy
from smodels.tools.physicsUnits import fb
[docs]class Uncovered(object):
"""
Object collecting all information of non-tested/covered elements
:ivar topoList: sms topology list
:ivar sumL: if true, sum up electron and muon to lepton, for missing topos
:ivar sumJet: if true, sum up jets, for missing topos
"""
def __init__(self, topoList, sumL=True, sumJet=True):
self.sqrts = max([xsec.info.sqrts for xsec in topoList.getTotalWeight()])
self.missingTopos = UncoveredList(sumL, sumJet, self.sqrts)
self.outsideGrid = UncoveredList(sumL, sumJet, self.sqrts) # FIXME change this to derived objects for printout
self.longCascade = UncoveredClassifier()
self.asymmetricBranches = UncoveredClassifier()
self.motherIDs = []
self.getAllMothers(topoList)
self.fill(topoList)
self.asymmetricBranches.combine()
self.longCascade.combine()
[docs] def getAllMothers(self, topoList):
"""
Find all IDs of mother elements, only most compressed element can be missing topology
:ivar topoList: sms topology list
"""
for el in topoList.getElements():
for mEl in el.motherElements:
motherID = mEl[-1].elID
if not motherID in self.motherIDs: self.motherIDs.append(motherID)
[docs] def fill(self, topoList):
"""
Check all elements, categorise those not tested / missing, classify long cascade decays and asymmetric branches
Fills all corresponding objects
:ivar topoList: sms topology list
"""
for el in topoList.getElements():
missing = self.isMissingTopo(el) #missing topo only if not mother, covered, mother covered
if not missing: # if not missing check if it is actually tested
if not el.tested:
self.outsideGrid.addToTopos(el) # not missing but not tested means we are outside the mass grid
continue
self.missingTopos.addToTopos(el) #keep track of all missing topologies
if self.hasLongCascade(el): self.longCascade.addToClasses(el)
elif self.hasAsymmetricBranches(el): self.asymmetricBranches.addToClasses(el) # if no long cascade, check for asymmetric branches
[docs] def hasLongCascade(self, el):
"""
Return True if element has more than 3 particles in the decay chain
:ivar el: Element
"""
if el._getLength() > 3: return True
return False
[docs] def hasAsymmetricBranches(self, el):
"""
Return True if Element branches are not equal
:ivar el: Element
"""
if el.branches[0] == el.branches[1]: return False
return True
[docs] def isMissingTopo(self, el):
"""
A missing topology is not a mother element, not covered, and does not have mother which is covered
:ivar el: Element
"""
if el.elID in self.motherIDs: return False
if el.covered: return False
if self.motherCovered(el): return False
return True
[docs] def motherCovered(self, el):
"""
Recursively check if a mother of the given Element is covered
:ivar el: Element
"""
mothers = el.motherElements
while mothers:
newmothers = []
for mother in mothers:
if mother[-1].covered: return True
newmothers += mother[-1].motherElements
mothers = newmothers
return False
[docs] def getMissingXsec(self, sqrts=None):
"""
Calculate total missing topology cross section at sqrts. If no sqrts is given use self.sqrts
:ivar sqrts: sqrts
"""
xsec = 0.
if not sqrts: sqrts = self.sqrts
for topo in self.missingTopos.topos:
for el in topo.contributingElements:
if not el.weight.getXsecsFor(sqrts): continue
xsec += el.weight.getXsecsFor(sqrts)[0].value.asNumber(fb)
return xsec
[docs] def getOutOfGridXsec(self, sqrts=None): #FIXME same as getMissingXsec but different object, should not be separate functions
xsec = 0.
if not sqrts: sqrts = self.sqrts
for topo in self.outsideGrid.topos:
for el in topo.contributingElements:
if not el.weight.getXsecsFor(sqrts): continue
xsec += el.weight.getXsecsFor(sqrts)[0].value.asNumber(fb)
return xsec
[docs] def getLongCascadeXsec(self, sqrts=None):
xsec = 0.
if not sqrts: sqrts = self.sqrts
for uncovClass in self.longCascade.classes:
for el in uncovClass.contributingElements:
if not el.weight.getXsecsFor(sqrts): continue
xsec += el.weight.getXsecsFor(sqrts)[0].value.asNumber(fb)
return xsec
[docs] def getAsymmetricXsec(self, sqrts=None):
xsec = 0.
if not sqrts: sqrts = self.sqrts
for uncovClass in self.asymmetricBranches.classes:
for el in uncovClass.contributingElements:
if not el.weight.getXsecsFor(sqrts): continue
xsec += el.weight.getXsecsFor(sqrts)[0].value.asNumber(fb)
return xsec
[docs]class UncoveredClassifier(object):
"""
Object collecting elements with long cascade decays or asymmetric branches.
Objects are grouped according to the initially produced particle PID pair.
"""
def __init__(self):
self.classes = []
[docs] def addToClasses(self, el):
"""
Add Element in corresponding UncoveredClass, defined by mother PIDs.
If no corresponding class in self.classes, add new UncoveredClass
:ivar el: Element
"""
motherPIDs = self.getMotherPIDs(el)
for entry in self.classes:
if entry.add(motherPIDs, el): return
self.classes.append(UncoveredClass(motherPIDs, el))
[docs] def getMotherPIDs(self, el):
allPIDs = []
for pids in el.getMothers():
cPIDs = []
for pid in pids:
cPIDs.append(abs(pid))
cPIDs.sort()
if not cPIDs in allPIDs:
allPIDs.append(cPIDs)
allPIDs.sort()
return allPIDs
[docs] def combine(self):
for ecopy in copy.deepcopy(self.classes):
for e in self.classes:
if e.isSubset(ecopy):
e.combine(ecopy)
self.remove(ecopy)
[docs] def remove(self, cl):
"""
Remove element where mother pids match exactly
"""
for i, o in enumerate(self.classes):
if o.motherPIDs == cl.motherPIDs:
del self.classes[i]
break
[docs] def getSorted(self,sqrts):
"""
Returns list of UncoveredClass objects in self.classes, sorted by weight
:ivar sqrts: sqrts for weight lookup
"""
return sorted(self.classes, key=lambda x: x.getWeight(sqrts).asNumber(fb), reverse=True)
[docs]class UncoveredClass(object):
"""
Object collecting all elements contributing to the same uncovered class, defined by the mother PIDs.
:ivar motherPIDs: PID of initially produces particles, sorted and without charge information
:ivar el: Element
"""
def __init__(self, motherPIDs, el):
self.motherPIDs = motherPIDs # holds nested list of mother PIDs as given by element.getMothers
self.contributingElements = [el] # collect all contributing elements, to keep track of weights as well
[docs] def add(self, motherPIDs, el):
"""
Add Element to this UncoveredClass object if motherPIDs match and return True, else return False
:ivar motherPIDs: PID of initially produces particles, sorted and without charge information
:ivar el: Element
"""
if not motherPIDs == self.motherPIDs: return False
self.contributingElements.append(el)
return True
[docs] def combine(self, other):
for el in other.contributingElements:
self.contributingElements.append(el)
[docs] def getWeight(self, sqrts):
"""
Calculate weight at sqrts
:ivar sqrts: sqrts
"""
xsec = 0.*fb
for el in self.contributingElements:
elxsec = el.weight.getXsecsFor(sqrts)
if not elxsec: continue
xsec += elxsec[0].value
return xsec
[docs] def isSubset(self, other):
"""
True if motherPIDs of others are subset of the motherPIDs of this UncoveredClass
"""
if len(other.motherPIDs) >= len(self.motherPIDs): return False
for mothers in other.motherPIDs:
if not mothers in self.motherPIDs: return False
return True
[docs]class UncoveredTopo(object):
"""
Object to describe one missing topology result / one topology outside the mass grid
:ivar topo: topology description
:ivar weights: weights dictionary
"""
def __init__(self, topo, weights, contributingElements=[]):
self.topo = topo
self.contributingElements = contributingElements
self.value = 0. # weight for sqrts set in uncoveredList, only set this in printout
[docs]class UncoveredList(object):
"""
Object to find and collect UncoveredTopo objects, plus printout functionality
:ivar sumL: if true sum electrons and muons to leptons
:ivar sumJet: if true, sum up jets
:ivar sqrts: sqrts, for printout
"""
def __init__(self, sumL, sumJet, sqrts):
self.topos = []
self.sumL = sumL
self.sumJet = sumJet
self.sqrts = sqrts
[docs] def addToTopos(self, el):
"""
adds an element to the list of missing topologies
if the element contributes to a missing topology that is already
in the list, add weight to topology
:parameter el: element to be added
"""
name = self.orderbranches(self.generalName(el.__str__()))
for topo in self.topos:
if name == topo.topo:
topo.contributingElements.append(el)
return
self.topos.append(UncoveredTopo(name, el.weight, [el]))
return
[docs] def generalName(self, instr):
"""
generalize by summing over charges
e, mu are combined to l
:parameter instr: element as string
:returns: string of generalized element
"""
from smodels.theory.particleNames import ptcDic
if self.sumL: exch = ["W", "l", "t", "ta"]
else: exch = ["W", "e", "mu", "t", "ta"]
if self.sumJet: exch.append("jet")
for pn in exch:
for on in ptcDic[pn]:
instr = instr.replace(on, pn).replace("hijetjets","higgs")
return instr
[docs] def orderbranches(self, instr):
"""
unique ordering of branches
:parameter instr: element as string
:returns: string of ordered element
"""
from smodels.theory.element import Element
li = Element(instr).getParticles()
for be in li:
for ve in be:
ve.sort()
li.sort()
return str(li).replace("'", "").replace(" ", "")