"""
.. module:: infoObj
:synopsis: Holds the classes and methods used to read and store the
information in the globalInfo.txt or dataInfo.txt files.
.. moduleauthor:: Veronika Magerl <v.magerl@gmx.at>
.. moduleauthor:: Andre Lessa <lessa.a.p@gmail.com>
"""
import os
from smodels.base.physicsUnits import GeV, fb, TeV, pb
from smodels.experiment.exceptions import SModelSExperimentError as SModelSError
from smodels.base.smodelsLogging import logger
from typing import Optional,Union
[docs]class Info(object):
"""
Holds the meta data information contained in a .txt file
(luminosity, sqrts, experimentID,...).
Its attributes are generated according to the lines in the
.txt file which contain "info_tag: value".
"""
[docs] def canonizeRegions ( self, regions : Optional[dict] = None ) \
-> Union[None,dict]:
""" given a list of regions in globalInfo.txt in the
regionMappings field,
return a canonical version of that list: strings in
that list get transformed into dictionaries, if region type is
missing, "SR" is assumed. if the "smodels" counterpart is not
given for a region, we assume that there is None.
If no pyhf name is given, we assume it to be the smodels name.
if no onnx name is given, we assume it to be the pyhf name.
if no label is given, we assume it to be the smodels name.
:param regions: list of regions in regionMappings globalInfo.txt
:returns: canonical list of regions
"""
if regions is None:
return regions
newregions={}
for label,region in regions.items():
regionDict = {}
if type(region)==str:
logger.debug ( f"exploding {region} to a dictionary in {self.path}" )
regionDict["smodels"] = region
elif type(region)==dict:
regionDict.update(region)
else:
raise SModelSError ( f"region {region} is neither a string nor a dictionary in {self.path}" )
for key in [ "type", "smodels", "pyhf", "sl", "onnx" ]:
if key not in regionDict:
logger.debug ( f"region {label} has no {key} defined, setting to default value in {self.path}.")
# Set defaults (if they were not defined)
regionDict.setdefault("type", "SR")
if regionDict["type"]=="SR":
regionDict.setdefault("smodels", label)
else:
regionDict.setdefault("smodels", None)
regionDict.setdefault("pyhf", regionDict["smodels"])
regionDict.setdefault("sl", regionDict["smodels"])
regionDict.setdefault("onnx", regionDict["pyhf"])
newregions[label] = regionDict
return newregions
def __init__(self, path=None):
"""
:param path: path to the .txt file
"""
self.path = path
if path:
logger.debug(f'Creating object based on {self.path}')
# Open the info file and get the information:
if not os.path.isfile(path):
logger.error(f"Info file {path} not found")
raise SModelSError()
from smodels.experiment.expAuxiliaryFuncs import concatenateLines
infoFile = open(self.path)
content = concatenateLines(infoFile.readlines())
infoFile.close()
# Get tags in info file:
tags = [line.split(':', 1)[0].strip() for line in content]
for i, tag in enumerate(tags):
if not tag:
continue
if tag.startswith("#"): # a comment!
continue
line = content[i]
value = line.split(':', 1)[1].strip()
if tag in [ "covariance", "jsonFiles", \
"jsonFiles_FullLikelihood", "datasetOrder" ]:
from smodels import installation
logger.warning ( f"Tag '{tag}' in {self.path} was used in the old format of the database. This version {installation.version()} of SModelS ignores this tag. Use 'regionMappings', 'regionSets', 'statModels' instead. See the documentation -> 'Detailed Guide to SModelS' -> 'Database of Experimental Results' for more info." )
if tag == "regionMappings":
try:
regions = eval(value)
except Exception as e:
try:
from pygments import highlight
from pygments.lexers import JsonLexer
from pygments.formatters import TerminalFormatter
print(highlight(value, JsonLexer(), TerminalFormatter()))
finally:
raise e
regions = self.canonizeRegions( regions )
value = str(regions)
if tags.count(tag) == 1:
self.addInfo(tag, value)
else:
logger.info(f"tag {tag} given multiple times in {self.path}" )
continue
self.checkConsistencyOfRegionSets()
self.checkConsistencyOfStatModels()
self.cacheStatsModels()
[docs] def checkConsistencyOfRegionSets( self ):
""" check that all SRs mentioned in regionSets are included in regionMappings """
if not hasattr ( self, "regionMappings" ):
return
if not hasattr ( self, "regionSets" ):
return
elif not isinstance ( self.regionSets, dict ):
raise SModelSError ( f"regionSets has to be a dict, but is {type(self.regionSets)}" )
for regionList in self.regionSets.values():
if not isinstance ( regionList, list ):
raise SModelSError ( f"regionSets has to be a dict of lists, but its values are of type {type(regionList)}" )
for region in regionList:
if region not in self.regionMappings:
raise SModelSError ( f"region label {region} not mentioned in regionMappings" )
[docs] def checkConsistencyOfStatModels( self ):
""" check that all stat models mentioned in statModels are included in regionSets """
if not hasattr ( self, "statModels" ):
return
if not hasattr ( self, "regionSets" ):
raise SModelSError ( f"In {self.path}: statModels is defined, but regionSets is not. This is inconsistent." )
elif not isinstance ( self.statModels, dict ):
raise SModelSError ( f"In {self.path}: statModels has to be a dict, but is {type(self.statModels)}" )
for regionSet, model_tuples in self.statModels.items():
if regionSet not in self.regionSets:
raise SModelSError ( f"In {self.path}: region set {regionSet} mentioned in statModels is not defined in regionSets" )
if not isinstance ( model_tuples, list ):
raise SModelSError ( f"In {self.path}: statModels has to be a dict of lists, but its values are of type {type(model_tuples)}" )
for model_tuple in model_tuples:
if not isinstance ( model_tuple, tuple ):
raise SModelSError ( f"In {self.path}: statModels has to be a dict of lists of tuples, but its values are of type {type(model_tuple)}" )
if len(model_tuple) != 2:
raise SModelSError ( f"In {self.path}: statModels has to be a dict of lists of tuples of length 2, but its values are of length {len(model_tuple)}" )
model_type,model = model_tuple
if model_type not in [ "sl", "full_pyhf", "pyhf", "onnx" ]:
raise SModelSError ( f"In {self.path}: model_type {model_type} is unknown. should be of: onnx, pyhf, full_pyhf, sl" )
if not isinstance ( model, str ):
raise SModelSError ( f"In {self.path}: model has to be a string, but is {type(model)}" )
if not os.path.isfile ( os.path.join(os.path.dirname(self.path), model) ):
raise SModelSError ( f"In {self.path}: model file {model} does not exist in the same directory as {self.path}" )
def __eq__(self, other):
if self.__dict__ != other.__dict__:
return False
return True
[docs] def cacheStatsModels(self):
""" if we have the "statModels" attribute defined,
we cache the corresponding onnx files. Needed when pickling """
if not hasattr(self, "statModels"):
## we dont have any stats models, nothing to cache
return
if hasattr(self, "cachedModels"):
# seems like we already have cached them
return
import json
self.cachedModels = {}
dirp = os.path.dirname(self.path)
for model_tuples in self.statModels.values():
for model_tuple in model_tuples:
model_type, model = model_tuple
fullPath = os.path.join(dirp, model )
with open ( fullPath, "rb" ) as f:
txt = None
if model_type == "sl":
with open(fullPath,"rt") as f:
txt = eval ( f.read() )
elif model_type in [ "full_pyhf", "pyhf" ]:
with open(fullPath,"rt") as f:
txt = json.load(f)
elif model_type == "onnx":
txt = f.read()
else:
logger.error ( f"model_type {model_type} is unknown. should be of: onnx, pyhf, full_pyhf, sl" )
self.cachedModels[model] = txt
f.close()
[docs] def dirName(self, up=0):
""" directory name of path. If up>0,
we step up 'up' directory levels.
"""
s_up = "/".join([".."] * up)
p = os.path.dirname(self.path)
return os.path.abspath(os.path.join(p, s_up))
def __ne__(self, other):
return not self.__eq__(other)
[docs] def addInfo(self, tag, value):
"""
Adds the info field labeled by tag with value value to the object.
:param tag: information label (string)
:param value: value for the field in string format
"""
if tag == "lastUpdate": # dont eval that!
setattr(self, "lastUpdate", str(value))
return
try:
setattr( self, tag, eval(value,
{'fb': fb, 'pb': pb, 'GeV': GeV, 'TeV': TeV}))
except (SyntaxError,NameError,TypeError):
setattr(self, tag, value)
[docs] def getInfo(self, infoLabel):
"""
Returns the value of info field.
:param infoLabel: label of the info field (string). It must be an attribute
of the GlobalInfo object
"""
if hasattr(self, infoLabel):
return getattr(self, infoLabel)
else:
return False