Source code for theory.branch

"""
.. module:: branch
   :synopsis: Module holding the branch class and methods.
        
.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
        
"""

from smodels.theory.particleNames import simParticles, elementsInStr
from smodels.tools.physicsUnits import fb
from smodels.theory.particleNames import ptcDic, finalStates, getFinalStateLabel, InclusiveStr
from smodels.theory.exceptions import SModelSTheoryError as SModelSError
from smodels.tools.smodelsLogging import logger


[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]'). :ivar masses: list of masses for the intermediate states :ivar particles: list of particles (strings) for the final states :ivar PIDs: a list of the pdg numbers of the intermediate states appearing in the branch. If the branch represents more than one possible pdg list, PIDs will correspond to a nested list (PIDs = [[pid1,pid2,...],[pidA,pidB,...]) :ivar maxWeight: weight of the branch (XSection object) """ def __init__(self, info=None, finalState=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') """ from smodels.particlesLoader import rEven self.masses = [] self.particles = [] self.PIDs = [] self.maxWeight = None self.vertnumb = None self.vertparts = None self.stable = False self.finalState = None if type(info) == type(str()): branch = elementsInStr(info) if not branch or len(branch) > 1: logger.error("Wrong input string " + info) raise SModelSError() else: branch = branch[0] vertices = elementsInStr(branch[1:-1]) for vertex in vertices: ptcs = vertex[1:-1].split(',') # Syntax check: for i,ptc in enumerate(ptcs): if ptc == "*": ptc = InclusiveStr() ptcs[i] = ptc if not ptc in rEven.values() \ and not ptc in list(ptcDic.keys()): raise SModelSError("Unknown particle. Add " + ptc + " to smodels/particle.py") self.particles.append(ptcs) self.vertnumb = len(self.particles) self.vertparts = [len(v) for v in self.particles] self.setFinalState(finalState) def __str__(self): """ Create the branch bracket notation string, e.g. [[e+],[jet]]. :returns: string representation of the branch (in bracket notation) """ st = str(self.particles).replace("'", "") st = st.replace(" ", "") return st def __cmp__(self,other): """ Compares the branch with other. The comparison is made based on . 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. """ if not isinstance(other,Branch): return -1 if not self.vertnumb == other.vertnumb: comp = self.vertnumb > other.vertnumb if comp: return 1 else: return -1 elif not self.vertparts == other.vertparts: comp = self.vertparts > other.vertparts if comp: return 1 else: return -1 elif not self.particles == other.particles: comp = self.particles > other.particles if comp: return 1 else: return -1 elif not self.masses == other.masses: comp = self.masses > other.masses if comp: return 1 else: return -1 elif not self.finalState == other.finalState: comp = self.finalState > other.finalState if comp: return 1 else: return -1 else: return 0 #Branches are equal
[docs] def sortParticles(self): """ Sort the particles inside each vertex """ for iv,vertex in enumerate(self.particles): self.particles[iv] = sorted(vertex)
[docs] def setInfo(self): """ Defines the number of vertices (vertnumb) and number of 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 particles. :returns: dictionary containing vertices and number of final states information """ vertnumb = len(self.particles) vertparts = [len(v) for v in self.particles] return {"vertnumb" : vertnumb, "vertparts" : vertparts}
[docs] def setFinalState(self,finalState=None): """ If finalState = None, define the branch final state according to the PID of the last R-odd particle appearing in the cascade decay. Else set the final state to the finalState given :parameter finalState: String defining the final state """ if finalState: if finalState == '*': finalState = InclusiveStr() if not finalState in list(finalStates.keys()): raise SModelSError("Final state %s has not been defined. Add it to particles.py." %finalState) else: self.finalState = finalState #If PIDs have been defined, use it: elif self.PIDs: fStates = set() for pidList in self.PIDs: fStates.add(getFinalStateLabel(pidList[-1])) if len(fStates) != 1: logger.error("Error obtaining the final state for branch %s" %self) raise SModelSError else: self.finalState = list(fStates)[0] #Else do nothing else: self.finalState = None
[docs] def particlesMatch(self, other): """ Compare two Branches for matching particles, allow for inclusive particle labels (such as the ones defined in particles.py). Includes the final state in the comparison. :parameter other: branch to be compared (Branch object) :returns: True if branches are equal (particles and masses match); False otherwise. """ #First of all check final state: if self.finalState != other.finalState: return False #If particles are identical, avoid further checks if self.particles == other.particles: return True if not isinstance(other,Branch): return False #Make sure number of vertices and particles have been defined self.setInfo() other.setInfo() if self.vertnumb != other.vertnumb: return False if self.vertparts != other.vertparts: return False for iv,vertex in enumerate(self.particles): if not simParticles(vertex,other.particles[iv]): return False return True
[docs] def copy(self): """ Generate an independent copy of self. Faster than deepcopy. :returns: Branch object """ #Allows for derived classes (like wildcard classes) newbranch = self.__class__() newbranch.masses = self.masses[:] newbranch.particles = self.particles[:] newbranch.finalState = self.finalState newbranch.PIDs = [] newbranch.stable = self.stable self.setInfo() newbranch.vertnumb = self.vertnumb newbranch.vertparts = self.vertparts[:] for pidList in self.PIDs: newbranch.PIDs.append(pidList[:]) if not self.maxWeight is None: newbranch.maxWeight = self.maxWeight.copy() return newbranch
[docs] def getLength(self): """ Returns the branch length (number of R-odd particles). :returns: length of branch (number of R-odd particles) """ return len(self.masses)
def __lt__( self, b2 ): return self.__cmp__ ( b2 ) == -1 def __eq__( self, b2 ): return self.__cmp__ ( b2 ) == 0 def _addDecay(self, br, massDictionary): """ Generate a new branch adding a 1-step cascade decay This is described by the br object, with particle masses given by massDictionary. :parameter br: branching ratio object (see pyslha). Contains information about the decay. :parameter massDictionary: dictionary containing the masses for all intermediate states. :returns: extended branch (Branch object). False if there was an error. """ from smodels.particlesLoader import rEven newBranch = self.copy() newparticles = [] newmass = [] if len(self.PIDs) != 1: logger.error("During decay the branch should \ not have multiple PID lists!") return False for partID in br.ids: # Add R-even particles to final state if partID in rEven: newparticles.append(rEven[partID]) else: # Add masses of non R-even particles to mass vector newmass.append(massDictionary[partID]) newBranch.PIDs[0].append(partID) if len(newmass) > 1: logger.warning("Multiple R-odd particles in the final state: " + str(br.ids)) return False if newparticles: newBranch.particles.append(sorted(newparticles)) if newmass: newBranch.masses.append(newmass[0]) if not self.maxWeight is None: newBranch.maxWeight = self.maxWeight * br.br #If there are no daughters, assume branch is stable if not br.ids: newBranch.stable = True return newBranch
[docs] def decayDaughter(self, brDictionary, massDictionary): """ Generate a list of all new branches generated by the 1-step cascade decay of the current branch daughter. :parameter brDictionary: dictionary with the decay information for all intermediate states (values are br objects, see pyslha) :parameter massDictionary: dictionary containing the masses for all intermediate states. :returns: list of extended branches (Branch objects). Empty list if daughter is stable or if daughterID was not defined. """ if len(self.PIDs) != 1: logger.error("Can not decay branch with multiple PID lists") return False if not self.PIDs[0][-1]: # Do nothing if there is no R-odd daughter (relevant for RPV decays # of the LSP) self.stable = True return [self] #If decay table is not defined, assume daughter is stable: if not self.PIDs[0][-1] in brDictionary: self.stable = True return [self] # List of possible decays (brs) for R-odd daughter in branch brs = brDictionary[self.PIDs[0][-1]] newBranches = [] for br in brs: if not br.br: continue #Skip zero BRs # Generate a new branch for each possible decay (including "stable decays") newBranches.append(self._addDecay(br, massDictionary)) if not newBranches: # Daughter is stable, there are no new branches self.stable = True return [self] else: return newBranches
[docs]class InclusiveBranch(Branch): """ A branch wildcard class. It will return True when compared to any other branch object with the same final state. """ def __init__(self): Branch.__init__(self) self.masses = InclusiveList() self.particles = InclusiveList() self.PIDs = InclusiveList() self.vertnumb = InclusiveInt() self.vertparts = InclusiveList() self.finalState = InclusiveStr() def __str__(self): return '[*]' def __repr__(self): return self.__str__() def __eq__(self,other): return self.__cmp__(other) == 0 def __ne__(self,other): return self.__cmp__(other) != 0
[docs] def getInfo(self): """ Get branch topology info from particles. :returns: dictionary containing vertices and number of final states information """ vertnumb = InclusiveInt() vertparts = InclusiveList() return {"vertnumb" : vertnumb, "vertparts" : vertparts}
[docs]class InclusiveInt(int): """ A integer wildcard class. It will return True when compared to any other integer object. """ def __init__(self): int.__init__(self) def __str__(self): return '*' def __repr__(self): return self.__str__() def __cmp__(self,other): if isinstance(other,int): return 0 else: return -1 def __eq__(self,other): return self.__cmp__(other) == 0 def __ne__(self,other): return self.__cmp__(other) != 0
[docs]class InclusiveList(list): """ A list wildcard class. It will return True when compared to any other list object. """ def __init__(self): int.__init__(self) def __str__(self): return '[*]' def __repr__(self): return self.__str__() def __cmp__(self,other): if isinstance(other,list): return 0 else: return -1 def __eq__(self,other): return self.__cmp__(other) == 0 def __ne__(self,other): return self.__cmp__(other) != 0
[docs]def decayBranches(branchList, brDictionary, massDictionary, sigcut=0. *fb): """ Decay all branches from branchList until all unstable intermediate states have decayed. :parameter branchList: list of Branch() objects containing the initial mothers :parameter brDictionary: dictionary with the decay information for all intermediate states (values are br objects, see pyslha). It may also contain information about long-lived particles. :parameter massDictionary: dictionary containing the masses for all intermediate states. :parameter promptDictionary: optional dictionary with the fraction of prompt and non-prompt decays. Allows to deal with quasi-stable or long-lived particles If not given, all particles are considered to always decay promptly or to be stable. :parameter sigcut: minimum sigma*BR to be generated, by default sigcut = 0. (all branches are kept) :returns: list of branches (Branch objects) """ #Check for stable branches stableBranches = [branch for branch in branchList if branch.stable] unstableBranches = [branch for branch in branchList if not branch.stable] if not unstableBranches: #All branches have been decayed: finalBranches = sorted(stableBranches, key=lambda branch: branch.PIDs) return finalBranches else: #Decayed unstable branches newBranches = [] for branch in unstableBranches: newBranches += [br for br in branch.decayDaughter(brDictionary, massDictionary) if br.maxWeight >= sigcut] return decayBranches(newBranches+stableBranches,brDictionary, massDictionary,sigcut)