diff --git a/Example.py b/Example.py index b36b84166..4c4390d35 100755 --- a/Example.py +++ b/Example.py @@ -24,6 +24,10 @@ # Set the path to the database import os +from smodels.statistics.pyhfInterface import setBackend +# set pyhf backend to one of: numpy (default), pytorch, tensorflow, jax. +# WARNING: if backend specified is not found, we fall back to numpy! +setBackend("pytorch") def main(inputFile='./inputFiles/slha/lightEWinos.slha', sigmacut=0.05*fb, database = 'official'): @@ -40,9 +44,9 @@ def main(inputFile='./inputFiles/slha/lightEWinos.slha', sigmacut=0.05*fb, model = Model(BSMparticles=BSMList, SMparticles=SMList) # Path to input file (either a SLHA or LHE file) -# lhefile = 'inputFiles/lhe/gluino_squarks.lhe' + # lhefile = 'inputFiles/lhe/gluino_squarks.lhe' slhafile = os.path.abspath(inputFile) -# model.updateParticles(inputFile=lhefile) + # model.updateParticles(inputFile=lhefile) model.updateParticles(inputFile=slhafile, ignorePromptQNumbers = ['eCharge','colordim','spin']) diff --git a/ReleaseNotes b/ReleaseNotes index d0e9b88e2..a355ae1d7 100644 --- a/ReleaseNotes +++ b/ReleaseNotes @@ -1,8 +1,9 @@ -Release v3.0.1, Thu 24 Oct 2024 +Release v3.0.1, Thu 31 Oct 2024 ======================================================= * Bug fix for printing signal region combination results * New cluster algorithm (simpler and more stable) + * Made the pyhf backend configurable. WARNING: if backend specified is not found, we now fall back to numpy! Release v3.0.0, Tue 20 Aug 2024 ======================================================= diff --git a/docs/manual/source/Installation.rst b/docs/manual/source/Installation.rst index 9fff36c1b..f9c95f3ed 100644 --- a/docs/manual/source/Installation.rst +++ b/docs/manual/source/Installation.rst @@ -19,6 +19,7 @@ SModelS is a Python library that requires Python version 3.6 or later. It depend .. include:: dependencies.rst +For the pyhf backend, either one of pytorch, tensorflow, and jax can be used alternatively to the default numpy. In this case, the corresponding python module needs to be installed, and the :ref:`pyhfbackend ` option needs to be configured accordingly (or a direct call of `pyhfInterface.setBackend `_ in case of python code) For performance reasons, we moreover recommend pytorch>=1.8.0 as backend for pyhf. This is, however, optional: if pytorch is not available, SModelS will use the default backend. In addition, the :ref:`cross section computers ` provided by :ref:`smodelsTools.py ` diff --git a/docs/manual/source/ReleaseUpdate.rst b/docs/manual/source/ReleaseUpdate.rst index 569a60135..46ea8d459 100644 --- a/docs/manual/source/ReleaseUpdate.rst +++ b/docs/manual/source/ReleaseUpdate.rst @@ -38,6 +38,7 @@ New in Version 3.0.1: * Bug fix for printing signal region combination results * Replaced algorithm for :ref:`clustering SMS ` for UL results by a modified minimum spanning tree algorithm + * Made the :ref:`pyhf backend ` configurable, see also `pyhfInterface.setBackend `_. WARNING: if backend specified is not found, we now fall back to numpy! New in Version 3.0.0: ^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/manual/source/RunningSModelS.rst b/docs/manual/source/RunningSModelS.rst index 9ed7f792d..b6bb9917b 100644 --- a/docs/manual/source/RunningSModelS.rst +++ b/docs/manual/source/RunningSModelS.rst @@ -138,6 +138,10 @@ Below we give more detailed information about each entry in the parameters file. * **combineSRs** (True/False): set to True to combine signal regions in |EMrs| when covariance matrix or pyhf JSON likelihood is available. Set to False to use only the most sensitive signal region (faster!). Available v1.1.3 onwards for covariance matrices and v1.2.4 onwards for full likelihoods (using pyhf). +.. _parameterFilePyhfbackend: + + * **pyhfbackend**: set to name of pyhf backend. Possible values are: numpy (default), pytorch, tensorflow, jax + .. _parameterFileReportAllSRs: * **reportAllSRs** (True/False): set to True to report all signal regions, instead of the best signal region only. From v3.0.0 onwards it will also include the combined SRs if combineSRs=True. Beware, the output can be long. @@ -395,11 +399,16 @@ Below we go step-by-step through this example code: .. literalinclude:: /examples/Example.py :lines: 16-25 +* *Define the pyhfbackend to use*. Specify which `pyhfbackend `_ to use. One of: numpy, pytorch, tensorflow, jax. + +.. literalinclude:: /examples/Example.py + :lines: 27-30 + * *Set the path to the database URL*. Specify which :ref:`database ` to use. It can be the path to the smodels-database folder, the path to a :ref:`pickle file ` or (starting with v1.1.3) a URL path. .. literalinclude:: /examples/Example.py - :lines: 39-39 + :lines: 38-39 * *Load the BSM particles*. By default SModelS assumes the MSSM particle content. For using SModelS @@ -407,7 +416,7 @@ Below we go step-by-step through this example code: to the path of the model file (see **particles:model** in :ref:`Parameter File `). .. literalinclude:: /examples/Example.py - :lines: 42-43 + :lines: 41-43 * *Load the model and set the path to the input file*. Load BSM and SM particle content; specify the location of the input file (must be an SLHA or LHE file, see :ref:`Basic Input `) and update particles in the model. diff --git a/docs/manual/source/examples/Example.py b/docs/manual/source/examples/Example.py index b36b84166..4c4390d35 100755 --- a/docs/manual/source/examples/Example.py +++ b/docs/manual/source/examples/Example.py @@ -24,6 +24,10 @@ # Set the path to the database import os +from smodels.statistics.pyhfInterface import setBackend +# set pyhf backend to one of: numpy (default), pytorch, tensorflow, jax. +# WARNING: if backend specified is not found, we fall back to numpy! +setBackend("pytorch") def main(inputFile='./inputFiles/slha/lightEWinos.slha', sigmacut=0.05*fb, database = 'official'): @@ -40,9 +44,9 @@ def main(inputFile='./inputFiles/slha/lightEWinos.slha', sigmacut=0.05*fb, model = Model(BSMparticles=BSMList, SMparticles=SMList) # Path to input file (either a SLHA or LHE file) -# lhefile = 'inputFiles/lhe/gluino_squarks.lhe' + # lhefile = 'inputFiles/lhe/gluino_squarks.lhe' slhafile = os.path.abspath(inputFile) -# model.updateParticles(inputFile=lhefile) + # model.updateParticles(inputFile=lhefile) model.updateParticles(inputFile=slhafile, ignorePromptQNumbers = ['eCharge','colordim','spin']) diff --git a/parameters.ini b/parameters.ini index 7f1e94b2d..b08473b1d 100644 --- a/parameters.ini +++ b/parameters.ini @@ -7,6 +7,7 @@ testCoverage = True ;Set True if topologies not covered by experiments (missing computeStatistics = True ;Set True to compute the likelihoods L_BSM, L_SM and L_max for EM-type results. combineSRs = True ;Set True to combine signal regions when covariance matrix or pyhf JSON likelihood is available. Caution, increases runtime! False uses only best SR (faster). #combineAnas = ATLAS-SUSY-2019-08,ATLAS-SUSY-2019-09 ; list of statistically independent analyses to combine. Works for EM-type results only. Use with care! +pyhfbackend = pytorch ;Define pyhf backend used, one of: numpy (default), pytorch, tensorflow, jax. WARNING: if backend specified is not found, we fall back to numpy! reportAllSRs = False ;Set True to report all signal regions, instead of best signal region only. experimentalFeatures = False ;Set True to enable experimental features that are not yet considered part of SModelS proper. Use only if you know what you are doing!! diff --git a/smodels/matching/modelTester.py b/smodels/matching/modelTester.py index 272eb34c7..c3c33c4bd 100644 --- a/smodels/matching/modelTester.py +++ b/smodels/matching/modelTester.py @@ -535,6 +535,12 @@ def getParameters(parameterFile): runtime.modelFile = parser.get("particles", "model") except: pass + try: + pyhfbackend = parser.get("options","pyhfbackend") + from smodels.statistics import pyhfInterface + r = pyhfInterface.setBackend ( pyhfbackend ) + except: + pass return parser diff --git a/smodels/statistics/pyhfInterface.py b/smodels/statistics/pyhfInterface.py index 11c7c8c4e..4fbda233c 100755 --- a/smodels/statistics/pyhfInterface.py +++ b/smodels/statistics/pyhfInterface.py @@ -9,6 +9,7 @@ .. moduleauthor:: Wolfgang Waltenberger """ + import jsonpatch import warnings import jsonschema @@ -18,25 +19,23 @@ from smodels.base.smodelsLogging import logger import logging logging.getLogger("pyhf").setLevel(logging.CRITICAL) -warnings.filterwarnings("ignore") +# warnings.filterwarnings("ignore") +warnings.filterwarnings("ignore", r"invalid value encountered in log") from typing import Dict, List jsonver = "" try: import importlib.metadata - jsonver = importlib.metadata.version("jsonschema") + jsonver = int(importlib.metadata.version("jsonschema")[0]) except Exception as e: try: from jsonschema import __version__ as jsonver except Exception as e: pass -if jsonver[0] == "2": +if jsonver < 3: # if jsonschema.__version__[0] == "2": ## deprecated - print( - "[SModelS:pyhfInterface] jsonschema is version %s, we need > 3.x.x" - % (jsonschema.__version__) - ) + print( f"[SModelS:pyhfInterface] jsonschema is version {jsonschema.__version__}, we need > 3.x.x" ) sys.exit() import time, sys, os @@ -47,36 +46,40 @@ print("[SModelS:pyhfInterface] pyhf import failed. Is the module installed?") sys.exit(-1) -ver = pyhf.__version__.split(".") -if ver[1] == "4" or (ver[1] == "5" and ver[2] in ["0", "1"]): - print("[SModelS:pyhfInterface] WARNING you are using pyhf v%s." % pyhf.__version__) - print("[SModelS:pyhfInterface] We recommend pyhf >= 0.5.2. Please try to update pyhf ASAP!") +ver = pyhf.__version__ pyhfinfo = { "backend": "numpy", "hasgreeted": False, - "backendver": "?", + "backendver": np.version.full_version, "ver": ver, - "required": "0.6.1".split("."), +# "required": "0.6.1", # the required pyhf version } -try: - pyhf.set_backend(b"pytorch") - import torch - - pyhfinfo["backend"] = "pytorch" - pyhfinfo["backendver"] = torch.__version__ - -except pyhf.exceptions.ImportBackendError as e: - print( - "[SModelS:pyhfInterface] WARNING could not set pytorch as the pyhf backend, falling back to the default." - ) - print("[SModelS:pyhfInterface] We however recommend that pytorch be installed.") - import numpy +def setBackend ( backend : str ) -> bool: + """ + try to setup backend to - pyhfinfo["backendver"] = numpy.version.full_version - - warnings.filterwarnings("ignore", r"invalid value encountered in log") + :param backend: one of: numpy (default), pytorch, jax, tensorflow + :returns: True, if worked, False if failed + """ + try: + pyhf.set_backend( backend ) + pyhfinfo["backend"] = backend + pyhfinfo["backendver"] = "?" + module_name = backend + if backend == "pytorch": + module_name = "torch" + from importlib.metadata import version + pyhfinfo["backendver"] = version(module_name) + return True + except (pyhf.exceptions.ImportBackendError,pyhf.exceptions.InvalidBackend) as e: + print( f"[SModelS:pyhfInterface] WARNING could not set {backend} as the pyhf backend: {e}" ) + print( f"[SModelS:pyhfInterface] falling back to {pyhfinfo['backend']}." ) + # print("[SModelS:pyhfInterface] We however recommend that pytorch be installed.") + return False + +# setBackend ( "pytorch" ) countWarning = {"llhdszero": 0} # Sets the maximum number of attempts for determining the brent bracketing interval for mu: @@ -389,7 +392,7 @@ def __init__(self, data, cl=0.95, lumi=None ): self.alreadyBeenThere = ( False # boolean to detect wether self.signals has returned to an older value ) - self.checkPyhfVersion() + # self.checkPyhfVersion() self.welcome() def welcome(self): @@ -400,7 +403,7 @@ def welcome(self): if pyhfinfo["hasgreeted"]: return logger.info( - f"Pyhf interface, we are using v{'.'.join(pyhfinfo['ver'])}, with {pyhfinfo['backend']} v{pyhfinfo['backendver']} as backend." + f"Pyhf interface, we are using v{str(pyhfinfo['ver'])}, with {pyhfinfo['backend']} v{str(pyhfinfo['backendver'])} as backend." ) pyhfinfo["hasgreeted"] = True @@ -411,7 +414,7 @@ def checkPyhfVersion(self): if pyhfinfo["ver"] < pyhfinfo["required"]: logger.warning( - f"pyhf version is {'.'.join(pyhfinfo['ver'])}. SModelS currently requires pyhf>={'.'.join(pyhfinfo['required'])}. You have been warned." + f"pyhf version is {str(pyhfinfo['ver'])}. SModelS currently requires pyhf>={str(pyhfinfo['required'])}. You have been warned." ) def rescale(self, factor):