Source code for theory.branch

"""
.. module:: branch
   :synopsis: Module holding the branch class and methods.

.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
.. moduleauthor:: Alicia Wongel <alicia.wongel@gmail.com>

"""

from smodels.theory.auxiliaryFunctions import elementsInStr
from smodels.theory.exceptions import SModelSTheoryError as SModelSError
from smodels.tools.smodelsLogging import logger
from smodels.theory.particle import MultiParticle,ParticleList
from smodels.tools.inclusiveObjects import InclusiveValue,InclusiveList



[docs]class Branch(object): """ An instance of this class represents a branch. A branch-element can be constructed from a string (e.g., ('[b,b],[W]'). """ def __init__(self, info=None, finalState=None, intermediateState=None, model=None): """ Initializes the branch. If info is defined, tries to generate the branch using it. :parameter info: string describing the branch in bracket notation (e.g. [[e+],[jet]]) :parameter finalState: final state label string for the branch (e.g. 'MET' or 'HSCP') :parameter intermediateState: list containing intermediate state labels (e.g. ['gluino','C1+']) :parameter model: The model (Model object) to be used when converting particle labels to particle objects (only used if info, finalState or intermediateState != None). """ self.evenParticles = [] self.oddParticles = [] self.maxWeight = None self.vertnumb = None self.vertparts = None if isinstance(info,str): branch = elementsInStr(info) if not branch or len(branch) > 1: raise SModelSError("Wrong input string " + info) if not model or not hasattr(model,'getParticlesWith'): raise SModelSError("A Model object has to be defined when creating branches from strings.") else: branch = branch[0] vertices = elementsInStr(branch[1:-1]) for vertex in vertices: particleNames = vertex[1:-1].split(',') ptcs = [] for pname in particleNames: smParticle = model.getParticlesWith(label=pname) if not smParticle: raise SModelSError("Final state SM particle ``%s'' has not been defined in %s" %(pname,model)) elif len(smParticle) != 1: raise SModelSError("Ambiguous definition of label ``%s'' in %s" %(smParticle[0].label,model)) else: ptcs.append(smParticle[0]) vertexParticles = ParticleList(ptcs) self.evenParticles.append(vertexParticles) self.vertnumb = len(self.evenParticles) self.vertparts = [len(v) for v in self.evenParticles] #Get labels of intermediate states (default is [anyOdd,anyOdd,...,MET]) if intermediateState: if not isinstance(intermediateState,list): raise SModelSError("Intermediate state (``%s'') should be a list)" %intermediateState) bsmLabels = intermediateState[:] else: bsmLabels = ['anyOdd']*self.vertnumb if finalState: bsmLabels.append(finalState) else: bsmLabels.append('MET') if len(bsmLabels) != self.vertnumb+1: raise SModelSError("Number of intermediate states (``%s'') is not consistent)" %intermediateState) for bsmLabel in bsmLabels: bsmParticle = model.getParticlesWith(label=bsmLabel) if not bsmParticle: raise SModelSError("BSM particle ``%s'' has not been defined in databaseParticles.py" %bsmLabel) elif len(bsmParticle) != 1: raise SModelSError("Ambiguous definition of label ``%s'' in databaseParticles.py" %bsmLabel) else: self.oddParticles.append(bsmParticle[0]) def __str__(self): """ Create the branch bracket notation string, e.g. [[e+],[jet]]. :returns: string representation of the branch (in bracket notation) """ sortedParticles = [sorted(vertex, key = lambda ptc: str(ptc)) for vertex in self.evenParticles] st = str(sortedParticles).replace("'", "") st = st.replace(" ", "") return st def __repr__(self): return self.__str__() def __cmp__(self,other): """ Compares the branch with other. The comparison is made based on vertnumb, vertparts, oddParticles and evenParticles. The comparison allows for any ordering of the evenParticles in the vertex. It relies on the particle comparison, which allows for the comparison of Particles and MultiParticles. Only the properties which are defined for both particles are compared. :param other: branch to be compared (Branch object) :return: -1 if self < other, 0 if self == other, +1, if self > other. """ if isinstance(other,InclusiveBranch): return -1*other.__cmp__(self) if self.vertnumb != other.vertnumb: comp = self.vertnumb > other.vertnumb if comp: return 1 else: return -1 elif self.vertparts != other.vertparts: comp = self.vertparts > other.vertparts if comp: return 1 else: return -1 #Compare BSM states: if self.oddParticles != other.oddParticles: comp = self.oddParticles > other.oddParticles if comp: return 1 else: return -1 #Compare even final states (ParticleLists) #The comparison of ParticleList objects is made irrespective of ordering for iv,vertex in enumerate(self.evenParticles): if vertex != other.evenParticles[iv]: comp = vertex > other.evenParticles[iv] if comp: return 1 else: return -1 return 0 #Branches are equal def __lt__( self, b2 ): return self.__cmp__(b2) == -1 def __eq__( self, b2 ): return self.__cmp__(b2) == 0 def __ne__( self, b2 ): return not self.__cmp__(b2) == 0 def __getattr__(self, attr): """ If the attribute has not been defined for the element try to fetch it from its branches. :param attr: Attribute name :return: Attribute value """ #If calling another special method, return default (required for pickling) if attr.startswith('__') and attr.endswith('__'): return object.__getattr__(attr) try: val = [getattr(ptc,attr) for ptc in self.oddParticles] return val except AttributeError: raise AttributeError("Element nor branch has attribute %s" %attr) def __add__(self,other): """ Adds two branches. Should only be used if the branches have the same topologies. The odd and even particles are combined. """ if self.getInfo() != other.getInfo(): raise SModelSError("Can not add branches with distinct topologies") newBranch = self.__class__() #Combine odd particles for iptc,ptc in enumerate(self.oddParticles): newBranch.oddParticles.append(ptc+other.oddParticles[iptc]) #Combine even particles (if they are the same nothing changes) for iv,vertex in enumerate(self.evenParticles): vertexParticles = [] for iptc,ptc in enumerate(vertex): vertexParticles.append(ptc + other.evenParticles[iv][iptc]) vertexParticles = ParticleList(vertexParticles) newBranch.evenParticles.append(vertexParticles) if not self.maxWeight is None and not other.maxWeight is None: newBranch.maxWeight = self.maxWeight + other.maxWeight return newBranch def __radd__(self,other): """ Adds two elements. Only elements with the same topology can be combined. """ return self.__add__(other) def __iadd__(self,other): """ Combine two branches. Should only be used if the elements have the same topologies. The branches odd and even particles are combined. """ if self.getInfo() != other.getInfo(): raise SModelSError("Can not add branches with distinct topologies") #Combine odd particles for iptc,ptc in enumerate(other.oddParticles): self.oddParticles[iptc] += ptc #Combine even particles (if they are the same nothing changes) for iv,vertex in enumerate(self.evenParticles): for iptc,ptc in enumerate(vertex): self.evenParticles[iv][iptc] += other.evenParticles[iv][iptc] if not self.maxWeight is None and not other.maxWeight is None: self.maxWeight += other.maxWeight return self
[docs] def getAverage(self,attr): """ Get the average value for a given attribute appearing in the odd particles of branch. """ try: vals = [] for ptc in self.oddParticles: v = getattr(ptc,attr) if isinstance(ptc,MultiParticle) and isinstance(v,list): avg = v[0] for x in v[1:]: avg += x avg =avg/len(v) else: avg = v vals.append(avg) except (AttributeError,ZeroDivisionError): raise SModelSError("Could not compute average for %s" %attr) return vals
[docs] def setInfo(self): """ Defines the number of vertices (vertnumb) and number of even particles in each vertex (vertpats) properties, if they have not been defined yet. """ bInfo = self.getInfo() self.vertnumb = bInfo['vertnumb'] self.vertparts = bInfo['vertparts']
[docs] def getInfo(self): """ Get branch topology info from evenParticles. :returns: dictionary containing vertices and number of final states information """ vertnumb = len(self.evenParticles) vertparts = [len(v) for v in self.evenParticles] return {"vertnumb" : vertnumb, "vertparts" : vertparts}
[docs] def removeVertex(self,iv): """ Remove vertex iv. The "vertex-mother" in BSMparticles and (SM) particles in the vertex are removed from the branch. The vertex index corresponds to the BSM decay (iv = 0 will remove the first BSM particle,...) :parameter iv: Index of vertex in branch (int) """ self.oddParticles = self.oddParticles[:iv] + self.oddParticles[iv+1:] self.evenParticles = self.evenParticles[:iv] + self.evenParticles[iv+1:] self.setInfo()
[docs] def copy(self): """ Generate an independent copy of self. Faster than deepcopy. :returns: Branch object """ #Allows for derived classes (like inclusive classes) newbranch = self.__class__() newbranch.evenParticles = self.evenParticles[:] newbranch.oddParticles = self.oddParticles[:] self.setInfo() newbranch.vertnumb = self.vertnumb newbranch.vertparts = self.vertparts[:] if not self.maxWeight is None: newbranch.maxWeight = self.maxWeight return newbranch
[docs] def getLength(self): """ Returns the branch length (number of odd particles). :returns: length of branch (number of odd particles) """ return len(self.oddParticles)
def _addDecay(self, decay): """ Generate a new branch adding a 1-step cascade decay This is described by the br object, with particle masses given by BSMList. :parameter decay: Decay object (see pyslha). Contains information about the decay. :returns: extended branch (Branch object). False if there was an error. """ newBranch = self.copy() oddParticles = decay.oddParticles evenParticles = decay.evenParticles if len(oddParticles) != 1: logger.warning("Decay %s does not preserve Z2 and will be ignored" %str(decay)) return False newBranch.oddParticles.append(oddParticles[0]) newBranch.evenParticles.append(evenParticles) if not self.maxWeight is None: newBranch.maxWeight = self.maxWeight*decay.br newBranch.setInfo() return newBranch
[docs] def decayDaughter(self): """ Generate a list of all new branches generated by the 1-step cascade decay of the current branch daughter. :returns: list of extended branches (Branch objects). Empty list if daughter is stable or if daughterID was not defined. """ if not self.oddParticles or not self.oddParticles[-1].decays: return False if self.oddParticles[-1].isStable(): return False newBranches = [] for decay in self.oddParticles[-1].decays: if not decay or not decay.br: continue #Skip decay = None and zero BRs # Generate a new branch for each possible decay: newBr = self._addDecay(decay) if newBr: newBranches.append(newBr) if not newBranches: return False else: return newBranches
[docs]def decayBranches(branchList, sigcut=0.): """ Decay all branches from branchList until all unstable intermediate states have decayed. :parameter branchList: list of Branch() objects containing the initial mothers :parameter sigcut: minimum sigma*BR (in fb) to be generated, by default sigcut = 0. (all branches are kept) :returns: list of branches (Branch objects) """ stableBranches,unstableBranches = [],[] for br in branchList: if br.maxWeight < sigcut: continue if br.decayDaughter(): unstableBranches.append(br) else: stableBranches.append(br) while unstableBranches: # Store branches after adding one step cascade decay newBranchList = [] for inbranch in unstableBranches: if sigcut > 0. and inbranch.maxWeight < sigcut: # Remove the branches above sigcut and with length > topmax continue #If None appear amongst the decays, add the possibility for the particle not decaying prompt if any(x is None for x in inbranch.oddParticles[-1].decays): stableBranches.append(inbranch) # Add all possible decays of the R-odd daughter to the original # branch (if any) newBranches = inbranch.decayDaughter() if newBranches: # New branches were generated, add them for next iteration newBranchList += [br for br in newBranches if br.maxWeight > sigcut] elif inbranch.maxWeight > sigcut: stableBranches.append(inbranch) # Use new unstable branches (if any) for next iteration step unstableBranches = newBranchList #Sort list by initial branch pdg: finalBranchList = sorted(stableBranches, key=lambda branch: branch.oddParticles[0].pdg) return finalBranchList
[docs]class InclusiveBranch(Branch): """ An inclusive branch class. It will return True when compared to any other branch object with the same final state. If intermediateState is defined, it will use all the odd (BSM) particles for comparison while neglecting even particles. """ def __init__(self,finalState=None,intermediateState=None,model=None): """ :parameter info: string describing the branch in bracket notation (e.g. [[e+],[jet]]) :parameter finalState: final state label string for the branch (e.g. 'MET' or 'HSCP') :parameter intermediateState: list containing intermediate state labels (e.g. ['gluino','C1+']) :parameter model: The model (Model object) to be used when converting particle labels to particle objects (only used if info, finalState or intermediateState != None). """ Branch.__init__(self) self.mass = InclusiveList() self.totalwidth = InclusiveList() self.evenParticles = InclusiveList() self.oddParticles = [] #Get labels of intermediate states if intermediateState: if not isinstance(intermediateState,list): raise SModelSError("Intermediate state (``%s'') should be a list)" %intermediateState) bsmLabels = intermediateState[:] else: bsmLabels = [] if finalState: bsmLabels.append(finalState) else: bsmLabels.append('anyOdd') for bsmLabel in bsmLabels: bsmParticle = model.getParticlesWith(label=bsmLabel) if not bsmParticle: raise SModelSError("BSM particle ``%s'' has not been defined in model %s" %(bsmLabel,model)) elif len(bsmParticle) != 1: raise SModelSError("Ambiguous definition of label ``%s'' in model %s" %(bsmLabel,model)) else: self.oddParticles.append(bsmParticle[0]) #If intintermediateState is defined, the number of vertexParticles #(odd particles) must be used for comparison. Otherwise accept any number. if intermediateState: self.vertnumb = len(intermediateState) else: self.vertnumb = InclusiveValue() self.vertparts = InclusiveList() def __cmp__(self,other): """ Always returns true. The only exception is if a final state particle has been defined. In this case, will include the final state in the comparison. The comparison is made based on vertnumb, vertparts, evenParticles, masses of BSM particles and the last BSM particle appearing in the cascade decay. OBS: The particles inside each vertex MUST BE sorted (see branch.sortParticles()) :param other: branch to be compared (Branch object) :return: -1 if self < other, 0 if self == other, +1, if self > other. """ #First compare number of vertices. If no intermediate states were #defined, should always pass if self.vertnumb != other.vertnumb: comp = self.vertnumb > other.vertnumb if comp: return 1 else: return -1 #Compare BSM states in reverse order, so the final state is always the first to be compared. # (if no intermediate states were defined, self.oddParticles contain only the final state) for iptc,ptc in enumerate(self.oddParticles[::-1]): iother = -1-iptc #reverse index if ptc == other.oddParticles[iother]: continue comp = ptc > other.oddParticles[iother] if comp: return 1 else: return -1 return 0 #Branches are equal def __str__(self): return '[*]'
[docs] def getInfo(self): """ Get branch topology info (inclusive list and int). :returns: dictionary containing vertices and number of final states information """ vertnumb = self.vertnumb vertparts = self.vertparts return {"vertnumb" : vertnumb, "vertparts" : vertparts}
[docs] def decayDaughter(self): """ Always return False. """ return False