"""
.. module:: txtPrinter
:synopsis: Class for defining a log (stdout) printer
.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
"""
import os
from smodels.tools.printers.basicPrinter import BasicPrinter
from smodels.decomposition.topologyDict import TopologyDict
from smodels.matching.theoryPrediction import TheoryPredictionList,TheoryPrediction,TheoryPredictionsCombiner
from smodels.experiment.databaseObj import Database
from smodels.tools.ioObjects import OutputStatus
from smodels.tools.coverage import Uncovered
from smodels.base.physicsUnits import GeV, fb, TeV
from smodels.base.smodelsLogging import logger
import numpy as np
from itertools import groupby
import time
[docs]class TxTPrinter(BasicPrinter):
"""
Printer class to handle the printing of one single text output
"""
def __init__(self, output='stdout', filename=None, outputFormat='current'):
BasicPrinter.__init__(self, output, filename, outputFormat)
self.name = "log"
self.printtimespent = False
self.printingOrder = [OutputStatus, Database, TopologyDict,
TheoryPredictionList, TheoryPredictionsCombiner,
TheoryPrediction, Uncovered]
self.toPrint = [None] * len(self.printingOrder)
[docs] def setOutPutFile(self, filename, overwrite=True, silent=False):
"""
Set the basename for the text printer. The output filename will be
filename.log.
:param filename: Base filename
:param overwrite: If True and the file already exists, it will be removed.
:param silent: dont comment removing old files
"""
self.filename = filename + '.' + self.name
if overwrite and os.path.isfile(self.filename):
if not silent:
logger.warning("Removing old output file " + self.filename)
os.remove(self.filename)
def _formatDoc(self, obj):
return False
def _formatOutputStatus(self, obj):
"""
Format data for a OutputStatus object.
:param obj: A OutputStatus object to be printed.
"""
output = ""
output += "Input status: " + str(obj.filestatus) + "\n"
# hidden feature, printtimespent, turn on in ini file, e.g.
# [summary-printer] printtimespent = True
if self.printtimespent:
output += "Time spent: %.2fs\n" % (time.time() - self.time)
output += "Decomposition output status: " + str(obj.status) + " "
st = "unknown status"
if obj.status in obj.statusStrings:
st = obj.statusStrings[obj.status]
output += st + "\n"
if obj.filestatus < 0:
output += str(obj.warnings) + "\n"
output += "# Input File: " + obj.inputfile + "\n"
labels = list(obj.parameters.keys())
labels.sort()
# for label, par in obj.parameters.items():
for label in labels:
par = obj.parameters[label]
output += "# " + label + " = " + str(par) + '\n'
if obj.smodelsVersion:
output += f"# SModelS version: {obj.smodelsVersion}\n"
if obj.databaseVersion:
output += f"# Database version: {obj.databaseVersion}\n"
output += "=" * 80 + "\n"
return output
def _formatTopologyDict(self, obj):
"""
Format data for a TopologyDict object.
:param obj: A TopologyDict object to be printed.
"""
if not hasattr(self, 'printdecomp') or not self.printdecomp:
return None
slabel = "Topologies Table"
output = ""
output += " " + "="*56 + " \n"
output += "||" + " "*56 + "||\n"
xspace = int((56-len(slabel))/2.)
output += "||" + " "*xspace+slabel+" "*(56-xspace-len(slabel))+"||\n"
output += "||" + " "*56 + "||\n"
output += " " + "="*56 + " \n"
if self.outputFormat == "version2":
baseLabel = 'Element'
else:
baseLabel = 'SMS'
# Get topology names:
topoNames = {canonName : canonName for canonName in obj}
topoNames_v2 = {}
if self.outputFormat == "version2":
for canonName in obj:
smsList = obj[canonName]
try:
sms = smsList[0]
evenParticles = sms.treeToBrackets()[0]
vertnumb = str([len(v) for v in evenParticles[0]])
vertnumb += str([len(v) for v in evenParticles[1]])
vertnumb = vertnumb.replace(' ','')
topoName = vertnumb
topoNames_v2[canonName] = topoName
except:
logger.info("Could not format SMS using version2, switching to current format.")
self.outputFormat = 'current'
if self.outputFormat == 'version2':
topoNames = topoNames_v2
for canonName in obj:
smsList = obj[canonName]
topoName = topoNames[canonName]
output += "===================================================== \n"
output += "Topology: %s \n" %topoName
totxsec = smsList[0].weightList
for sms in smsList[1:]:
totxsec += sms.weightList
output += "Total Global topology weight :\n" + totxsec.niceStr() + '\n'
output += "Total Number of %s: " %baseLabel + \
str(len(smsList)) + '\n'
if not hasattr(self, 'addsmsinfo') or not self.addsmsinfo:
continue
for sms in smsList:
output += "\t\t " + 73 * "." + "\n"
output += self._formatSMS(sms) + "\n"
return output
def _formatSMS(self, obj):
"""
Format data for an SMS object.
:param obj: SMS object to be printed.
"""
output = ""
if self.outputFormat == 'version2':
output += "\t\t Element: \n"
branchList, finalState, _ = obj.treeToBrackets()
masses = []
pidlist = []
for bIndex in obj.daughterIndices(obj.rootIndex):
branch = obj.indexToNode(bIndex)
if branch.isSM:
continue
bMasses = [branch.mass]
pids = [branch.pdg]
for n in obj.dfsIndexIterator(bIndex):
node = obj.indexToNode(n)
if node.isSM:
continue
bMasses.append(node.mass)
pids.append(node.pdg)
masses.append(bMasses)
pidlist.append(pids)
# Sort pidlist for consistent output
pidlist = sorted(pidlist, key = lambda x: str(x))
output += "\t\t Element ID: " + str(obj.smsID)
output += "\n"
output += "\t\t Particles in element: " + str(branchList).replace("'","").replace(" ","")
output += "\n"
output += "\t\t Final states in element: " + str(finalState).replace("'","").replace(" ","")
output += "\n"
output += "\t\t The element masses are \n"
for i, mass in enumerate(masses):
output += "\t\t Branch %i: " % i + str(mass) + "\n"
output += "\n"
output += "\t\t The element PIDs are \n"
pidstr = sorted([str(p) for p in pidlist])
output += "\t\t PIDs: " + str(pidlist) + "\n"
output += "\t\t The element weights are: \n \t\t " + \
obj.weightList.niceStr().replace("\n", "\n \t\t ")
else:
output += "\t\t SMS ID: %i\n" %obj.smsID
output += "\t\t SMS: %s\n" %str(obj)
output += "\t\t Masses: %s\n" %str([(node,mass) for node,mass in zip(obj.nodes,obj.mass)
if not node.isSM and not node is obj.root])
output += "\t\t Cross-Sections:\n \t\t "+obj.weightList.niceStr().replace("\n", "\n \t\t ")
return output
def _formatDatabase(self, obj):
"""
Format data for a Database object.
:param obj: A Database object to be printed.
"""
if not hasattr(self, "printdatabase") or not self.printdatabase:
return None
expResults = obj.expResultList
slabel = "Selected Experimental Results"
output = ""
output += " " + "="*56 + " \n"
output += "||" + " "*56 + "||\n"
xspace = int((56-len(slabel))/2.)
output += "||" + " "*xspace+slabel+" "*(56-xspace-len(slabel))+"||\n"
output += "||" + " "*56 + "||\n"
output += " " + "="*56 + " \n"
for expRes in expResults:
output += self._formatExpResult(expRes)
return output+"\n"
def _formatExpResult(self, obj):
"""
Format data for a ExpResult object.
:param obj: A ExpResult object to be printed.
"""
if self.outputFormat == "version2":
baseLabel = 'Elements'
else:
baseLabel = 'SMS'
txnames = []
for dataset in obj.datasets:
for txname in dataset.txnameList:
tx = txname.txName
if tx not in txnames:
txnames.append(tx)
txnames = sorted(txnames)
output = ""
output += "========================================================\n"
output += "Experimental Result ID: " + obj.globalInfo.id + '\n'
output += "Tx Labels: " + str(txnames) + '\n'
output += "Sqrts: %2.2E\n" % obj.globalInfo.sqrts.asNumber(TeV)
if hasattr(self, "addanainfo") and self.addanainfo:
output += "\t -----------------------------\n"
output += "\t %s tested by analysis:\n" %baseLabel
listOfSMS = []
for dataset in obj.datasets:
for txname in dataset.txnameList:
for sms in txname.smsMap:
if self.outputFormat == 'version2':
smsStr = str(sms.treeToBrackets()[0])
smsStr = smsStr.replace("'","").replace(" ","")
else:
smsStr = str(sms)
if smsStr not in listOfSMS:
listOfSMS.append(smsStr)
for sms in listOfSMS:
output += "\t %s \n" %sms
return output
def _formatNumber(self, number, n=4):
""" format a number <number> to have n digits,
but allow also for None, strings, etc """
if type(number) not in [float, np.float64]:
return str(number)
fmt = ".%dg" % n
return ("%"+fmt) % number
def _formatTheoryPredictionList(self, obj):
"""
Format data for a TheoryPredictionList object.
:param obj: A TheoryPredictionList object to be printed.
"""
if self.outputFormat == "version2":
baseLabel = 'Elements'
else:
baseLabel = 'SMS'
slabel = "Theory Predictions and"
output = ""
output += " " + "="*56 + " \n"
output += "||" + " "*56 + "||\n"
xspace = int((56-len(slabel))/2.)
output += "||" + " "*xspace+slabel+" "*(56-xspace-len(slabel))+"||\n"
slabel = "Experimental Constraints"
xspace = int((56-len(slabel))/2.)
output += "||" + " "*xspace+slabel+" "*(56-xspace-len(slabel))+"||\n"
output += "||" + " "*56 + "||\n"
output += " " + "="*56 + " \n"
for theoryPrediction in obj._theoryPredictions:
expRes = theoryPrediction.expResult
dataId = theoryPrediction.dataId()
txnames = [str(txname) for txname in theoryPrediction.txnames]
txnames = sorted(list(set(txnames)))
output += "\n"
output += "---------------Analysis Label = " + expRes.globalInfo.id + "\n"
output += "-------------------Dataset Label = " + \
str(dataId).replace("None", "(UL)") + "\n"
output += "-------------------Txname Labels = " + \
str(txnames) + "\n"
output += "Analysis sqrts: " + str(expRes.globalInfo.sqrts) + \
"\n"
output += "Theory prediction: " + \
str(theoryPrediction.xsection) + "\n"
output += "Theory conditions:"
output += " " + str(theoryPrediction.conditions) + "\n"
# Get upper limit for the respective prediction:
upperLimit = theoryPrediction.getUpperLimit(expected=False)
upperLimitExp = theoryPrediction.getUpperLimit(
expected=self.getTypeOfExpected())
output += "Observed experimental limit: " + str(upperLimit) + "\n"
if upperLimitExp is not None:
output += "Expected experimental limit: " + \
str(upperLimitExp) + "\n"
srv = self._formatNumber(
theoryPrediction.getRValue(expected=False), 4)
output += "Observed r-value: %s\n" % srv
if upperLimitExp is not None:
serv = self._formatNumber(theoryPrediction.getRValue(
expected=self.getTypeOfExpected()), 4)
output += "Expected r-value: %s\n" % serv
nll = theoryPrediction.likelihood( return_nll = True )
if nll is not None:
chi2, chi2sm = None, None
nllsm = theoryPrediction.lsm( return_nll = True )
nllmin = theoryPrediction.lmax( return_nll = True )
try:
# chi2sm = -2*np.log(llhd/theoryPrediction.lsm())
chi2sm = 2*(nll - nllsm )
except TypeError:
pass
try:
# chi2 = -2*np.log(llhd/theoryPrediction.lmax())
chi2 = 2*(nll - nllmin )
except TypeError:
pass
output += "nll: " + self._formatNumber(nll, 4) + "\n"
output += "nll_min: " + self._formatNumber(nllmin, 4) + \
" -2log(L/L_max): " + self._formatNumber(chi2, 4) + "\n"
output += "nll_SM: " + self._formatNumber(nllsm, 4) + \
" -2log(L/L_SM): " + \
self._formatNumber(chi2sm, 4) + "\n"
if hasattr(self, "printextendedresults") and self.printextendedresults:
if theoryPrediction.mass:
for ibr, br in enumerate(theoryPrediction.mass):
output += "Masses in branch %i: " % ibr + \
str(br) + "\n"
IDList = list(
set([sms.smsID for sms in theoryPrediction.smsList]))
if IDList:
output += "Contributing %s: " %baseLabel + str(IDList) + "\n"
if self.outputFormat == 'version2':
smsPIDs = []
for sms in theoryPrediction.smsList:
pidList = []
for bIndex in sms.daughterIndices(sms.rootIndex):
pids = [sms.indexToNode(bIndex).pdg]
for nodeIndex in sms.dfsIndexIterator(bIndex):
node = sms.indexToNode(nodeIndex)
if node.isSM:
continue
pids.append(node.pdg)
pidList.append(pids)
if pidList not in smsPIDs:
smsPIDs.append(pidList)
for pidList in smsPIDs:
# Sort pidlist for consistent output
pidList = sorted(pidList, key = lambda x: str(x))
output += "PIDs:" + str(pidList) + "\n"
return output
def _formatUncovered(self, obj):
"""
Format all uncovered data.
:param obj: Uncovered object to be printed.
"""
if self.outputFormat == "version2":
baseLabel = 'Element'
else:
baseLabel = 'SMS'
# Number of missing topologies to be printed (ordered by cross sections)
nprint = 10
# First sort groups by label
groups = sorted(obj.groups[:], key=lambda g: g.label)
# Get summary of groups:
output = "\n"
for group in groups:
output += "Total cross-section for %s (fb): %10.3E\n" % (
group.description, group.getTotalXSec())
output += "\n#Full information on unconstrained cross sections\n"
output += "================================================================================\n"
# Get detailed information:
for group in groups:
description = group.description
sqrts = group.sqrts.asNumber(TeV)
if not group.finalStateSMS:
output += "No %s found\n" % description
output += "================================================================================\n"
continue
output += "%s with the highest cross sections (up to %i):\n" % (
description, nprint)
output += "Sqrts (TeV) Weight (fb) %s description\n" %baseLabel
for fsSMS in group.finalStateSMS[:nprint]:
if self.outputFormat == 'version2':
smsStr = fsSMS.oldStr()
else:
smsStr = str(fsSMS)
output += "%5s %10.3E # %53s \n" % (
str(sqrts), fsSMS.missingX, smsStr)
if hasattr(self, "addcoverageid") and self.addcoverageid:
contributing = []
for sms in fsSMS._contributingSMS:
contributing.append(sms.smsID)
output += "Contributing %s %s\n" %(baseLabel,str(contributing))
output += "================================================================================\n"
return output
def _formatTheoryPrediction(self,obj):
return self._formatTheoryPredictionsCombiner(obj)
def _formatTheoryPredictionsCombiner(self, obj):
"""
Format data of the TheoryPredictionsCombiner object.
:param obj: A TheoryPredictionsCombiner object to be printed.
"""
output = "===================================================== \n"
# Get list of analyses used in combination:
expIDs = obj.analysisId()
# Get r-value:
r = self._formatNumber(obj.getRValue(),4)
r_expected = self._formatNumber(obj.getRValue(expected=self.getTypeOfExpected()),4)
# Get likelihoods:
nllsm = obj.lsm( return_nll = True )
nll = obj.likelihood( return_nll = True )
nllmin = obj.lmax( return_nll = True )
output += f"Combined Analyses: {expIDs}\n"
output += f"Likelihoods: nll, nll_min, nll_SM = {nll:.3f}, {nllmin:.3f}, {nllsm:.3f}\n"
output += f"combined r-value: {r:s}\n"
output += f"combined r-value (expected): {r_expected:s}"
output += "\n===================================================== \n"
output += "\n"
return output