From 045297d04b447a58efc391b1b2ef1b2f92a483a3 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Fri, 26 Jan 2024 12:59:43 -0500 Subject: [PATCH 01/31] optimise gradients * using vectorised `value_and_grad` instead of grad --- src/spey/backends/default_pdf/__init__.py | 8 ++------ src/spey/backends/default_pdf/simple_pdf.py | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/spey/backends/default_pdf/__init__.py b/src/spey/backends/default_pdf/__init__.py index 8dfdc92..a0ef7f4 100644 --- a/src/spey/backends/default_pdf/__init__.py +++ b/src/spey/backends/default_pdf/__init__.py @@ -2,7 +2,7 @@ from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union -from autograd import grad, hessian, jacobian +from autograd import value_and_grad, hessian, jacobian from autograd import numpy as np from scipy.optimize import NonlinearConstraint @@ -251,11 +251,7 @@ def negative_loglikelihood(pars: np.ndarray) -> np.ndarray: ) - self.constraint_model.log_prob(pars) if do_grad: - grad_negative_loglikelihood = grad(negative_loglikelihood, argnum=0) - return lambda pars: ( - negative_loglikelihood(pars), - grad_negative_loglikelihood(pars), - ) + return value_and_grad(negative_loglikelihood, argnum=0) return negative_loglikelihood diff --git a/src/spey/backends/default_pdf/simple_pdf.py b/src/spey/backends/default_pdf/simple_pdf.py index e0cacb5..c1be7a1 100644 --- a/src/spey/backends/default_pdf/simple_pdf.py +++ b/src/spey/backends/default_pdf/simple_pdf.py @@ -2,7 +2,7 @@ from typing import Callable, List, Optional, Text, Tuple, Union -from autograd import grad, hessian +from autograd import value_and_grad, hessian from autograd import numpy as np from spey._version import __version__ @@ -162,11 +162,7 @@ def negative_loglikelihood(pars: np.ndarray) -> np.ndarray: return -self.main_model.log_prob(pars, data) if do_grad: - grad_negative_loglikelihood = grad(negative_loglikelihood, argnum=0) - return lambda pars: ( - negative_loglikelihood(pars), - grad_negative_loglikelihood(pars), - ) + return value_and_grad(negative_loglikelihood, argnum=0) return negative_loglikelihood From cce1ad99af6d5cd8fb4b07eb11a02f371e898d2b Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 09:04:19 -0500 Subject: [PATCH 02/31] add ability to get bibtex entry for backend --- src/spey/__init__.py | 111 ++++++++++++++++++++++++++++++++++++++++++- src/spey/utils.py | 2 + 2 files changed, 112 insertions(+), 1 deletion(-) diff --git a/src/spey/__init__.py b/src/spey/__init__.py index a00161c..3dbfe18 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -1,3 +1,4 @@ +import os from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union import numpy as np @@ -25,6 +26,9 @@ "BackendBase", "ConverterBase", "about", + "check_updates", + "get_backend_bibtex", + "cite", ] @@ -59,7 +63,7 @@ def reset_backend_entries() -> None: _backend_entries = _get_backend_entrypoints() -def AvailableBackends() -> List[Text]: +def AvailableBackends() -> List[Text]: # pylint: disable=C0103 """ Returns a list of available backends. The default backends are automatically installed with ``spey`` package. To enable other backends, please see the relevant section @@ -216,3 +220,108 @@ def get_backend_metadata(name: Text) -> Dict[Text, Any]: + ", ".join(AvailableBackends()) + "." ) + + +def get_backend_bibtex(name: Text) -> List[Text]: + """ + Retreive BibTex entry for backend plug-in if available. + + The bibtext entries are retreived both from Inspire HEP and doi.org. + If the arXiv number matches the DOI the output will include two versions + of the same reference. If backend does not include an arXiv or DOI number + it will return an empty list. + + Args: + name (``Text``): backend identifier. This backend refers to different packages + that prescribes likelihood function. + + Returns: + ``List[Text]``: + BibTex entries for the backend. + """ + # pylint: disable=import-outside-toplevel + txt = [] + if name is None: + meta = {"arXiv": ["2307.06996"], "doi": ["10.5281/zenodo.10569099"]} + else: + meta = get_backend_metadata(name) + + import warnings + + try: + import textwrap + + import requests + + # check arXiv + for arxiv_id in meta.get("arXiv", []): + response = requests.get( + f"https://inspirehep.net/api/arxiv/{arxiv_id}", + headers={"accept": "application/x-bibtex"}, + timeout=5, + ) + response.encoding = "utf-8" + txt.append(textwrap.indent(response.text, " " * 4)) + for doi in meta.get("doi", []): + page = f"https://doi.org/{doi}" if "https://doi.org/" not in doi else doi + response = requests.get( + page, headers={"accept": "application/x-bibtex"}, timeout=5 + ) + response.encoding = "utf-8" + current_bibtex = response.text + if current_bibtex not in txt: + txt.append(current_bibtex) + except Exception: # pylint: disable=W0718 + warnings.warn("Unable to retreive bibtex information.", category=UserWarning) + + return txt + + +def cite() -> List[Text]: + """Retreive BibTex information for Spey""" + return get_backend_bibtex(None) + + +def check_updates() -> None: + """ + Check Spey Updates. + + Spey always checks updates when initialised. To disable this set the following + + Option if using terminal: + + .. code-block:: bash + + export SPEY_CHECKUPDATE=OFF + + Option if using python interface: + + .. code-block:: python + + import os + os.environ["SPEY_CHECKUPDATE"]="OFF" + + """ + # pylint: disable=import-outside-toplevel + try: + import warnings + + import requests + + response = requests.get("https://pypi.org/pypi/spey/json", timeout=1) + response.encoding = "utf-8" + pypi_info = response.json() + pypi_version = pypi_info.get("info", {}).get("version", False) + if pypi_version: + if Version(version()) < Version(pypi_version): + warnings.warn( + f"An update is available. Current version of spey is {version()}, " + f"available version is {pypi_version}." + ) + except Exception: # pylint: disable=W0718 + # Can not retreive updates + pass + + +if os.environ.get("SPEY_CHECKUPDATE", "ON").upper() != "OFF": + check_updates() diff --git a/src/spey/utils.py b/src/spey/utils.py index af10e88..7c88272 100644 --- a/src/spey/utils.py +++ b/src/spey/utils.py @@ -2,6 +2,8 @@ __all__ = ["ExpectationType"] +# pylint: disable=C0103 + class ExpectationType(Enum): """ From 07ca2dd891ed8c2911afa651a310f994b47604fc Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 09:04:28 -0500 Subject: [PATCH 03/31] update api --- docs/api.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 376f4f5..b67fb58 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -16,10 +16,13 @@ Top-Level version about + check_updates ExpectationType AvailableBackends get_backend get_backend_metadata + get_backend_bibtex + cite reset_backend_entries statistical_model_wrapper helper_functions.correlation_to_covariance From 6a7d38c6df68fc33dc5f6d372155a889a29f534e Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 09:40:40 -0500 Subject: [PATCH 04/31] add logger --- docs/api.rst | 1 + src/spey/__init__.py | 49 ++++++++++++++++++++++++++++-------- src/spey/system/logger.py | 52 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 src/spey/system/logger.py diff --git a/docs/api.rst b/docs/api.rst index b67fb58..de093c9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -28,6 +28,7 @@ Top-Level helper_functions.correlation_to_covariance helper_functions.covariance_to_correlation optimizer.core.fit + set_log_level Main Classes ------------ diff --git a/src/spey/__init__.py b/src/spey/__init__.py index 3dbfe18..8e4c568 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -1,4 +1,6 @@ +import logging import os +import sys from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union import numpy as np @@ -8,6 +10,7 @@ from spey.base import BackendBase, ConverterBase from spey.combiner import UnCorrStatisticsCombiner from spey.interface.statistical_model import StatisticalModel, statistical_model_wrapper +from spey.system import logger from spey.system.exceptions import PluginError from ._version import __version__ @@ -29,6 +32,7 @@ "check_updates", "get_backend_bibtex", "cite", + "set_log_level", ] @@ -36,6 +40,34 @@ def __dir__(): return __all__ +logger.init(LoggerStream=sys.stdout) +log = logging.getLogger("Spey") + + +def set_log_level(level: int) -> None: + """ + Set log level for spey + + Args: + level (``int``): log level + + * 0: error + * 1: warning + * 2: info + * 3: debug + """ + log_dict = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG, + } + log.setLevel(log_dict[level]) + + +set_log_level(2) + + def version() -> Text: """ Version of ``spey`` package @@ -246,8 +278,6 @@ def get_backend_bibtex(name: Text) -> List[Text]: else: meta = get_backend_metadata(name) - import warnings - try: import textwrap @@ -271,8 +301,9 @@ def get_backend_bibtex(name: Text) -> List[Text]: current_bibtex = response.text if current_bibtex not in txt: txt.append(current_bibtex) - except Exception: # pylint: disable=W0718 - warnings.warn("Unable to retreive bibtex information.", category=UserWarning) + except Exception as err: # pylint: disable=W0718 + log.warning("Unable to retreive bibtex information.") + log.debug(str(err)) return txt @@ -302,10 +333,8 @@ def check_updates() -> None: os.environ["SPEY_CHECKUPDATE"]="OFF" """ - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel, W1203 try: - import warnings - import requests response = requests.get("https://pypi.org/pypi/spey/json", timeout=1) @@ -314,13 +343,13 @@ def check_updates() -> None: pypi_version = pypi_info.get("info", {}).get("version", False) if pypi_version: if Version(version()) < Version(pypi_version): - warnings.warn( + log.warning( f"An update is available. Current version of spey is {version()}, " f"available version is {pypi_version}." ) - except Exception: # pylint: disable=W0718 + except Exception as err: # pylint: disable=W0718 # Can not retreive updates - pass + log.debug(str(err)) if os.environ.get("SPEY_CHECKUPDATE", "ON").upper() != "OFF": diff --git a/src/spey/system/logger.py b/src/spey/system/logger.py new file mode 100644 index 0000000..d76a987 --- /dev/null +++ b/src/spey/system/logger.py @@ -0,0 +1,52 @@ +"""Logging style for spey""" +import logging +import sys + +# pylint: disable=C0103 + + +class ColoredFormatter(logging.Formatter): + """Coloured logging formatter for spey""" + + def __init__(self, msg): + logging.Formatter.__init__(self, msg) + + def format(self, record): + if record.levelno >= 50: # FATAL + color = "\x1b[31mSpey - ERROR: " + elif record.levelno >= 40: # ERROR + color = "\x1b[31mSpey - ERROR: " + elif record.levelno >= 30: # WARNING + color = "\x1b[35mSpey - WARNING: " + elif record.levelno >= 20: # INFO + color = "\x1b[0mSpey: " + elif record.levelno >= 10: # DEBUG + color = ( + f"\x1b[36mSpey - DEBUG ({record.module}.{record.funcName}() " + f"in {record.filename}::L{record.lineno}): " + ) + else: # ANYTHING ELSE + color = "\x1b[0mSpey: " + + record.msg = color + str(record.msg) + "\x1b[0m" + return logging.Formatter.format(self, record) + + +def init(LoggerStream=sys.stdout): + """Initialise logger""" + rootLogger = logging.getLogger() + hdlr = logging.StreamHandler() + fmt = ColoredFormatter("%(message)s") + hdlr.setFormatter(fmt) + rootLogger.addHandler(hdlr) + + # we need to replace all root loggers by ma5 loggers for a proper + # interface with madgraph5 + SpeyLogger = logging.getLogger("Spey") + for hdlr in SpeyLogger.handlers: + SpeyLogger.removeHandler(hdlr) + hdlr = logging.StreamHandler(LoggerStream) + fmt = ColoredFormatter("%(message)s") + hdlr.setFormatter(fmt) + SpeyLogger.addHandler(hdlr) + SpeyLogger.propagate = False From fb64b4ea7c3a669f78f44add8de84ffb2070225c Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 15:41:51 -0500 Subject: [PATCH 05/31] update bibliography capabilities --- src/spey/__init__.py | 115 ++++++++++++------------------------ src/spey/system/webutils.py | 113 +++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 77 deletions(-) create mode 100644 src/spey/system/webutils.py diff --git a/src/spey/__init__.py b/src/spey/__init__.py index 8e4c568..76841b8 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -1,7 +1,9 @@ import logging import os +import re import sys -from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union +import textwrap +from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union, Literal import numpy as np import pkg_resources @@ -12,6 +14,7 @@ from spey.interface.statistical_model import StatisticalModel, statistical_model_wrapper from spey.system import logger from spey.system.exceptions import PluginError +from spey.system.webutils import get_bibtex, check_updates from ._version import __version__ from .about import about @@ -42,9 +45,10 @@ def __dir__(): logger.init(LoggerStream=sys.stdout) log = logging.getLogger("Spey") +log.setLevel(logging.INFO) -def set_log_level(level: int) -> None: +def set_log_level(level: Literal[0, 1, 2, 3]) -> None: """ Set log level for spey @@ -65,9 +69,6 @@ def set_log_level(level: int) -> None: log.setLevel(log_dict[level]) -set_log_level(2) - - def version() -> Text: """ Version of ``spey`` package @@ -245,6 +246,7 @@ def get_backend_metadata(name: Text) -> Dict[Text, Any]: "spey_requires": statistical_model.spey_requires, "doi": list(getattr(statistical_model, "doi", [])), "arXiv": list(getattr(statistical_model, "arXiv", [])), + "zenodo": list(getattr(statistical_model, "zenodo", [])), } raise PluginError( @@ -271,85 +273,44 @@ def get_backend_bibtex(name: Text) -> List[Text]: ``List[Text]``: BibTex entries for the backend. """ - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel, W1203 txt = [] - if name is None: - meta = {"arXiv": ["2307.06996"], "doi": ["10.5281/zenodo.10569099"]} - else: - meta = get_backend_metadata(name) - - try: - import textwrap - - import requests - - # check arXiv - for arxiv_id in meta.get("arXiv", []): - response = requests.get( - f"https://inspirehep.net/api/arxiv/{arxiv_id}", - headers={"accept": "application/x-bibtex"}, - timeout=5, - ) - response.encoding = "utf-8" - txt.append(textwrap.indent(response.text, " " * 4)) - for doi in meta.get("doi", []): - page = f"https://doi.org/{doi}" if "https://doi.org/" not in doi else doi - response = requests.get( - page, headers={"accept": "application/x-bibtex"}, timeout=5 - ) - response.encoding = "utf-8" - current_bibtex = response.text - if current_bibtex not in txt: - txt.append(current_bibtex) - except Exception as err: # pylint: disable=W0718 - log.warning("Unable to retreive bibtex information.") - log.debug(str(err)) + meta = get_backend_metadata(name) + + for arxiv_id in meta.get("arXiv", []): + tmp = get_bibtex("inspire/arxiv", arxiv_id) + if tmp != "": + txt.append(textwrap.indent(tmp, " " * 4)) + else: + log.debug(f"Can not find {arxiv_id} in Inspire") + for doi in meta.get("doi", []): + tmp = get_bibtex("inspire/doi", doi) + if tmp == "": + log.debug(f"Can not find {doi} in Inspire, looking at doi.org") + tmp = get_bibtex("doi", doi) + if tmp != "": + txt.append(tmp) + else: + log.debug(f"Can not find {doi} in doi.org") + else: + txt.append(textwrap.indent(tmp, " " * 4)) + for zenodo_id in meta.get("zenodo", []): + tmp = get_bibtex("zenodo", zenodo_id) + if tmp != "": + txt.append(textwrap.indent(tmp, " " * 4)) + else: + log.debug(f"{zenodo_id} is not a valid zenodo identifier") return txt def cite() -> List[Text]: """Retreive BibTex information for Spey""" - return get_backend_bibtex(None) - - -def check_updates() -> None: - """ - Check Spey Updates. - - Spey always checks updates when initialised. To disable this set the following - - Option if using terminal: - - .. code-block:: bash - - export SPEY_CHECKUPDATE=OFF - - Option if using python interface: - - .. code-block:: python - - import os - os.environ["SPEY_CHECKUPDATE"]="OFF" - - """ - # pylint: disable=import-outside-toplevel, W1203 - try: - import requests - - response = requests.get("https://pypi.org/pypi/spey/json", timeout=1) - response.encoding = "utf-8" - pypi_info = response.json() - pypi_version = pypi_info.get("info", {}).get("version", False) - if pypi_version: - if Version(version()) < Version(pypi_version): - log.warning( - f"An update is available. Current version of spey is {version()}, " - f"available version is {pypi_version}." - ) - except Exception as err: # pylint: disable=W0718 - # Can not retreive updates - log.debug(str(err)) + arxiv = textwrap.indent(get_bibtex("inspire/arxiv", "2307.06996"), " " * 4) + zenodo = get_bibtex("zenodo", "10156353") + linker = re.search("@software{(.+?),\n", zenodo).group(1) + zenodo = textwrap.indent(zenodo.replace(linker, "spey_zenodo"), " " * 4) + return arxiv + "\n\n" + zenodo if os.environ.get("SPEY_CHECKUPDATE", "ON").upper() != "OFF": diff --git a/src/spey/system/webutils.py b/src/spey/system/webutils.py new file mode 100644 index 0000000..d59efd1 --- /dev/null +++ b/src/spey/system/webutils.py @@ -0,0 +1,113 @@ +import logging +from typing import Literal, Text + +import requests +from pkg_resources import get_distribution +from semantic_version import Version + +log = logging.getLogger("Spey") + +__all__ = ["get_bibtex", "check_updates"] + + +def __dir__(): + return __all__ + + +def get_bibtex( + home: Literal["inspire/arxiv", "inspire/doi", "doi", "zenodo"], + identifier: Text, + timeout: int = 5, +) -> Text: + """ + Retreive BibTex information from InspireHEP, Zenodo or DOI.org + + Args: + home (``Text``): Home location for the identifier. + + * ``"inspire/arxiv"``: retreive information from inspire using + arXiv number. + * ``"inspire/doi"``: retreive information from inspore using + doi number. + * ``"doi"``: retreive information from doi.org + * ``"zenodo"``: retreive information from zenodo using zenodo + identifier. + + identifier (``Text``): web identifier + timeout (``int``, default ``5``): time out. + + Returns: + ``Text``: + Bibtex entry. + """ + # pylint: disable=W1203 + + if requests is None: + log.error("Unable to retreive information. Please install `requests`.") + return "" + + home_loc = { + "inspire/arxiv": "https://inspirehep.net/api/arxiv/%s", + "inspire/doi": "https://inspirehep.net/api/doi/%s", + "doi": "https://doi.org/%s", + "zenodo": "https://zenodo.org/api/records/%s", + } + + try: + response = requests.get( + home_loc[home] % identifier, + headers={"accept": "application/x-bibtex"}, + timeout=timeout, + ) + if not 200 <= response.status_code <= 299: + log.debug(f"HTML Status Code: {response.status_code}") + return "" + response.encoding = "utf-8" + return response.text + except Exception as err: # pylint: disable=W0718 + log.debug(str(err)) + return "" + + +def check_updates() -> None: + """ + Check Spey Updates. + + Spey always checks updates when initialised. To disable this set the following + + Option if using terminal: + + .. code-block:: bash + + export SPEY_CHECKUPDATE=OFF + + Option if using python interface: + + .. code-block:: python + + import os + os.environ["SPEY_CHECKUPDATE"]="OFF" + + """ + # pylint: disable=import-outside-toplevel, W1203 + try: + response = requests.get("https://pypi.org/pypi/spey/json", timeout=1) + response.encoding = "utf-8" + pypi_info = response.json() + pypi_version = pypi_info.get("info", {}).get("version", False) + version = get_distribution("spey").version + if pypi_version: + if Version(version) < Version(pypi_version): + log.warning( + f"An update is available. Current version of spey is {version}, " + f"available version is {pypi_version}." + ) + elif Version(version) > Version(pypi_version): + log.warning( + f"An unstable version of spey ({version}) is being used." + f" Latest stable version is {pypi_version}." + ) + + except Exception as err: # pylint: disable=W0718 + # Can not retreive updates + log.debug(str(err)) From 328715548cfb2ad9d84de5b8637787bd40fe964d Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 15:41:58 -0500 Subject: [PATCH 06/31] optimise --- src/spey/about.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/spey/about.py b/src/spey/about.py index f3fce8d..7576a5f 100644 --- a/src/spey/about.py +++ b/src/spey/about.py @@ -8,7 +8,7 @@ import scipy import semantic_version import tqdm -from pkg_resources import iter_entry_points +from pkg_resources import get_distribution, iter_entry_points def about() -> None: @@ -21,13 +21,7 @@ def about() -> None: ) print(f"Numpy version: {numpy.__version__}") print(f"Scipy version: {scipy.__version__}") - print( - "Autograd version: %s" - % check_output([sys.executable, "-m", "pip", "show", "autograd"]) - .decode() - .split("\n")[1] - .split(":")[1][1:] - ) + print(f"Autograd version: {get_distribution('autograd').version}") print(f"tqdm version: {tqdm.__version__}") print(f"semantic_version version: {semantic_version.__version__}") From d9b6f7481bef215a5022ef023572063d079945d5 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 15:45:51 -0500 Subject: [PATCH 07/31] add debug --- src/spey/system/webutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/spey/system/webutils.py b/src/spey/system/webutils.py index d59efd1..0ed06cd 100644 --- a/src/spey/system/webutils.py +++ b/src/spey/system/webutils.py @@ -97,6 +97,7 @@ def check_updates() -> None: pypi_version = pypi_info.get("info", {}).get("version", False) version = get_distribution("spey").version if pypi_version: + log.debug(f"Curernt version {version}, latest version {pypi_version}.") if Version(version) < Version(pypi_version): log.warning( f"An update is available. Current version of spey is {version}, " From 22aaf8e8cbfe651b3e1f1c6902817a90e8759032 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 15:47:49 -0500 Subject: [PATCH 08/31] add requests --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 54f1081..3f70e6b 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ "autograd==1.5", "semantic_version~=2.10", "tqdm>=4.64.0", + "requests>=2.31.0", ] backend_plugins = [ From e4ca89e5c35c21634290d29db76b3945c2a344c2 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 15:50:14 -0500 Subject: [PATCH 09/31] update docstring --- src/spey/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spey/__init__.py b/src/spey/__init__.py index 76841b8..27cab96 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -260,7 +260,7 @@ def get_backend_bibtex(name: Text) -> List[Text]: """ Retreive BibTex entry for backend plug-in if available. - The bibtext entries are retreived both from Inspire HEP and doi.org. + The bibtext entries are retreived both from Inspire HEP, doi.org and zenodo. If the arXiv number matches the DOI the output will include two versions of the same reference. If backend does not include an arXiv or DOI number it will return an empty list. From 0a47777406f7663f6c68eb77edfdf83c5bda9cd6 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 22:01:50 -0500 Subject: [PATCH 10/31] add logging --- src/spey/backends/default_pdf/__init__.py | 10 +++++- src/spey/backends/default_pdf/third_moment.py | 20 +++++++---- src/spey/interface/statistical_model.py | 34 ++++++++++--------- src/spey/optimizer/core.py | 13 ++++++- src/spey/optimizer/scipy_tools.py | 18 +++++++--- 5 files changed, 65 insertions(+), 30 deletions(-) diff --git a/src/spey/backends/default_pdf/__init__.py b/src/spey/backends/default_pdf/__init__.py index 8dfdc92..a4f6051 100644 --- a/src/spey/backends/default_pdf/__init__.py +++ b/src/spey/backends/default_pdf/__init__.py @@ -1,5 +1,6 @@ """Interface for default PDF sets""" +import logging from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union from autograd import grad, hessian, jacobian @@ -17,6 +18,9 @@ from .uncertainty_synthesizer import signal_uncertainty_synthesizer # pylint: disable=E1101,E1120 +log = logging.getLogger("Spey") + +# pylint: disable=W1203 class DefaultPDFBase(BackendBase): @@ -92,7 +96,7 @@ def __init__( self.signal_uncertainty_configuration = signal_uncertainty_synthesizer( signal_yields=self.signal_yields, **signal_uncertainty_configuration, - domain=slice(len(background_yields) + 1, None) + domain=slice(len(background_yields) + 1, None), ) minimum_poi = -np.inf @@ -101,6 +105,7 @@ def __init__( self.background_yields[self.signal_yields > 0.0] / self.signal_yields[self.signal_yields > 0.0] ) + log.debug(f"Min POI set to : {minimum_poi}") self._main_model = None self._constraint_model = None @@ -243,6 +248,7 @@ def get_objective_function( self.background_yields if expected == ExpectationType.apriori else self.data ) data = current_data if data is None else data + log.debug(f"Data: {data}") def negative_loglikelihood(pars: np.ndarray) -> np.ndarray: """Compute twice negative log-likelihood""" @@ -291,6 +297,7 @@ def get_logpdf_func( self.background_yields if expected == ExpectationType.apriori else self.data ) data = current_data if data is None else data + log.debug(f"Data: {data}") return lambda pars: self.main_model.log_prob( pars, data[: len(self.data)] @@ -329,6 +336,7 @@ def get_hessian_logpdf_func( self.background_yields if expected == ExpectationType.apriori else self.data ) data = current_data if data is None else data + log.debug(f"Data: {data}") def log_prob(pars: np.ndarray) -> np.ndarray: """Compute log-probability""" diff --git a/src/spey/backends/default_pdf/third_moment.py b/src/spey/backends/default_pdf/third_moment.py index 052faa6..87e54f1 100644 --- a/src/spey/backends/default_pdf/third_moment.py +++ b/src/spey/backends/default_pdf/third_moment.py @@ -1,12 +1,13 @@ """Tools for computing third moment expansion""" import warnings from typing import Optional, Tuple, Union - +import logging import autograd.numpy as np from scipy import integrate from scipy.stats import norm -# pylint: disable=E1101,E1120 +# pylint: disable=E1101,E1120,W1203 +log = logging.getLogger("Spey") def third_moment_expansion( @@ -37,11 +38,11 @@ def third_moment_expansion( """ cov_diag = np.diag(covariance_matrix) - # ! Assertion error is removed, instead nan values will be converted to zero. - # assert np.all(8.0 * cov_diag**3 >= third_moment**2), ( - # "Given covariance matrix and diagonal terms of the third moment does not " - # + "satisfy the condition: 8 * diag(cov)**3 >= third_moment**2." - # ) + if not np.all(8.0 * cov_diag**3 >= third_moment**2): + log.warning( + r"Third moments does not satisfy the following condition: $8\Sigma_{ii}^3 \geq (m^{(3)}_i)^2$" + ) + log.warning("The values that do not satisfy this condition will be set to zero.") # arXiv:1809.05548 eq. 2.9 with warnings.catch_warnings(record=True) as w: @@ -61,12 +62,15 @@ def third_moment_expansion( category=RuntimeWarning, ) C = np.where(np.isnan(C), 0.0, C) + log.debug(f"C: {C}") # arXiv:1809.05548 eq. 2.10 B = np.sqrt(cov_diag - 2 * C**2) + log.debug(f"B: {B}") # arXiv:1809.05548 eq. 2.11 A = expectation_value - C + log.debug(f"A: {A}") # arXiv:1809.05548 eq. 2.12 eps = 1e-5 @@ -88,6 +92,7 @@ def third_moment_expansion( if i != j: corr[j, i] = corr[i, j] + log.debug(f"rho: {corr}") return A, B, C, corr return A, B, C @@ -154,5 +159,6 @@ def compute_x3BifurgatedGaussian(x: float, upper: float, lower: float) -> float: if return_integration_error: return np.array(third_moment), np.array(error) + log.debug(f"Error: {error}") return np.array(third_moment) diff --git a/src/spey/interface/statistical_model.py b/src/spey/interface/statistical_model.py index 4b71967..ea414d0 100644 --- a/src/spey/interface/statistical_model.py +++ b/src/spey/interface/statistical_model.py @@ -1,6 +1,5 @@ """Statistical Model wrapper class""" - -import inspect +import logging from typing import Any, Callable, Dict, List, Optional, Text, Tuple, Union import numpy as np @@ -22,13 +21,9 @@ def __dir__(): return __all__ -def _module_check(backend: BackendBase, func_name: Union[Text, List[Text]]) -> bool: - """Check if the member function is implemented in backend or in plugin""" - func_name = [func_name] if isinstance(func_name, str) else func_name - return all( - ("spey/base/backend_base.py" not in inspect.getfile(getattr(backend, fn))) - for fn in func_name - ) +log = logging.getLogger("Spey") + +# pylint: disable=W1203 class StatisticalModel(HypothesisTestingBase): @@ -104,15 +99,17 @@ def backend_type(self) -> Text: @property def is_asymptotic_calculator_available(self) -> bool: """Check if Asymptotic calculator is available for the backend""" - return _module_check(self.backend, "expected_data") or _module_check( - self.backend, - ["asimov_negative_loglikelihood", "minimize_asimov_negative_loglikelihood"], + return self.backend.expected_data != BackendBase.expected_data or ( + self.backend.asimov_negative_loglikelihood + != BackendBase.asimov_negative_loglikelihood + and self.backend.minimize_asimov_negative_loglikelihood + != BackendBase.minimize_asimov_negative_loglikelihood ) @property def is_toy_calculator_available(self) -> bool: """Check if Toy calculator is available for the backend""" - return _module_check(self.backend, "get_sampler") + return self.backend.get_sampler != BackendBase.get_sampler @property def is_chi_square_calculator_available(self) -> bool: @@ -166,8 +163,7 @@ def excluded_cross_section( raise UnknownCrossSection("Cross-section value has not been initialised.") return ( - self.poi_upper_limit(expected=expected, confidence_level=0.95) - * self.xsection + self.poi_upper_limit(expected=expected, confidence_level=0.95) * self.xsection ) @property @@ -233,6 +229,7 @@ def prepare_for_fit( expected=expected, data=data, do_grad=do_grad ) except NotImplementedError: + log.debug("Gradient is not available, will not be included in computation.") do_grad = False objective_and_grad = self.backend.get_objective_function( expected=expected, data=data, do_grad=do_grad @@ -370,7 +367,7 @@ def generate_asimov_data( """ fit_opts = self.prepare_for_fit( expected=expected, - allow_negative_signal=True if test_statistic in ["q", "qmu"] else False, + allow_negative_signal=test_statistic in ["q", "qmu"], **kwargs, ) @@ -380,6 +377,7 @@ def generate_asimov_data( bounds=par_bounds, fixed_poi_value=1.0 if test_statistic == "q0" else 0.0, ) + log.debug(f"fit parameters: {fit_pars}") return self.backend.expected_data(fit_pars) @@ -510,6 +508,7 @@ def maximize_likelihood( logpdf, fit_param = fit( **fit_opts, initial_parameters=init_pars, bounds=par_bounds ) + log.debug(f"fit parameters: {fit_param}") muhat = fit_param[self.backend.config().poi_index] return muhat, -logpdf if return_nll else np.exp(logpdf) @@ -643,6 +642,7 @@ def fixed_poi_sampler( fixed_poi_value=poi_test, ) + log.debug(f"fit parameters: {fit_param}") try: sampler = self.backend.get_sampler(fit_param) except NotImplementedError as exc: @@ -704,8 +704,10 @@ def sigma_mu_from_hessian( bounds=par_bounds, fixed_poi_value=poi_test, ) + log.debug(f"fit parameters: {fit_param}") hessian = -1.0 * hessian_func(fit_param) + log.debug(f"full hessian: {hessian}") poi_index = self.backend.config().poi_index return np.sqrt(np.linalg.inv(hessian)[poi_index, poi_index]) diff --git a/src/spey/optimizer/core.py b/src/spey/optimizer/core.py index 30fb56e..a5f1cad 100644 --- a/src/spey/optimizer/core.py +++ b/src/spey/optimizer/core.py @@ -1,9 +1,16 @@ -from typing import Callable, List, Tuple, Optional, Dict +import logging +from typing import Callable, Dict, List, Optional, Tuple + import numpy as np from spey.base.model_config import ModelConfig + from .scipy_tools import minimize +# pylint: disable=W1203 + +log = logging.getLogger("Spey") + def fit( func: Callable[[np.ndarray], np.ndarray], @@ -58,6 +65,9 @@ def func(vector: np.ndarray) -> float: constraints = [] if constraints is None else constraints if fixed_poi_value is not None: init_pars[model_configuration.poi_index] = fixed_poi_value + log.debug( + f"Fixing POI index at: {model_configuration.poi_index} to value {fixed_poi_value}" + ) constraints.append( { "type": "eq", @@ -68,6 +78,7 @@ def func(vector: np.ndarray) -> float: if model_configuration.suggested_fixed is not None: for idx, isfixed in enumerate(model_configuration.suggested_fixed): if isfixed: + log.debug(f"Constraining {idx} to value {init_pars[idx]}") constraints.append( { "type": "eq", diff --git a/src/spey/optimizer/scipy_tools.py b/src/spey/optimizer/scipy_tools.py index f7494dd..777290b 100644 --- a/src/spey/optimizer/scipy_tools.py +++ b/src/spey/optimizer/scipy_tools.py @@ -1,11 +1,16 @@ """Optimisation tools based on scipy""" +import logging import warnings from typing import Callable, Dict, List, Tuple import numpy as np import scipy +# pylint: disable=W1201, W1203, R0913 + +log = logging.getLogger("Spey") + def minimize( func: Callable[[np.ndarray], float], @@ -66,10 +71,8 @@ def minimize( # Note that it is important to respect the lower bounds especially since there might # be bounds that lead to negative yields in the statistical model, e.g. Asimov data, # background + mu * signal etc. - warnings.warn( - message=opt.message + "\nspey::Expanding the bounds.", - category=RuntimeWarning, - ) + log.warning("Optimiser has not been able to satisfy all the conditions:") + log.warning(opt.message + " Expanding the bounds...") init_pars = opt.x for bdx, bound in enumerate(bounds): if bdx == poi_index and constraints is None: @@ -80,6 +83,11 @@ def minimize( break if not opt.success: - warnings.warn(message=opt.message, category=RuntimeWarning) + log.warning("Optimiser has not been able to satisfy all the conditions:") + log.warning(opt.message) + log.debug(f"func: {opt.fun}") + log.debug(f"parameters: {opt.x}") + if do_grad: + log.debug(f"gradients: {func(opt.x)[-1]}") return opt.fun, opt.x From 489c6969b49b216ef926d1ef7a4882c6de92ce22 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 22:04:05 -0500 Subject: [PATCH 11/31] update --- src/spey/system/webutils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/spey/system/webutils.py b/src/spey/system/webutils.py index 0ed06cd..7256b76 100644 --- a/src/spey/system/webutils.py +++ b/src/spey/system/webutils.py @@ -42,10 +42,6 @@ def get_bibtex( """ # pylint: disable=W1203 - if requests is None: - log.error("Unable to retreive information. Please install `requests`.") - return "" - home_loc = { "inspire/arxiv": "https://inspirehep.net/api/arxiv/%s", "inspire/doi": "https://inspirehep.net/api/doi/%s", From 014e41e5642f1b85bc3996402b4e13c92b09cb3c Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 22:11:07 -0500 Subject: [PATCH 12/31] update changelog --- docs/releases/changelog-v0.1.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/releases/changelog-v0.1.md b/docs/releases/changelog-v0.1.md index 0a8efe2..d5142a4 100644 --- a/docs/releases/changelog-v0.1.md +++ b/docs/releases/changelog-v0.1.md @@ -28,6 +28,15 @@ * Update clarification on text based keyword arguments. ([#30](https://github.com/SpeysideHEP/spey/pull/30)) +* Adding logging across the software and implement tools to scilence them. + ([#32](https://github.com/SpeysideHEP/spey/pull/32)) + +* Spey will automatically look for updates during initiation. + ([#32](https://github.com/SpeysideHEP/spey/pull/32)) + +* Utilities to retreive bibtex information for third-party plug-ins. + ([#32](https://github.com/SpeysideHEP/spey/pull/32)) + ## Bug Fixes * In accordance to the latest updates ```UnCorrStatisticsCombiner``` has been updated with From 68a20b9828f54be437a9f7c8a249b7ae359ca65b Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 22:13:55 -0500 Subject: [PATCH 13/31] bump version --- .zenodo.json | 6 +++--- src/spey/_version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.zenodo.json b/.zenodo.json index 723bfc8..1aeedd4 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -1,8 +1,8 @@ { "description": "smooth inference for reinterpretation studies", "license": "MIT", - "title": "SpeysideHEP/spey: v0.1.5", - "version": "v0.1.5", + "title": "SpeysideHEP/spey: v0.1.6", + "version": "v0.1.6", "upload_type": "software", "creators": [ { @@ -29,7 +29,7 @@ }, { "scheme": "url", - "identifier": "https://github.com/SpeysideHEP/spey/tree/v0.1.5", + "identifier": "https://github.com/SpeysideHEP/spey/tree/v0.1.6", "relation": "isSupplementTo" }, { diff --git a/src/spey/_version.py b/src/spey/_version.py index acb9923..235765d 100644 --- a/src/spey/_version.py +++ b/src/spey/_version.py @@ -1,3 +1,3 @@ """Version number (major.minor.patch[-label])""" -__version__ = "0.1.5" +__version__ = "0.1.6" From deadb21d3168f110b69f04d309e2fb7524514d33 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 22:23:31 -0500 Subject: [PATCH 14/31] add more tests --- src/spey/interface/functiontools.py | 28 ------------------------ tests/test_default_pdf.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 28 deletions(-) delete mode 100644 src/spey/interface/functiontools.py diff --git a/src/spey/interface/functiontools.py b/src/spey/interface/functiontools.py deleted file mode 100644 index d1aec30..0000000 --- a/src/spey/interface/functiontools.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Text, Any, ClassVar - - -def get_function( - class_base: ClassVar, - function_name: Text, - default: Any = None, - exception: Exception = NotImplementedError, - **kwargs, -) -> Any: - """ - Function wrapper to be able to set a default value for the attributes that are not implemented - - :param class_base (`ClassVar`): Class basis where the function supposed to live - :param function_name (`Text`): name of the function - :param default (`Any`): default value that function needs to return. - :param exception (`Exception`, default `NotImplementedError`): Exception that should be avoided. - :param kwargs: function inputs - :return `Any`: default value or the function output - """ - try: - output = getattr(class_base, function_name, default) - if callable(output): - output = output(**kwargs) - except exception: - output = default(**kwargs) if callable(default) else default - - return output diff --git a/tests/test_default_pdf.py b/tests/test_default_pdf.py index eb97d23..16bd8d4 100644 --- a/tests/test_default_pdf.py +++ b/tests/test_default_pdf.py @@ -46,6 +46,9 @@ def test_correlated_background(): assert np.isclose( statistical_model.exclusion_confidence_level()[0], 0.9635100547173434 ), "CLs is wrong" + assert np.isclose( + statistical_model.sigma_mu(1.0), 0.8456469932844632 + ), "Sigma mu is wrong" def test_third_moment(): @@ -68,6 +71,9 @@ def test_third_moment(): assert np.isclose( statistical_model.poi_upper_limit(), 0.9221339770245336 ), "POI is wrong" + assert np.isclose( + statistical_model.sigma_mu(1.0), 0.854551194250324 + ), "Sigma mu is wrong" def test_effective_sigma(): @@ -90,3 +96,30 @@ def test_effective_sigma(): assert np.isclose( statistical_model.poi_upper_limit(), 1.5298573610113775 ), "POI is wrong." + assert np.isclose( + statistical_model.sigma_mu(1.0), 1.2152765953701747 + ), "Sigma mu is wrong" + + +def test_poisson(): + """tester for uncorrelated background model""" + + pdf_wrapper = spey.get_backend("default_pdf.poisson") + + data = [36, 33] + signal_yields = [12.0, 15.0] + background_yields = [50.0, 48.0] + + stat_model = pdf_wrapper( + signal_yields=signal_yields, + background_yields=background_yields, + data=data, + analysis="multi_bin", + xsection=0.123, + ) + + assert np.isclose(stat_model.poi_upper_limit(), 0.3140867496931846), "POI is wrong." + assert np.isclose( + stat_model.exclusion_confidence_level()[0], 0.9999807105228611 + ), "CLs is wrong" + assert np.isclose(stat_model.sigma_mu(1.0), 0.5573350296644078), "Sigma mu is wrong" From a46b31eb6ea7851181a58d21641943311889ef7b Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 23:01:14 -0500 Subject: [PATCH 15/31] add callers for grad and hessian --- docs/api.rst | 11 ++++++ src/spey/__init__.py | 1 + src/spey/math.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 src/spey/math.py diff --git a/docs/api.rst b/docs/api.rst index 376f4f5..2c8583e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -82,6 +82,17 @@ Hypothesis testing asymptotic_calculator.compute_asymptotic_confidence_level toy_calculator.compute_toy_confidence_level +Gradient Tools +-------------- + +.. currentmodule:: spey.math + +.. autosummary:: + :toctree: _generated/ + + value_and_grad + hessian + Default Backends ---------------- diff --git a/src/spey/__init__.py b/src/spey/__init__.py index a00161c..1691a4b 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -25,6 +25,7 @@ "BackendBase", "ConverterBase", "about", + "math", ] diff --git a/src/spey/math.py b/src/spey/math.py new file mode 100644 index 0000000..04bb370 --- /dev/null +++ b/src/spey/math.py @@ -0,0 +1,86 @@ +from typing import Callable, Optional, Tuple + +from autograd import numpy + +from .interface.statistical_model import StatisticalModel +from .utils import ExpectationType + +# pylint: disable=E1101 + +__all__ = ["value_and_grad", "hessian"] + + +def __dir__(): + return __all__ + + +def value_and_grad( + statistical_model: StatisticalModel, + expected: ExpectationType = ExpectationType.observed, + data: Optional[numpy.ndarray] = None, +) -> Callable[[numpy.ndarray], Tuple[numpy.ndarray, numpy.ndarray]]: + """ + Retreive function to compute negative log-likelihood and its gradient. + + Args: + statistical_model (~spey.StatisticalModel): statistical model to be used. + expected (~spey.ExpectationType): Sets which values the fitting algorithm should focus and + p-values to be computed. + + * :obj:`~spey.ExpectationType.observed`: Computes the p-values with via post-fit + prescriotion which means that the experimental data will be assumed to be the truth + (default). + * :obj:`~spey.ExpectationType.aposteriori`: Computes the expected p-values with via + post-fit prescriotion which means that the experimental data will be assumed to be + the truth. + * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit + prescription which means that the SM will be assumed to be the truth. + + data (``numpy.ndarray``, default ``None``): input data that to fit. If `None` observed + data will be used. + + Returns: + ``Callable[[numpy.ndarray], numpy.ndarray, numpy.ndarray]``: + negative log-likelihood and its gradient with respect to nuisance parameters + """ + val_and_grad = statistical_model.backend.get_objective_function( + expected=expected, data=data, do_grad=True + ) + return lambda pars: val_and_grad(numpy.array(pars)) + + +def hessian( + statistical_model: StatisticalModel, + expected: ExpectationType = ExpectationType.observed, + data: Optional[numpy.ndarray] = None, +) -> Callable[[numpy.ndarray], numpy.ndarray]: + r""" + Retreive the function to compute Hessian of negative log-likelihood + + .. math:: + + {\rm Hessian} = -\frac{\partial^2\mathcal{L}(\theta)}{\partial\theta_i\partial\theta_j} + + Args: + statistical_model (~spey.StatisticalModel): statistical model to be used. + expected (~spey.ExpectationType): Sets which values the fitting algorithm should focus and + p-values to be computed. + + * :obj:`~spey.ExpectationType.observed`: Computes the p-values with via post-fit + prescriotion which means that the experimental data will be assumed to be the truth + (default). + * :obj:`~spey.ExpectationType.aposteriori`: Computes the expected p-values with via + post-fit prescriotion which means that the experimental data will be assumed to be + the truth. + * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit + prescription which means that the SM will be assumed to be the truth. + + data (``numpy.ndarray``, default ``None``): input data that to fit. If `None` observed + data will be used. + + Returns: + ``Callable[[numpy.ndarray], numpy.ndarray]``: + function to compute hessian of negative log-likelihood + """ + hess = statistical_model.backend.get_hessian_logpdf_func(expected=expected, data=data) + return lambda pars: -1.0 * hess(numpy.array(pars)) From fce776cb3b3865bf9065e054b692f9aa326d06e3 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sat, 27 Jan 2024 23:08:55 -0500 Subject: [PATCH 16/31] update changelog --- docs/releases/changelog-v0.1.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releases/changelog-v0.1.md b/docs/releases/changelog-v0.1.md index d5142a4..d0cdc77 100644 --- a/docs/releases/changelog-v0.1.md +++ b/docs/releases/changelog-v0.1.md @@ -37,6 +37,12 @@ * Utilities to retreive bibtex information for third-party plug-ins. ([#32](https://github.com/SpeysideHEP/spey/pull/32)) +* Add math utilities for users to extract gradient and hessian of negative log-likelihood + ([#31](https://github.com/SpeysideHEP/spey/pull/31)) + +* Improve gradient execution for `default_pdf`. + ([#31](https://github.com/SpeysideHEP/spey/pull/31)) + ## Bug Fixes * In accordance to the latest updates ```UnCorrStatisticsCombiner``` has been updated with From a9f4c5fbfee3f04fe860ff43610f79150c1428aa Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 09:18:21 -0500 Subject: [PATCH 17/31] bugfix --- src/spey/math.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/spey/math.py b/src/spey/math.py index 04bb370..ab47436 100644 --- a/src/spey/math.py +++ b/src/spey/math.py @@ -1,6 +1,6 @@ -from typing import Callable, Optional, Tuple +from typing import Callable, Optional, Tuple, List -from autograd import numpy +from autograd import numpy as np from .interface.statistical_model import StatisticalModel from .utils import ExpectationType @@ -17,8 +17,8 @@ def __dir__(): def value_and_grad( statistical_model: StatisticalModel, expected: ExpectationType = ExpectationType.observed, - data: Optional[numpy.ndarray] = None, -) -> Callable[[numpy.ndarray], Tuple[numpy.ndarray, numpy.ndarray]]: + data: Optional[List[float]] = None, +) -> Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]: """ Retreive function to compute negative log-likelihood and its gradient. @@ -36,24 +36,24 @@ def value_and_grad( * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit prescription which means that the SM will be assumed to be the truth. - data (``numpy.ndarray``, default ``None``): input data that to fit. If `None` observed + data (``np.ndarray``, default ``None``): input data that to fit. If `None` observed data will be used. Returns: - ``Callable[[numpy.ndarray], numpy.ndarray, numpy.ndarray]``: + ``Callable[[np.ndarray], np.ndarray, np.ndarray]``: negative log-likelihood and its gradient with respect to nuisance parameters """ val_and_grad = statistical_model.backend.get_objective_function( - expected=expected, data=data, do_grad=True + expected=expected, data=None if data is None else np.array(data), do_grad=True ) - return lambda pars: val_and_grad(numpy.array(pars)) + return lambda pars: val_and_grad(np.array(pars)) def hessian( statistical_model: StatisticalModel, expected: ExpectationType = ExpectationType.observed, - data: Optional[numpy.ndarray] = None, -) -> Callable[[numpy.ndarray], numpy.ndarray]: + data: Optional[List[float]] = None, +) -> Callable[[np.ndarray], np.ndarray]: r""" Retreive the function to compute Hessian of negative log-likelihood @@ -75,12 +75,14 @@ def hessian( * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit prescription which means that the SM will be assumed to be the truth. - data (``numpy.ndarray``, default ``None``): input data that to fit. If `None` observed + data (``np.ndarray``, default ``None``): input data that to fit. If `None` observed data will be used. Returns: - ``Callable[[numpy.ndarray], numpy.ndarray]``: + ``Callable[[np.ndarray], np.ndarray]``: function to compute hessian of negative log-likelihood """ - hess = statistical_model.backend.get_hessian_logpdf_func(expected=expected, data=data) - return lambda pars: -1.0 * hess(numpy.array(pars)) + hess = statistical_model.backend.get_hessian_logpdf_func( + expected=expected, data=None if data is None else np.array(data) + ) + return lambda pars: -1.0 * hess(np.array(pars)) From 8d432871b8a78a863cc669a3ebb66b108ad6fb93 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 09:18:29 -0500 Subject: [PATCH 18/31] tester for math --- tests/test_math.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test_math.py diff --git a/tests/test_math.py b/tests/test_math.py new file mode 100644 index 0000000..9715c17 --- /dev/null +++ b/tests/test_math.py @@ -0,0 +1,58 @@ +"""Test default grad and hessian""" + +import numpy as np +from spey.math import value_and_grad, hessian +import spey + + +def test_uncorrelated_background(): + """tester for uncorrelated background model""" + + pdf_wrapper = spey.get_backend("default_pdf.uncorrelated_background") + + data = [36, 33] + signal_yields = [12.0, 15.0] + background_yields = [50.0, 48.0] + background_unc = [12.0, 16.0] + + stat_model = pdf_wrapper( + signal_yields=signal_yields, + background_yields=background_yields, + data=data, + absolute_uncertainties=background_unc, + analysis="multi_bin", + xsection=0.123, + ) + hess = hessian(stat_model)([1.0, 1.0]) + nll, grad = value_and_grad(stat_model)([1.0, 1.0]) + nll_apri, grad_apri = value_and_grad( + stat_model, expected=spey.ExpectationType.apriori + )([1.0, 1.0]) + hess_apri = hessian(stat_model, expected=spey.ExpectationType.apriori)([1.0, 1.0]) + + hess_data = hessian(stat_model, expected=spey.ExpectationType.apriori, data=[22, 34])( + [1.0, 1.0] + ) + nll_dat, grad_dat = value_and_grad( + stat_model, expected=spey.ExpectationType.apriori, data=[22, 34] + )([1.0, 1.0]) + + assert np.allclose( + hess, np.array([[2.13638959, 2.21570381], [2.21570381, 4.30030563]]) + ), "Hessian is wrong" + assert np.isclose(nll, 37.47391613937222), "NLL is wrong" + assert np.allclose(grad, np.array([14.89633938, 17.47861786])), "Gradient is wrong" + assert np.isclose(nll_apri, 20.052816087791097), "NLL apriori is wrong" + assert np.allclose( + grad_apri, np.array([9.77796784, 12.1703729]) + ), "apriori Gradient is wrong" + assert np.allclose( + hess_apri, np.array([[3.04532025, 3.16068638], [3.16068638, 5.28374358]]) + ), "Hessian apriori is wrong" + assert np.allclose( + hess_data, np.array([[1.80428957, 1.88600725], [1.88600725, 3.97317276]]) + ), "Hessian data is wrong" + assert np.isclose(nll_dat, 49.63922692607177), "NLL data is wrong" + assert np.allclose( + grad_dat, np.array([16.97673623, 19.54635648]) + ), "data Gradient is wrong" From 9bd30e7f1f1c0ed433c6d8d4bfbb33a97e407e48 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 09:54:16 -0500 Subject: [PATCH 19/31] add python 3.8 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fcd4b03..88c7729 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, macos-latest] steps: From d3b9a11a8c8fb8335420bcfcf0113450b062bf49 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 14:58:33 -0500 Subject: [PATCH 20/31] improve logging --- src/spey/hypothesis_testing/upper_limits.py | 30 ++++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/spey/hypothesis_testing/upper_limits.py b/src/spey/hypothesis_testing/upper_limits.py index cf75c1f..db8073d 100644 --- a/src/spey/hypothesis_testing/upper_limits.py +++ b/src/spey/hypothesis_testing/upper_limits.py @@ -1,8 +1,9 @@ """Tools for computing upper limit on parameter of interest""" +import logging import warnings from functools import partial -from typing import Callable, List, Text, Tuple, Union +from typing import Callable, List, Tuple, Union, Literal import numpy as np import scipy @@ -19,6 +20,11 @@ def __dir__(): return __all__ +log = logging.getLogger("Spey") + +# pylint: disable=W1203,C0103 + + class ComputerWrapper: """ Wrapper for the computer function to track inputs and outputs @@ -86,12 +92,14 @@ def find_root_limits( low *= 0.5 if low < low_bound: break + log.debug(f"Low results: {low_computer._results}") hig_computer = ComputerWrapper(computer) while hig_computer(hig) < loc: hig *= 2.0 if hig > hig_bound: break + log.debug(f"High results: {hig_computer._results}") return low_computer, hig_computer @@ -106,7 +114,7 @@ def find_poi_upper_limit( allow_negative_signal: bool = True, low_init: float = 1.0, hig_init: float = 1.0, - expected_pvalue: Text = "nominal", + expected_pvalue: Literal["nominal", "1sigma", "2sigma"] = "nominal", maxiter: int = 10000, ) -> Union[float, List[float]]: r""" @@ -198,27 +206,28 @@ def computer(poi_test: float, pvalue_idx: int) -> float: "2sigma": range(0, 5), } for pvalue_idx in index_range[expected_pvalue]: + log.debug(f"Running for p-value idx: {pvalue_idx}") comp = partial(computer, pvalue_idx=pvalue_idx) # Set an upper bound for the computation hig_bound = 1e5 low, hig = find_root_limits( comp, loc=0.0, low_ini=low_init, hig_ini=hig_init, hig_bound=hig_bound ) + log.debug(f"low: {low[-1]}, hig: {hig[-1]}") # Check if its possible to find roots if np.sign(low[-1]) * np.sign(hig[-1]) > 0.0: - warnings.warn( - message="Can not find the roots of the function, returning `inf`" + log.warning( + "Can not find the roots of the function, returning `inf`" + f"\n hig, low must bracket a root f({low.get_value(-1):.5e})={low[-1]:.5e}, " + f"f({hig.get_value(-1):.5e})={hig[-1]:.5e}. " + "This is likely due to low number of signal yields." * (hig.get_value(-1) >= hig_bound), - category=RuntimeWarning, ) result.append(np.inf) continue - with warnings.catch_warnings(record=True): + with warnings.catch_warnings(record=True) as w: x0, r = scipy.optimize.toms748( comp, low.get_value(-1), @@ -229,7 +238,14 @@ def computer(poi_test: float, pvalue_idx: int) -> float: full_output=True, maxiter=maxiter, ) + + log.debug("Warnings:") + if log.level == logging.DEBUG: + for warning in w: + log.debug(f"\t{warning.message}") + log.debug("<><>" * 10) + if not r.converged: - warnings.warn(f"Optimiser did not converge.\n{r}", category=RuntimeWarning) + log.warning(f"Optimiser did not converge.\n{r}") result.append(x0) return result if len(result) > 1 else result[0] From fc042295ab98f6ecce3275e336d4e1200070a530 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 14:58:49 -0500 Subject: [PATCH 21/31] remove unnecessary functions --- src/spey/hypothesis_testing/__init__.py | 35 ------------------------- 1 file changed, 35 deletions(-) diff --git a/src/spey/hypothesis_testing/__init__.py b/src/spey/hypothesis_testing/__init__.py index eda2b0b..bc144d7 100644 --- a/src/spey/hypothesis_testing/__init__.py +++ b/src/spey/hypothesis_testing/__init__.py @@ -1,37 +1,2 @@ -from typing import Callable, Text, Tuple, List - -from spey.system.exceptions import UnknownComputer from .asymptotic_calculator import compute_asymptotic_confidence_level from .toy_calculator import compute_toy_confidence_level - -__all__ = ["get_confidence_level_computer"] - - -def get_confidence_level_computer( - name: str, -) -> Callable[[float, float, Text], Tuple[List[float], List[float]]]: - r""" - Retreive confidence level computer - - Args: - name (``str``): Name of the computer - - * ``"asymptotic"``: uses asymptotic confidence level computer - * ``"toy"``: uses toy confidence level computer - - Raises: - ``UnknownComputer``: If ``name`` input does not correspond any of the above. - - Returns: - ``Callable[[float, float, Text], Tuple[List[float], List[float]]]``: - Confidence level computer. - """ - try: - return { - "asymptotic": compute_asymptotic_confidence_level, - "toy": compute_toy_confidence_level, - }[name] - except KeyError as excl: - raise UnknownComputer( - f"{name} is unknown. Available confidence level computers are 'toy' or 'asymptotic'" - ) from excl From 4fff2c5a8886b78847096895bf9f419a6781793a Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 14:59:06 -0500 Subject: [PATCH 22/31] improve logging --- src/spey/base/hypotest_base.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/spey/base/hypotest_base.py b/src/spey/base/hypotest_base.py index 673305d..73794ca 100644 --- a/src/spey/base/hypotest_base.py +++ b/src/spey/base/hypotest_base.py @@ -3,6 +3,7 @@ tools to compute exclusion limits and POI upper limits """ +import logging import warnings from abc import ABC, abstractmethod from functools import partial @@ -31,6 +32,11 @@ def __dir__(): return __all__ +log = logging.getLogger("Spey") + +# pylint: disable=W1203,C0103 + + class HypothesisTestingBase(ABC): """ Abstract class that ensures classes that are performing hypothesis teststing includes certain @@ -352,6 +358,7 @@ def chi2( denominator = self.likelihood( poi_test=poi_test_denominator, expected=expected, **kwargs ) + log.debug(f"denominator: {denominator}") return 2.0 * ( self.likelihood(poi_test=poi_test, expected=expected, **kwargs) - denominator @@ -421,11 +428,13 @@ def _prepare_for_hypotest( allow_negative_signal=allow_negative_signal, **kwargs, ) + log.debug(f"muhat: {muhat}, nll: {nll}") muhatA, nllA = self.maximize_asimov_likelihood( expected=expected, test_statistics=test_statistics, **kwargs, ) + log.debug(f"muhatA: {muhatA}, nllA: {nllA}") def logpdf(mu: Union[float, np.ndarray]) -> float: return -self.likelihood( @@ -511,7 +520,7 @@ def sigma_mu( poi_test=poi_test, expected=expected, **kwargs ) except MethodNotAvailable: - warnings.warn( + log.warning( "Hessian implementation is not available for this backend, " "continuing with the approximate method." ) @@ -520,6 +529,7 @@ def sigma_mu( muhatA, min_nllA = self.maximize_asimov_likelihood( expected=expected, test_statistics=test_statistics, **kwargs ) + log.debug(f"muhatA: {muhatA}, min_nllA: {min_nllA}") def logpdf_asimov(mu: Union[float, np.ndarray]) -> float: return -self.asimov_likelihood( @@ -726,10 +736,9 @@ def maximize_likelihood( if expected in [ExpectationType.aposteriori, ExpectationType.apriori]: fit = "post" if expected == ExpectationType.aposteriori else "pre" - warnings.warn( - message="chi-square calculator does not support expected p-values." - + f" Only one p-value for {fit}fit will be returned.", - category=RuntimeWarning, + log.warning( + "chi-square calculator does not support expected p-values." + f" Only one p-value for {fit}fit will be returned." ) if expected == "all": @@ -909,6 +918,7 @@ def poi_upper_limit( ) low_init = low_init or muhat + 1.5 * sigma_mu hig_init = hig_init or muhat + 2.5 * sigma_mu + log.debug(f"new low_init = {low_init}, new hig_init = {hig_init}") return find_poi_upper_limit( maximum_likelihood=maximum_likelihood, From a004a10e473a978d0cfef07f233d1bce16774234 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 14:59:31 -0500 Subject: [PATCH 23/31] remove unused imports --- src/spey/base/hypotest_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spey/base/hypotest_base.py b/src/spey/base/hypotest_base.py index 73794ca..6e19bee 100644 --- a/src/spey/base/hypotest_base.py +++ b/src/spey/base/hypotest_base.py @@ -4,7 +4,6 @@ """ import logging -import warnings from abc import ABC, abstractmethod from functools import partial from typing import Any, Callable, Dict, List, Literal, Optional, Text, Tuple, Union From 6264870c56239d4e95735874975b1ff9a714f6a8 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 15:46:12 -0500 Subject: [PATCH 24/31] update docstring --- src/spey/math.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/spey/math.py b/src/spey/math.py index ab47436..7eecc87 100644 --- a/src/spey/math.py +++ b/src/spey/math.py @@ -36,11 +36,11 @@ def value_and_grad( * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit prescription which means that the SM will be assumed to be the truth. - data (``np.ndarray``, default ``None``): input data that to fit. If `None` observed + data (``List[float]``, default ``None``): input data that to fit. If `None` observed data will be used. Returns: - ``Callable[[np.ndarray], np.ndarray, np.ndarray]``: + ``Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]``: negative log-likelihood and its gradient with respect to nuisance parameters """ val_and_grad = statistical_model.backend.get_objective_function( @@ -75,7 +75,7 @@ def hessian( * :obj:`~spey.ExpectationType.apriori`: Computes the expected p-values with via pre-fit prescription which means that the SM will be assumed to be the truth. - data (``np.ndarray``, default ``None``): input data that to fit. If `None` observed + data (``List[float]``, default ``None``): input data that to fit. If `None` observed data will be used. Returns: From 9bba9d8b49b3a2f33f51ad3cbc734f79263cbf81 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 16:13:31 -0500 Subject: [PATCH 25/31] extend tutorials --- docs/tutorials.rst | 1 + docs/tutorials/gradients.md | 95 +++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 docs/tutorials/gradients.md diff --git a/docs/tutorials.rst b/docs/tutorials.rst index f703609..a851786 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -7,5 +7,6 @@ Kitchen Sink tutorials/intro_spey tutorials/functional_tuto tutorials/histogram + tutorials/gradients tuto_plugin Introduction to Spey (PyHEP 2023) \ No newline at end of file diff --git a/docs/tutorials/gradients.md b/docs/tutorials/gradients.md new file mode 100644 index 0000000..d1d4d05 --- /dev/null +++ b/docs/tutorials/gradients.md @@ -0,0 +1,95 @@ +--- +myst: + html_meta: + "property=og:title": "Gradient of a Statistical Model" + "property=og:description": "Modules to compute gradient and Hessian of negative log-probabilities" + "property=og:image": "https://spey.readthedocs.io/en/main/_static/spey-logo.png" +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.12 + jupytext_version: 1.8.2 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Gradient of a Statistical Model + +````{margin} +```{note} +In previous versions gradient and Hessian was limited to internal computations only. +``` +```` + +With version 0.1.6, Spey includes additional functionalities to extract gradient and Hessian information directly from the statistical model. The gradient and Hessian are defined as follows + +$$ +{\rm Gradient} = -\frac{d\log\mathcal{L}(\theta)}{d\theta}\quad , \quad {\rm Hessian} = -\frac{d^2\log\mathcal{L}(\theta)}{d\theta_i d\theta_j}\quad , \quad \mu ,\theta_i \in \theta \ . +$$ + +In order to access this information we will use `spey.math` module. + +```{code-cell} ipython3 +:tags: [hide-cell] +import spey +from spey.math import value_and_grad, hessian +import numpy as np +np.random.seed(14) +``` + +{py:func}`spey.math.value_and_grad` returns a function that computes negative log-likelihood and its gradient for a given statistical model and {py:func}`spey.math.hessian` returns a function that computes Hessian of negative log-likelihood. + +Let us examine this on ``"default_pdf.uncorrelated_background"``: + +```{code-cell} ipython3 +pdf_wrapper = spey.get_backend("default_pdf.uncorrelated_background") + +data = [36, 33] +signal_yields = [12.0, 15.0] +background_yields = [50.0, 48.0] +background_unc = [12.0, 16.0] + +stat_model = pdf_wrapper( + signal_yields=signal_yields, + background_yields=background_yields, + data=data, + absolute_uncertainties=background_unc, +) +``` + +Here we constructed a two-bin statistical model with observations $36,\ 33$, signal yields $12,\ 15$ and background yields $50\pm12,\ 48\pm16$. We can construct the function that will return negative log probability and its gradient as follows + +```{code-cell} ipython3 +neg_logprob = value_and_grad(stat_model) +``` + +Notice that this function constructs a negative log-probability for the observed statistical model using the default data that we provided earlier. This can be changed using ``expected`` and ``data`` keywords. Now we can choose nuisance parameters and execute the function: + +```{code-cell} ipython3 +nui = np.random.uniform(0,1,(2,)) +neg_logprob(nui) +``` + +```python +(26.928732030439747, array([13.12818756, 15.18979232])) +``` + +For this particular model, we have only two nuisance parameters due to the structure of the statistical model. For Hessian, we can use the same formulation: + +```{code-cell} ipython3 +hess = hessian(stat_model) +hess(nui) +``` + +```python +array([[2.81233272, 2.91913211], + [2.91913211, 5.03305146]]) +``` + +```{attention} +This functionality is only available for the statistical models that defined as differentiable. +``` From 9922f6f17441a0faba5ddf6085dc747de05042f7 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 16:15:25 -0500 Subject: [PATCH 26/31] remove wf --- .github/workflows/black.yml | 43 ------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 .github/workflows/black.yml diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml deleted file mode 100644 index d43f094..0000000 --- a/.github/workflows/black.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Lint - -on: - push: - branches: - - release/* - # Each pull request should be validated before merging with main or dev - pull_request: - branches: - - main - - dev - # Enables manual action execution. - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: write - pages: write - id-token: write - -jobs: - lint: - name: runner / black - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: psf/black@stable - id: action_black - with: - options: "-l 100" - src: "./src" - # - name: Create Pull Request - # if: steps.action_black.outputs.is_formatted == 'true' - # uses: peter-evans/create-pull-request@v3 - # with: - # token: ${{ secrets.GITHUB_TOKEN }} - # title: "Format Python code with psf/black push" - # commit-message: ":art: Format Python code with psf/black" - # body: | - # There appear to be some python formatting errors in ${{ github.sha }}. This pull request - # uses the [psf/black](https://github.com/psf/black) formatter to fix these issues. - # base: ${{ github.head_ref }} # Creates pull request onto pull request or commit branch - # branch: actions/black From 6790c00d4274aa09a20ce6f558c0dbf8d83b8b79 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 16:23:23 -0500 Subject: [PATCH 27/31] update changelog --- docs/releases/changelog-v0.1.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/releases/changelog-v0.1.md b/docs/releases/changelog-v0.1.md index d0cdc77..932a17c 100644 --- a/docs/releases/changelog-v0.1.md +++ b/docs/releases/changelog-v0.1.md @@ -1,15 +1,14 @@ # Release notes v0.1 -## New features since last release +## New features since the last release -* Exclusion limit calculator has been extended to include p-value computation - from chi-square. +* The exclusion limit calculator has been extended to include p-value computation from chi-square. ([#17](https://github.com/SpeysideHEP/spey/pull/17)) * $\chi^2$ function has been extended to compute background + signal vs background only model. ([#17](https://github.com/SpeysideHEP/spey/pull/17)) -* Poisson based likelihood constructor without uncertainties has been added +* A Poisson-based likelihood constructor without uncertainties has been added (Request by Veronica Sanz for EFT studies). ([#22](https://github.com/SpeysideHEP/spey/pull/22)) @@ -25,16 +24,16 @@ before approximating through $q_{\mu,A}$. [#25](https://github.com/SpeysideHEP/spey/pull/25) -* Update clarification on text based keyword arguments. +* Update clarification on text-based keyword arguments. ([#30](https://github.com/SpeysideHEP/spey/pull/30)) -* Adding logging across the software and implement tools to scilence them. +* Adding logging across the software and implementing tools to silence them. ([#32](https://github.com/SpeysideHEP/spey/pull/32)) * Spey will automatically look for updates during initiation. ([#32](https://github.com/SpeysideHEP/spey/pull/32)) -* Utilities to retreive bibtex information for third-party plug-ins. +* Utilities to retrieve BibTeX information for third-party plug-ins. ([#32](https://github.com/SpeysideHEP/spey/pull/32)) * Add math utilities for users to extract gradient and hessian of negative log-likelihood @@ -45,14 +44,14 @@ ## Bug Fixes -* In accordance to the latest updates ```UnCorrStatisticsCombiner``` has been updated with - chi-square computer. See issue [#19](https://github.com/SpeysideHEP/spey/issues/19). +* In accordance with the latest updates, `UnCorrStatisticsCombiner` has been updated with + a chi-square computer. See issue [#19](https://github.com/SpeysideHEP/spey/issues/19). ([#20](https://github.com/SpeysideHEP/spey/pull/20)) * Execution error fix during likelihood computation for models with single nuisance parameter. ([#22](https://github.com/SpeysideHEP/spey/pull/22)) -* Numeric problem rising from `==` which has been updated to `np.isclose` +* The numeric problem rising from `==` which has been updated to `np.isclose` see issue [#23](https://github.com/SpeysideHEP/spey/issues/23). ([#25](https://github.com/SpeysideHEP/spey/pull/25)) From d030903cf9c97a6e72d38167eeb3966453a033c0 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 16:24:11 -0500 Subject: [PATCH 28/31] update changelog --- docs/releases/changelog-v0.1.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/releases/changelog-v0.1.md b/docs/releases/changelog-v0.1.md index 932a17c..e276212 100644 --- a/docs/releases/changelog-v0.1.md +++ b/docs/releases/changelog-v0.1.md @@ -42,6 +42,9 @@ * Improve gradient execution for `default_pdf`. ([#31](https://github.com/SpeysideHEP/spey/pull/31)) +* Add more tests for code coverage. + ([#33](https://github.com/SpeysideHEP/spey/pull/33)) + ## Bug Fixes * In accordance with the latest updates, `UnCorrStatisticsCombiner` has been updated with From 0b03ba663cf7eef676dba02aa49f56e3ade2ea9d Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 19:41:17 -0500 Subject: [PATCH 29/31] update changelog --- docs/releases/changelog-v0.1.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/releases/changelog-v0.1.md b/docs/releases/changelog-v0.1.md index e276212..99cfa4a 100644 --- a/docs/releases/changelog-v0.1.md +++ b/docs/releases/changelog-v0.1.md @@ -1,5 +1,7 @@ # Release notes v0.1 +Specific upgrades for the latest release can be found [here](https://github.com/SpeysideHEP/spey/releases/latest). + ## New features since the last release * The exclusion limit calculator has been extended to include p-value computation from chi-square. From 0d092797c6f3b8cc71545de7bde68073446dee99 Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 21:21:21 -0500 Subject: [PATCH 30/31] update gradients.md --- docs/tutorials/gradients.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/tutorials/gradients.md b/docs/tutorials/gradients.md index d1d4d05..9576d36 100644 --- a/docs/tutorials/gradients.md +++ b/docs/tutorials/gradients.md @@ -89,7 +89,3 @@ hess(nui) array([[2.81233272, 2.91913211], [2.91913211, 5.03305146]]) ``` - -```{attention} -This functionality is only available for the statistical models that defined as differentiable. -``` From b5c786ac551219f531c9614f5acd14159dad078b Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Sun, 28 Jan 2024 21:29:07 -0500 Subject: [PATCH 31/31] typo fix --- docs/tutorials/gradients.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/gradients.md b/docs/tutorials/gradients.md index 9576d36..4b11b06 100644 --- a/docs/tutorials/gradients.md +++ b/docs/tutorials/gradients.md @@ -70,15 +70,15 @@ neg_logprob = value_and_grad(stat_model) Notice that this function constructs a negative log-probability for the observed statistical model using the default data that we provided earlier. This can be changed using ``expected`` and ``data`` keywords. Now we can choose nuisance parameters and execute the function: ```{code-cell} ipython3 -nui = np.random.uniform(0,1,(2,)) +nui = np.random.uniform(0,1,(3,)) neg_logprob(nui) ``` ```python -(26.928732030439747, array([13.12818756, 15.18979232])) +(27.81902589793928, array([13.29067478, 6.17223275, 9.28814191])) ``` -For this particular model, we have only two nuisance parameters due to the structure of the statistical model. For Hessian, we can use the same formulation: +For this particular model, we have only two nuisance parameters, $\theta_i$, and signal strength, $\mu$, due to the structure of the statistical model. For Hessian, we can use the same formulation: ```{code-cell} ipython3 hess = hessian(stat_model) @@ -86,6 +86,7 @@ hess(nui) ``` ```python -array([[2.81233272, 2.91913211], - [2.91913211, 5.03305146]]) +array([[ 2.74153126, 1.21034187, 1.63326868], + [ 1.21034187, 2.21034187, -0. ], + [ 1.63326868, -0. , 2.74215326]]) ```