Source code for tools.ioObjects

#!/usr/bin/env python

"""
.. module:: tools.ioObjects
   :synopsis: Definitions of input/output parameters which are read from parameter.in.
    
.. moduleauthor:: Ursula Laa <Ursula.Laa@assoc.oeaw.ac.at>    
.. moduleauthor:: Suchita Kulkarni <suchita.kulkarni@gmail.com>

"""

import os, sys
from smodels.theory import lheReader
from smodels.theory.printer import Printer
from smodels.tools.physicsUnits import GeV, fb
from smodels.tools import modpyslha as pyslha
from smodels.particles import qNumbers, rEven
from smodels.theory import crossSection
import logging

logger = logging.getLogger(__name__)

[docs]class ResultList(Printer): """ Class that collects experimental constraints and has a predefined printout. :ivar outputarray: list of theorypredictions :ivar bestresultonly: if True, printout will print only the best result :ivar describeTopo: if True, printout will print the constraints """ def __init__(self, outputarray=[], bestresultonly=None, describeTopo=None): self.outputarray = outputarray self.bestresultonly = bestresultonly self.describeTopo = describeTopo
[docs] def addResult(self, res, maxcond): """ Add a result to the outputarry, unless it violates maxcond. :parameter res: theoryprediction to be added to ResultList :parameter maxcond: maximum condition violation """ mCond = res.getmaxCondition() if mCond == 'N/A': return if mCond > maxcond: return self.outputarray.append(res) return
[docs] def getR(self, res): """ Calculate R value. :parameter res: theoryprediction :returns: R value = weight / upper limit """ return res.value[0].value / res.analysis.getUpperLimitFor(res.mass)
[docs] def sort(self): """ Reverse sort outputarray by R value. """ self.outputarray = sorted(self.outputarray, key=self.getR, reverse=True) return
[docs] def getBestResult(self): """ Return best result. """ self.sort() return self.outputarray[0]
[docs] def isEmpty(self): """ Check if outputarray is empty. """ return len(self.outputarray) == 0
[docs] def formatData(self, outputLevel): """ Access printout format. :param outputLevel: general control for the output depth to be printed (0 = no output, 1 = basic output, 2 = detailed output,... """ return self.formatResultsData(outputLevel)
[docs]class OutputStatus(Printer): """ Object that holds all status information and has a predefined printout. :ivar status: status of input file :ivar inputFile: input file name :ivar parameters: input parameters :ivar databaseVersion: database version (string) :ivar outputfile: path to outputfile """ def __init__(self, status, inputFile, parameters, databaseVersion, outputfile): """ Initialize output. If one of the checks failed, exit. """ self.outputfile = outputfile self.inputfile = inputFile self.parameters = parameters self.filestatus = status[0] self.warnings = status[1] self.databaseVersion = databaseVersion self.statusStrings = {-1: "#could not run the decomposition", - 3: "#no cross sections above sigmacut found", - 4: "#database not found", - 2: "#bad input file, did not run decomposition", 0: "#no matching experimental results", 1: "#decomposition was successful"} self.status = 0 if not self.databaseVersion or self.databaseVersion < 0: self.status = -4 if self.filestatus < 0: self.status = -2 self.checkStatus()
[docs] def checkStatus(self): """ Printout negative status. """ if self.status < 0: self.printout("stdout") self.printout("file", self.outputfile) return self.status
[docs] def updateStatus(self, status): """ Update status. :parameter status: new status flag """ self.status = status return self.checkStatus()
[docs] def updateSLHAStatus(self, status): """ Update SLHA status. :parameter status: new SLHA status flag """ self.slhastatus = status return
[docs] def addWarning(self, warning): """ Append warning to warnings. :parameter warning: warning to be appended """ self.warnings += warning return
[docs] def formatData(self, outputLevel): """ Access printout format. :param outputLevel: general control for the output depth to be printed (0 = no output, 1 = basic output, 2 = detailed output,... """ return self.formatStatusData(outputLevel)
[docs]class FileStatus(Printer): """ Object to run several checks on the input file. It holds an LheStatus (SlhaStatus) object if inputType = lhe (slha) :ivar inputType: specify input type as SLHA or LHE :ivar inputFile: path to input file :ivar sigmacut: sigmacut in fb """ def __init__(self): self.filestatus = None self.status = 0, "File not checked\n"
[docs] def checkFile(self, inputType, inputFile, sigmacut=None): if inputType == 'lhe': self.filestatus = LheStatus(inputFile) self.status = self.filestatus.status elif inputType == 'slha': self.filestatus = SlhaStatus(inputFile, sigmacut=sigmacut) self.status = self.filestatus.status else: self.filestatus = None self.status = -5, 'Unknown input type: %s' % self.inputType
[docs]class LheStatus(Printer): """ Object to check if input lhe file contains errors. :ivar filename: path to input LHE file """ def __init__(self, filename): self.filename = filename self.status = self.evaluateStatus()
[docs] def evaluateStatus(self): """ run status check """ if not os.path.exists(self.filename): # set status flag to -3, as in slha checks for missing input file return -3, "Inputfile %s not found" % self.filename lhe = lheReader.LheReader(self.filename) nevents = lhe.metainfo["nevents"] totxsec = lhe.metainfo["totalxsec"] sqrts = lhe.metainfo["sqrts"] if (not type(sqrts) == type(1 * GeV)) or (not sqrts.asNumber()): return -1, "Center-of-mass energy not found in the input LHE file %s" % self.filename elif not nevents: return -1, "No events found in the input LHE file %s" % self.filename elif (not type(totxsec) == type(1 * fb)) or (not totxsec.asNumber()): return -1, "Total cross-section not found in the input LHE file %s" % self.filename return 1, "Input file ok"
[docs]class SlhaStatus(Printer): """ An instance of this class represents the status of an SLHA file. The output status is: = 0 : the file is not checked, = 1: the check is ok = -1: case of a physical problem, e.g. charged LSP, = -2: case of formal problems, e.g. no cross sections :ivar filename: path to input SLHA file :ivar maxDisplacement: maximum c*tau for promt decays in meters :ivar sigmacut: sigmacut in fb :ivar checkLSP: if True check if LSP is neutral :ivar findMissingDecayBlocks: if True add a warning for missing decay blocks :ivar findIllegalDecays: if True check if all decays are kinematically allowed :ivar checkXsec: if True check if SLHA file contains cross sections :ivar findLonglived: if True find stable charged particles and displaced vertices """ def __init__(self, filename, maxDisplacement=.01, sigmacut=.01 * fb, checkLSP=True, findMissingDecayBlocks=True, findIllegalDecays=False, checkXsec=True, findLonglived=True): self.filename = filename self.maxDisplacement = maxDisplacement self.sigmacut = sigmacut self.slha = self.read() if not self.slha: self.status = -3, "Could not read input SLHA file" return self.lsp = self.findLSP() self.lspStatus = self.testLSP(checkLSP) self.illegalDecays = self.findIllegalDecay(findIllegalDecays) self.xsec = self.hasXsec(checkXsec) self.decayBlocksStatus = self.findMissingDecayBlocks(findMissingDecayBlocks) self.longlived = self.findLonglivedParticles(findLonglived) self.status = self.evaluateStatus()
[docs] def read(self): """ Get pyslha output object. """ try: ret = pyslha.readSLHAFile(self.filename) except: return None if not ret.blocks["MASS"]: return None return ret
[docs] def evaluateStatus(self): """ Get status summary from all performed checks. :returns: a status flag and a message for explanation """ if not self.slha: return -3, "Could not read input slha file" ret = 0 warning = None retMes = "#Warnings:\n" st , message = self.decayBlocksStatus # add only warning, no negative staus flag in case of missing decay blocks if st < 0: warning = True retMes += message + "\n" for st, message in [self.xsec]: if st < 0: ret = -2 retMes = retMes + "#" + message + ".\n" elif st == 1 and not ret == -2: ret = 1 for st, message in [self.lspStatus, self.longlived, self.illegalDecays]: if st < 0: ret = -1 retMes = retMes + "#" + message + "\n" elif st == 1 and ret >= 0: ret = 1 if ret == 0: return 0, "No checks performed" if ret == -1: return -1, "#ERROR: special signatures in this point.\n" + retMes if ret == -2: return -2, retMes if not warning: retMes = "Input file ok" return ret, retMes
[docs] def emptyDecay(self, pid): """ Check if any decay is listed for the particle with pid :parameter pid: PID number of particle to be checked :returns: True if the decay block is missing or if it is empty, None otherwise """ if not abs(pid) in self.slha.decays: return True # consider missing decay block as empty if not self.slha.decays[abs(pid)].decays: return True return None
[docs] def findMissingDecayBlocks(self, findMissingBlocks): """ For all non-rEven particles listed in mass block, check if decay block is written :returns: status flag and message """ if not findMissingBlocks: return 0, "Did not check for missing decay blocks" st = 1 missing = [] pids = self.slha.blocks["MASS"].keys() for pid in pids: if pid in rEven: continue if not pid in self.slha.decays: missing.append(pid) st = -1 if st == 1: msg = "No missing decay blocks" else: msg = "# Missing decay blocks for %s" % str(missing) return st, msg
[docs] def findIllegalDecay(self, findIllegal): """ Find decays for which the sum of daughter masses excels the mother mass :parameter findIllegal: True if check should be run :returns: status flag and message """ if not findIllegal: return 0, "Did not check for illegal decays" st = 1 badDecay = "Illegal decay for PIDs " for particle, block in self.slha.decays.items(): if particle in rEven : continue if not particle in self.slha.blocks["MASS"].keys(): continue mMom = abs(self.slha.blocks["MASS"][particle]) for dcy in block.decays: mDau = 0. for ptc in dcy.ids: ptc = abs(ptc) if ptc in SMmasses: mDau += SMmasses[ptc] elif ptc in self.slha.blocks["MASS"].keys(): mDau += abs(self.slha.blocks["MASS"][ptc]) else: return -2, "Unknown PID %s in decay of %s" % (str(ptc), str(particle) + ". Add " + str(ptc) + " to smodels/particle.py") if mDau > mMom: st = -1 if not str(particle) in badDecay: badDecay += str(particle) + " " if st == 1: badDecay = "No illegal decay blocks" return st, badDecay
[docs] def hasXsec(self, checkXsec): """ Check if XSECTION table is present in the slha file. :parameter checkXsec: set True to run the check :returns: status flag, message """ if not checkXsec: return 0, "Did not check for missing XSECTION table" f = open(self.filename) for line in f: if "XSECTION" in line: return 1, "XSECTION table present" msg = "XSECTION table is missing. Please include the cross-section information and try again.\n" msg += "\n\t For MSSM models, it is possible to compute the MSSM cross-sections" msg += " using Pythia through the command:\n\n" msg += "\t ./runTools.py xseccomputer -p -f " + self.filename + " \n\n" msg += "\t For more options and information run: ./runTools.py xseccomputer -h\n" logger.error(msg) return -1, msg
[docs] def testLSP(self, checkLSP): """ Check if LSP is charged. :parameter checkLSP: set True to run the check :returns: status flag, message """ if not checkLSP: return 0, "Did not check for charged lsp" qn = Qnumbers(self.lsp) if qn.pid == 0: return -1, "lsp pid " + str(self.lsp) + " is not known\n" if qn.charge3 != 0 or qn.cdim != 1: return -1, "lsp has 3*electrical charge = " + str(qn.charge3) + \ " and color dimension = " + str(qn.cdim) + "\n" return 1, "lsp is neutral"
[docs] def findLSP(self, returnmass=None): """ Find lightest particle (not in rEven). :returns: pid, mass of the lsp, if returnmass == True """ pid = 0 minmass = None for particle, mass in self.slha.blocks["MASS"].items(): if particle in rEven: continue mass = abs(mass) if minmass == None: pid, minmass = particle, mass if mass < minmass: pid, minmass = particle, mass if returnmass: return pid, minmass * GeV return pid
[docs] def getLifetime(self, pid, ctau=False): """ Compute lifetime from decay-width for a particle with pid. :parameter pid: PID of particle :parameter ctau: set True to multiply lifetime by c :returns: lifetime """ widths = self.getDecayWidths() try: if widths[abs(pid)]: lt = (1.0 / widths[abs(pid)]) / 1.51926778e24 else: # Particle is stable return -1 if self.emptyDecay(pid): return -1 # if decay block is empty particle is also considered stable if ctau: return lt * 3E8 else: return lt except KeyError: logger.warning("No decay block for %s, consider it as a stable particle") return -1
[docs] def sumBR(self, pid): """ Calculate the sum of all branching ratios for particle with pid. :parameter pid: PID of particle :returns: sum of branching ratios as given in the decay table for pid """ decaylist = self.slha.decays[pid].decays totalBR = 0. for entry in decaylist: totalBR += entry.br return totalBR
[docs] def deltaMass(self, pid1, pid2): """ Calculate mass splitting between particles with pid1 and pid2. :returns: mass difference """ m1 = self.slha.blocks["MASS"][pid1] m2 = self.slha.blocks["MASS"][pid2] dm = abs(abs(m1) - abs(m2)) return dm
[docs] def findNLSP(self, returnmass=None): """ Find second lightest particle (not in rEven). :returns: pid ,mass of the NLSP, if returnmass == True """ lsp = self.findLSP() pid = 0 minmass = None for particle, mass in self.slha.blocks["MASS"].items(): mass = abs(mass) if particle == lsp or particle in rEven: continue if minmass == None: pid, minmass = particle, mass if mass < minmass: pid, minmass = particle, mass if returnmass: return pid, minmass * GeV return pid
[docs] def getDecayWidths(self): """ Get all decay-widths as a dictionary {pid: width}. """ widths = {} for particle, block in self.slha.decays.items(): widths[particle] = block.totalwidth return widths
[docs] def getDecayWidth(self, pid): """ Get the decay-width for particle with pid, if it exists. """ widths = self.getDecayWidths() try: return widths[pid] except KeyError: print("%s is no valid PID" % pid)
[docs] def massDiffLSPandNLSP(self): """ Get the mass difference between the lsp and the nlsp. """ lsp = self.findLSP() nlsp = self.findNLSP() return self.deltaMass(lsp, nlsp)
[docs] def findLonglivedParticles(self, findLonglived): """ Find meta-stable particles that decay to visible particles and stable charged particles. :returns: status flag, message """ if not findLonglived: return 0, "Did not check for long lived particles" # Get list of cross-sections: xsecList = crossSection.getXsecFromSLHAFile(self.filename) # Check if any of particles being produced have visible displaced vertices # with a weight > sigmacut chargedList = [] missingList = [] ltstr = "" for pid in xsecList.getPIDs(): if pid in rEven: continue if pid == self.findLSP(): continue xsecmax = xsecList.getXsecsFor(pid).getMaxXsec() if xsecmax < self.sigmacut: continue lt = self.getLifetime(pid, ctau=True) if lt < 0: # error for stable charged particles if self.visible(abs(pid)): if not abs(pid) in chargedList: chargedList.append(abs(pid)) if not str(abs(pid)) in ltstr: ltstr += "#%s : c*tau = inf\n" % str(abs(pid)) if lt < self.maxDisplacement: continue brvalue = 0. daughters = [] # Sum all BRs which contain at least one visible particle for decay in self.slha.decays[abs(pid)].decays: for pidb in decay.ids: if self.visible(abs(pidb), decay=True): brvalue += decay.br daughters.append(decay.ids) break elif self.visible(abs(pidb), decay=True) == None: if not abs(pidb) in missingList: missingList.append(abs(pidb)) if xsecmax * brvalue > self.sigmacut: if not abs(pid) in chargedList: chargedList.append(abs(pid)) if not str(abs(pid)) in ltstr: ltstr += "#%s : c*tau = %s\n" % (str(abs(pid)), str(lt)) if not chargedList and not missingList: return 1, "no long lived particles found" else: msg = "" if chargedList: msg += "#Visible decays of longlived particles / stable charged particles: %s\n%s" % (str(chargedList), ltstr) if missingList: msg += "#Missing decay blocks of new r-Even particles appearing in displaced vertices: %s\n" % (str(missingList)) return -1, msg
[docs] def degenerateChi(self): """ Check if chi01 is lsp and chipm1 is NLSP. If so, check mass splitting. This function is not used, the limit is arbitrary. """ lsp, m1 = self.findLSP(returnmass=True) nlsp, m2 = self.findNLSP(returnmass=True) if lsp == 1000022 and nlsp == 1000024: if abs(m2) - abs(m1) < 0.18: return True return None
[docs] def visible(self, pid, decay=None): """ Check if pid is detectable. If pid is not known, consider it as visible. If pid not SM particle and decay = True, check if particle or decay products are visible. """ if pid in SMvisible: return True if pid in SMinvisible: return False qn = Qnumbers(pid) if qn.pid == 0: return True if qn.charge3 != 0 or qn.cdim != 1: return True if decay: if not pid in self.slha.decays: logger.warning("Missing decay block for pid %s" % (str(pid))) return None # Note: purposely distinguished from False so I can propagate the information to the output file for decay in self.slha.decays[pid].decays: for pids in decay.ids: if self.visible(abs(pids), decay=True): return True return False
[docs]class Qnumbers: """ An instance of this class represents quantum numbers. Get quantum numbers (spin*2, electrical charge*3, color dimension) from qNumbers. """ def __init__(self, pid): self.pid = pid if not pid in qNumbers.keys(): self.pid = 0 else: self.l = qNumbers[pid] self.spin2 = self.l[0] self.charge3 = self.l[1] self.cdim = self.l[2]
SMmasses = {1: 4.8e-3 , 2: 2.3e-3 , 3: 95e-2 , 4: 1.275 , 5: 4.18 , 6: 173.21 , 11: 0.51099e-3 , 12: 0, 13: 105.658e-3 , 14: 0 , 15: 1.177682 , 16: 0 , 21: 0 , 22: 0 , 23: 91.1876 , 24: 80.385 , 25: 125.5} SMvisible = [1, 2, 3, 4, 5, 6, 11, 13, 15, 21, 22, 23, 24, 25] SMinvisible = [12, 14, 16]