From d2f02683c0a1afa3019e4fdf4f6770e3a20733fd Mon Sep 17 00:00:00 2001 From: "Jack Y. Araz" Date: Thu, 12 Dec 2024 15:42:17 -0500 Subject: [PATCH] add precommit --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 +- .github/ISSUE_TEMPLATE/question.yml | 6 +- .github/workflows/docs.yml | 6 +- .github/workflows/pre-commit.yaml | 17 ++++ .pre-commit-config.yaml | 34 +++++++ .readthedocs.yaml | 2 +- .zenodo.json | 2 +- CITATIONS.bib | 2 +- Makefile | 5 -- data/path_finder/example_data.json | 2 +- docs/Makefile | 2 +- docs/_static/css/custom.css | 2 +- docs/api.rst | 14 +-- docs/bib/references.bib | 2 +- docs/bibliography.rst | 2 +- docs/citations.rst | 6 +- docs/comb.rst | 40 ++++----- docs/contributing.rst | 6 +- docs/index.rst | 2 +- docs/known_issues.rst | 2 +- docs/new_plugin.rst | 82 ++++++++--------- docs/plugins.rst | 88 +++++++++---------- docs/quick_start.rst | 60 ++++++------- docs/release_notes.rst | 2 +- docs/requirements.txt | 2 +- docs/tuto_plugin.rst | 2 +- docs/tutorials.rst | 2 +- setup.py | 4 +- src/spey/__init__.py | 16 ++-- src/spey/backends/default_pdf/__init__.py | 58 ++++++------ src/spey/backends/default_pdf/simple_pdf.py | 78 ++++++++++++---- .../default_pdf/uncertainty_synthesizer.py | 6 +- src/spey/backends/distributions.py | 4 +- src/spey/base/backend_base.py | 6 +- src/spey/base/hypotest_base.py | 4 +- src/spey/base/model_config.py | 4 +- src/spey/base/recorder.py | 17 +++- .../uncorrelated_statistics_combiner.py | 32 +++---- .../asymptotic_calculator.py | 22 ++--- .../hypothesis_testing/test_statistics.py | 6 +- src/spey/hypothesis_testing/toy_calculator.py | 2 +- src/spey/hypothesis_testing/utils.py | 18 ++-- src/spey/interface/__init__.py | 2 +- src/spey/interface/statistical_model.py | 16 ++-- src/spey/system/webutils.py | 4 +- 45 files changed, 395 insertions(+), 299 deletions(-) create mode 100644 .github/workflows/pre-commit.yaml create mode 100644 .pre-commit-config.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 95e566d..2b13d0b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -9,7 +9,7 @@ body: value: | ## Before posting a feature request Search existing [GitHub issues](https://github.com/SpeysideHEP/spey/issues) to make sure the issue does not already exist. - + * **Please use [Markdown syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).** - type: textarea id: system @@ -63,4 +63,3 @@ body: options: - label: I have searched existing GitHub issues to make sure the issue does not already exist. required: true - diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index b83005d..e950439 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -10,9 +10,9 @@ body: ## Before posting a question Search existing [GitHub issues](https://github.com/SpeysideHEP/spey/issues) to make sure the issue does not already exist. - + If your question involves software issues, please include your system settings as shown below; - + **System Settings:** Please copy and paste the output of `spey.about()` function. If you are working on a specific branch please add the name of the branch and last commit ID. The abbreviated commit ID can be found via `git log -n1 --format="%h"` command. To retreive the branch name please use `git rev-parse --abbrev-ref HEAD` command.) - type: textarea @@ -21,7 +21,7 @@ body: label: Question description: | How can we help? - + * **Please use [Markdown syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax).** validations: required: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ab8edc1..3ae6c3b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,7 +33,7 @@ jobs: uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - + - name: Install dependencies run: | python -m pip install --upgrade pip @@ -44,7 +44,7 @@ jobs: - name: Compile Docs run: | cd docs; make clean; make html; cd .. - + - name: Fix permissions if needed run: | chmod -c -R +rX "docs/_build/html/" | while read line; do @@ -77,4 +77,4 @@ jobs: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v2 \ No newline at end of file + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..779b699 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,17 @@ +on: + pull_request: + branches: ["main", "releases/**"] + push: + branches: ["main", "releases/**", "testing"] + +name: Check if pre-commit checks pass + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.8" + - uses: pre-commit/action@v3.0.1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1a94238 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + args: ["--maxkb=1024"] + - id: check-ast + - id: check-docstring-first + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + - id: forbid-submodules + + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [--py38-plus] + language_version: python3.8 + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-directive-colons + - id: rst-inline-touching-normal + + - repo: https://github.com/psf/black + rev: 22.6.0 + hooks: + - id: black + args: ["--line-length=90", "--target-version=py38"] + language_version: python3.8 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b6c7111..a01c286 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -23,4 +23,4 @@ python: install: - requirements: docs/requirements.txt - method: pip - path: . \ No newline at end of file + path: . diff --git a/.zenodo.json b/.zenodo.json index ff7d2f0..4d4fab6 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -40,4 +40,4 @@ "relation": "isDocumentedBy" } ] -} \ No newline at end of file +} diff --git a/CITATIONS.bib b/CITATIONS.bib index 07e5b5a..567cd00 100644 --- a/CITATIONS.bib +++ b/CITATIONS.bib @@ -22,4 +22,4 @@ @software{spey_zenodo version = {v0.1.11}, doi = {10.5281/zenodo.14071351}, url = {https://doi.org/10.5281/zenodo.14071351} -} \ No newline at end of file +} diff --git a/Makefile b/Makefile index 68e8882..fe5a1ee 100755 --- a/Makefile +++ b/Makefile @@ -31,8 +31,3 @@ testpypi: .PHONY: pypi pypi: python3 -m twine upload -r pypi dist/* - - - - - diff --git a/data/path_finder/example_data.json b/data/path_finder/example_data.json index e856dc5..162b263 100644 --- a/data/path_finder/example_data.json +++ b/data/path_finder/example_data.json @@ -2942,4 +2942,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/docs/Makefile b/docs/Makefile index 5fff4a6..ebe3e3d 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,4 +1,4 @@ -# Makefile for Sphinx documentation +# Makefile for Sphinx documentation SPHINXOPTS = -W --keep-going #-n SPHINXBUILD = sphinx-build diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 800f69d..aaab949 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -45,4 +45,4 @@ span#release { width: 200px; margin-right: 50px; line-height: 22px; -} \ No newline at end of file +} diff --git a/docs/api.rst b/docs/api.rst index 8fb04c8..42231d2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -11,7 +11,7 @@ Top-Level .. currentmodule:: spey -.. autosummary:: +.. autosummary:: :toctree: _generated/ version @@ -68,7 +68,7 @@ Hypothesis testing .. currentmodule:: spey.hypothesis_testing -.. autosummary:: +.. autosummary:: :toctree: _generated/ test_statistics.qmu @@ -91,7 +91,7 @@ Gradient Tools .. currentmodule:: spey.math -.. autosummary:: +.. autosummary:: :toctree: _generated/ value_and_grad @@ -105,7 +105,7 @@ Distributions .. currentmodule:: spey.backends.distributions -.. autosummary:: +.. autosummary:: :toctree: _generated/ Poisson @@ -119,7 +119,7 @@ Default PDFs .. currentmodule:: spey.backends.default_pdf -.. autosummary:: +.. autosummary:: :toctree: _generated/ DefaultPDFBase @@ -133,7 +133,7 @@ Default PDFs Simple PDFs ~~~~~~~~~~~ -.. autosummary:: +.. autosummary:: :toctree: _generated/ simple_pdf.Poisson @@ -146,7 +146,7 @@ Exceptions .. currentmodule:: spey.system.exceptions -.. autosummary:: +.. autosummary:: :toctree: _generated/ :nosignatures: diff --git a/docs/bib/references.bib b/docs/bib/references.bib index 0ae5afd..9437740 100644 --- a/docs/bib/references.bib +++ b/docs/bib/references.bib @@ -193,4 +193,4 @@ @article{Araz:2023bwx reportnumber = {IPPP/23/34}, month = {7}, year = {2023} -} \ No newline at end of file +} diff --git a/docs/bibliography.rst b/docs/bibliography.rst index 849ea63..9e91408 100644 --- a/docs/bibliography.rst +++ b/docs/bibliography.rst @@ -1,4 +1,4 @@ Bibliography ============ -.. bibliography:: bib/references.bib \ No newline at end of file +.. bibliography:: bib/references.bib diff --git a/docs/citations.rst b/docs/citations.rst index 5ed2d6f..1dfd84d 100644 --- a/docs/citations.rst +++ b/docs/citations.rst @@ -11,8 +11,8 @@ The BibTeX entry for citing ``spey`` including latest zenodo archive: .. note:: To retrieve the latest BibTeX information one can also use :func:`~spey.cite` function. - This function pulls the publication information from Inspire-HEP and uses the repository - identification to pull the latest Zenodo archive information. + This function pulls the publication information from Inspire-HEP and uses the repository + identification to pull the latest Zenodo archive information. .. code-block:: bibtex @@ -40,4 +40,4 @@ An up-to-date list can be found on `Inspire-HEP `_ +In this section, we will provide a simple statistical model combination example using +`Path Finder algorithm `_ (For details, see :cite:`Araz:2022vtr`). The data, necessary to complete this exercise, has been provided under the ``data/path_finder`` folder of `spey's GitHub repository `_. Here, one will find ``example_data.json`` -and ``overlap_matrix.csv`` files. Both files are generated using MadAnalysis 5 recast of ATLAS-SUSY-2018-31 -:cite:`ATLAS:2019gdh, DVN/IHALED_2020, Araz:2020stn` +and ``overlap_matrix.csv`` files. Both files are generated using MadAnalysis 5 recast of ATLAS-SUSY-2018-31 +:cite:`ATLAS:2019gdh, DVN/IHALED_2020, Araz:2020stn` and CMS-SUS-19-006 :cite:`CMS:2019zmd, Mrowietz:2020ztq` analyses. * ``example_data.json``: Includes cross section and signal, background, and observed yield information for this example. * ``overlap_matrix.csv``: Includes overlap matrix that the PathFinder algorithm needs to find the best combination. -Let us first import all the necessary packages and construct the data (please add the Pathfinder path to +Let us first import all the necessary packages and construct the data (please add the Pathfinder path to ``sys.path`` list if needed) .. code-block:: python3 @@ -35,12 +35,12 @@ Let us first import all the necessary packages and construct the data (please ad >>> with open("example_data.json", "r") as f: >>> example_data = json.load(f) - + >>> models = {} >>> # loop overall data >>> for data in example_data["data"]: >>> pdf_wrapper = spey.get_backend("default_pdf.uncorrelated_background") - + >>> stat_model = pdf_wrapper( ... signal_yields=data["signal_yields"], ... background_yields=data["background_yields"], @@ -49,23 +49,23 @@ Let us first import all the necessary packages and construct the data (please ad ... analysis=data["region"], ... xsection=example_data["xsec"], ... ) - + >>> llhr = stat_model.chi2( ... poi_test=1.0, poi_test_denominator=0.0, expected=spey.ExpectationType.apriori ... ) / 2.0 - + >>> models.update({data["region"]: {"stat_model": stat_model, "llhr": llhr}}) -``example_data`` has two main section which are ``"data"`` including all the information about regions +``example_data`` has two main section which are ``"data"`` including all the information about regions and ``"xsec"`` including cross section value in pb. Using the information provided for each region we construct an uncorrelated background-based statistical model. ``llhr`` is the log-likelihood ratio of signal+background and background-only statistical models given as -.. math:: +.. math:: {\rm llhr} = -\log\frac{\mathcal{L}(1,\theta_1)}{\mathcal{L}(0,\theta_0)}\ . -Finally, the dictionary called ``models`` is just a container to collect all the models. In the next, let us +Finally, the dictionary called ``models`` is just a container to collect all the models. In the next, let us construct a Binary acceptance matrix and compute the best possible paths .. code-block:: python3 @@ -80,7 +80,7 @@ construct a Binary acceptance matrix and compute the best possible paths >>> plot_results.plot(bam, whdfs) In the first three lines, we read the overlap matrix, extracted the corresponding weights (``llhr``), and fed these -into the ``pf.BinaryAcceptance`` function. We use the ``WHDFS`` algorithm to compute the top 5 combinations and plot the +into the ``pf.BinaryAcceptance`` function. We use the ``WHDFS`` algorithm to compute the top 5 combinations and plot the resulting binary acceptance matrix with the paths. .. image:: ./figs/bam.png @@ -90,11 +90,11 @@ resulting binary acceptance matrix with the paths. Each column and row corresponds to ``overlap_matrix.columns``, and the coloured lines are the chosen paths where the best path can be seen via ``whdfs.best.path``. In this case we find ``"atlas_susy_2018_31::SRA_H"``, -``"cms_sus_19_006::SR25_Njet23_Nb2_HT6001200_MHT350600"`` and ``'cms_sus_19_006::AGGSR7_Njet2_Nb2_HT600_MHT600'`` -regions as best regions to be combined. For the combination, we will use :obj:`~spey.UnCorrStatisticsCombiner` +``"cms_sus_19_006::SR25_Njet23_Nb2_HT6001200_MHT350600"`` and ``'cms_sus_19_006::AGGSR7_Njet2_Nb2_HT600_MHT600'`` +regions as best regions to be combined. For the combination, we will use :obj:`~spey.UnCorrStatisticsCombiner` and feed the statistical models as input. -.. code-block:: +.. code-block:: >>> regions = [ ... "atlas_susy_2018_31::SRA_H", @@ -105,7 +105,7 @@ and feed the statistical models as input. >>> combined.exclusion_confidence_level(expected=spey.ExpectationType.aposteriori)[2] >>> # 0.9858284831278277 -.. note:: +.. note:: :obj:`~spey.UnCorrStatisticsCombiner` can be used for any backend retrieved via :func:`spey.get_backend` function, which wraps the likelihood prescription with :obj:`~spey.StatisticalModel`. @@ -117,7 +117,7 @@ the stack, which can be found via .. code-block:: python3 >>> poiUL = np.array([models[reg]["stat_model"].poi_upper_limit(expected=spey.ExpectationType.aposteriori) for reg in models.keys()]) - + In our case, the minimum value that we found was from ``"atlas_susy_2018_31::SRA_H"`` where the expected exclusion limit can be computed via @@ -127,7 +127,7 @@ limit can be computed via >>> models["atlas_susy_2018_31::SRA_H"]["stat_model"].exclusion_confidence_level(expected=spey.ExpectationType.aposteriori)[2] >>> # 0.9445409288935508 -Finally, we can compare the likelihood distribution of the two +Finally, we can compare the likelihood distribution of the two .. code-block:: python3 :linenos: @@ -154,7 +154,7 @@ which gives us the following result: :scale: 20 :alt: Binary Acceptance Matrix -.. attention:: +.. attention:: The results can vary between scipy versions and the versions of its compilers due to their effect on optimisation algorithm. diff --git a/docs/contributing.rst b/docs/contributing.rst index e275149..c168d31 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -8,7 +8,7 @@ To get started, fork the ``main`` repository. For anything beyond minimal fixes that do not require discussion, please first `open an issue `_ to discuss your request with the development team. -If your proposed changes are more extensive than a few lines of code, please create a draft pull request. +If your proposed changes are more extensive than a few lines of code, please create a draft pull request. This draft should include the context of the change, a description, and the benefits of the implementation. * For changes within the Python interface of the program, please run standard tests and write additional tests if necessary. @@ -66,5 +66,5 @@ Throughout the code, the following documentation style has been employed {{descriptionPlaceholder}} {{/returns}} -This code can directly be imported into ``custom.mustache`` file and used within -vscode as an auto docstring generator. \ No newline at end of file +This code can directly be imported into ``custom.mustache`` file and used within +vscode as an auto docstring generator. diff --git a/docs/index.rst b/docs/index.rst index 5f5ea83..c611365 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,4 +45,4 @@ Welcome to the documentation of Spey * :ref:`genindex` -The Spey logo is generated by `DALL-E 3 `_. \ No newline at end of file +The Spey logo is generated by `DALL-E 3 `_. diff --git a/docs/known_issues.rst b/docs/known_issues.rst index 2ddec87..59562c3 100644 --- a/docs/known_issues.rst +++ b/docs/known_issues.rst @@ -3,4 +3,4 @@ Known Issues * It has been observed that the Scipy version and its compiler, which is used for maximising and profiling the likelihood can have a slight effect on the results. -* NumPy v2.0.0 does not work with autograd, which is used in "default_pdf.XXX" likelihoods. The dependencies are reformulated accordingly. \ No newline at end of file +* NumPy v2.0.0 does not work with autograd, which is used in "default_pdf.XXX" likelihoods. The dependencies are reformulated accordingly. diff --git a/docs/new_plugin.rst b/docs/new_plugin.rst index 5cc36c7..093a661 100644 --- a/docs/new_plugin.rst +++ b/docs/new_plugin.rst @@ -9,7 +9,7 @@ Building a plugin :property=og:image: https://spey.readthedocs.io/en/main/_static/spey-logo.png :property=og:url: https://spey.readthedocs.io/en/main/new_plugin.html -``spey`` package has been designed to be expandable. It only needs to know certain aspects of the +``spey`` package has been designed to be expandable. It only needs to know certain aspects of the data structure that is presented and a prescription to form a likelihood function. What a plugin provides @@ -17,20 +17,20 @@ What a plugin provides A quick intro on the terminology of spey plugins in this section: - * A plugin is an external Python package that provides additional statistical model + * A plugin is an external Python package that provides additional statistical model prescriptions to spey. - * Each plugin may provide one (or more) statistical model prescriptions + * Each plugin may provide one (or more) statistical model prescriptions accessible directly through Spey. - * Depending on the scope of the plugin, you may wish to provide additional (custom) + * Depending on the scope of the plugin, you may wish to provide additional (custom) operations and differentiability through various autodif packages such as ``autograd`` or ``jax``. As long as they are implemented through predefined function names, - Spey can automatically detect and use them within the interface. + Spey can automatically detect and use them within the interface. Creating your Statistical Model Prescription -------------------------------------------- -The first step in creating your Spey plugin is to create your statistical model interface. -This is as simple as importing abstract base class :class:`~spey.BackendBase` from spey and +The first step in creating your Spey plugin is to create your statistical model interface. +This is as simple as importing abstract base class :class:`~spey.BackendBase` from spey and inheriting it. The most basic implementation of a statistical model can be found below; .. _MyStatisticalModel: @@ -61,59 +61,59 @@ inheriting it. The most basic implementation of a statistical model can be found ... self, expected = spey.ExpectationType.observed, data = None ... ): >>> ... - + >>> def expected_data(self, pars): >>> ... -:class:`~spey.BackendBase` requires certain functionality from the statistical model to be -implemented, but let us first go through the above class structure. Spey looks for specific -metadata to track the implementation's version, author and name. Additionally, +:class:`~spey.BackendBase` requires certain functionality from the statistical model to be +implemented, but let us first go through the above class structure. Spey looks for specific +metadata to track the implementation's version, author and name. Additionally, it checks compatibility with the current Spey version to ensure that the plugin works as it should. -.. note:: +.. note:: The list of metadata that Spey is looking for: * **name** (``str``): Name of the plugin. * **version** (``str``): Version of the plugin. * **author** (``str``): Author of the plugin. - * **spey_requires** (``str``): The minimum spey version that the + * **spey_requires** (``str``): The minimum spey version that the plugin needs, e.g. ``spey_requires="0.0.1"`` or ``spey_requires=">=0.3.3"``. * **doi** (``List[str]``): Citable DOI numbers for the plugin. * **arXiv** (``List[str]``): arXiv numbers for the plugin. -`MyStatisticalModel`_ class has four main functionalities namely :func:`~spey.BackendBase.is_alive`, -:func:`~spey.BackendBase.config`, :func:`~spey.BackendBase.get_logpdf_func`, and -:func:`~spey.BackendBase.expected_data`(for detailed descriptions of these functions please go to the +`MyStatisticalModel`_ class has four main functionalities namely :func:`~spey.BackendBase.is_alive`, +:func:`~spey.BackendBase.config`, :func:`~spey.BackendBase.get_logpdf_func`, and +:func:`~spey.BackendBase.expected_data`(for detailed descriptions of these functions please go to the :class:`~spey.BackendBase` documentation by clicking on them.) -* :func:`~spey.BackendBase.is_alive`: This function returns a boolean indicating that the statistical model +* :func:`~spey.BackendBase.is_alive`: This function returns a boolean indicating that the statistical model has at least one signal bin with a non-zero yield. * :func:`~spey.BackendBase.config`: This function returns :class:`~spey.base.model_config.ModelConfig` class - which includes certain information about the model structure, such as the index of the parameter of interest - within the parameter list (:attr:`~spey.base.model_config.ModelConfig.poi_index`), minimum value parameter + which includes certain information about the model structure, such as the index of the parameter of interest + within the parameter list (:attr:`~spey.base.model_config.ModelConfig.poi_index`), minimum value parameter of interest can take (:attr:`~spey.base.model_config.ModelConfig.minimum_poi`), suggested initialisation - parameters for the optimiser (:attr:`~spey.base.model_config.ModelConfig.suggested_init`) and suggested - bounds for the parameters (:attr:`~spey.base.model_config.ModelConfig.suggested_bounds`). If - ``allow_negative_signal=True`` the lower bound of POI is expected to be zero; if ``False`` - :attr:`~spey.base.model_config.ModelConfig.minimum_poi`. ``poi_upper_bound`` is used to enforce an upper + parameters for the optimiser (:attr:`~spey.base.model_config.ModelConfig.suggested_init`) and suggested + bounds for the parameters (:attr:`~spey.base.model_config.ModelConfig.suggested_bounds`). If + ``allow_negative_signal=True`` the lower bound of POI is expected to be zero; if ``False`` + :attr:`~spey.base.model_config.ModelConfig.minimum_poi`. ``poi_upper_bound`` is used to enforce an upper bound on POI. - .. note:: + .. note:: - Suggested bounds and initialisation values should return a list with a length of the number of nuisance - parameters and parameters of interest. Initialisation values should be a type of ``List[float, ...]`` + Suggested bounds and initialisation values should return a list with a length of the number of nuisance + parameters and parameters of interest. Initialisation values should be a type of ``List[float, ...]`` and bounds should have the type of ``List[Tuple[float, float], ...]``. -* :func:`~spey.BackendBase.get_logpdf_func`: This function returns a function that takes a NumPy array +* :func:`~spey.BackendBase.get_logpdf_func`: This function returns a function that takes a NumPy array as an input which indicates the fit parameters (nuisance, :math:`\theta`, and POI, :math:`\mu`) and returns the - value of the natural logarithm of the likelihood function, :math:`\log\mathcal{L}(\mu, \theta)`. The input - ``expected`` defines which data to be used in the absence of ``data`` input, i.e. if - ``expected=spey.ExpectationType.observed`` yields of observed data should be used to compute the likelihood, but - if ``expected=spey.ExpectationType.apriori`` background yields should be used. This ensures the difference between - prefit and postfit likelihoods. If ``data`` is provided, it is overwritten; this is for the case where Asimov + value of the natural logarithm of the likelihood function, :math:`\log\mathcal{L}(\mu, \theta)`. The input + ``expected`` defines which data to be used in the absence of ``data`` input, i.e. if + ``expected=spey.ExpectationType.observed`` yields of observed data should be used to compute the likelihood, but + if ``expected=spey.ExpectationType.apriori`` background yields should be used. This ensures the difference between + prefit and postfit likelihoods. If ``data`` is provided, it is overwritten; this is for the case where Asimov data is in use. * :func:`~spey.BackendBase.expected_data` (optional): This function is crutial for **asymptotic** hypothesis testing. @@ -123,24 +123,24 @@ it checks compatibility with the current Spey version to ensure that the plugin Other available functions that can be implemented are shown in the table below. -.. list-table:: +.. list-table:: :header-rows: 1 - + * - Functions and Properties - Explanation * - :func:`~spey.BackendBase.get_objective_function` - Returns the objective function and/or its gradient. - * - :func:`~spey.BackendBase.get_hessian_logpdf_func` + * - :func:`~spey.BackendBase.get_hessian_logpdf_func` - Returns Hessian of the log-probability - * - :func:`~spey.BackendBase.get_sampler` + * - :func:`~spey.BackendBase.get_sampler` - Returns a function to sample from the likelihood distribution. -.. attention:: - +.. attention:: + A simple example implementation can be found in the `example-plugin repository `_ which implements - .. math:: + .. math:: \mathcal{L}(\mu) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i) @@ -149,7 +149,7 @@ Other available functions that can be implemented are shown in the table below. Identifying and installing your statistical model ------------------------------------------------- -In order to add your brand new statistical model to the spey interface, all you need to do is to create a ``setup.py`` file, +In order to add your brand new statistical model to the spey interface, all you need to do is to create a ``setup.py`` file, which will create an entry point for the statistical model class. So let us assume that you have the following folder structure .. code-block:: bash @@ -194,4 +194,4 @@ arXiv number to be used in academic publications. This information can be access ... # 'version': '1.0.0', ... # 'spey_requires': '>=0.1.0,<0.2.0', ... # 'doi': [], - ... # 'arXiv': []} \ No newline at end of file + ... # 'arXiv': []} diff --git a/docs/plugins.rst b/docs/plugins.rst index c1911a0..7433d79 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -35,11 +35,11 @@ Plug-ins :property=og:image: https://spey.readthedocs.io/en/main/_static/spey-logo.png :property=og:url: https://spey.readthedocs.io/en/main/plugins.html -Spey seamlessly integrates with diverse packages that offer specific +Spey seamlessly integrates with diverse packages that offer specific statistical model prescriptions. Its primary objective is to centralise -these prescriptions within a unified interface, facilitating the -combination of different likelihood sources. This section -will overview the currently available plugins accessible +these prescriptions within a unified interface, facilitating the +combination of different likelihood sources. This section +will overview the currently available plugins accessible through the Spey interface. The string-based accessors to the available plugins can be seen using the following command: @@ -53,7 +53,7 @@ to the available plugins can be seen using the following command: where once installed without any plug-ins :func:`~spey.AvailableBackends` function only shows the default PDFs. In the following section, we will summarise their usability. -Once we know the accessor of the plug-in, it can be called using :func:`~spey.get_backend` +Once we know the accessor of the plug-in, it can be called using :func:`~spey.get_backend` function e.g. .. code-block:: python3 @@ -63,26 +63,26 @@ function e.g. this will automatically create a wrapper around the likelihood prescription and allow `spey` to use it properly. We will demonstrate the usage for each of the default plugins below. -.. note:: +.. note:: Documentation of each plug-in has been included within the ``pdf_wrapper`` documentation. Hence, if one types ``pdf_wrapper?`` in the ipython command line or a jupyter notebook, it is possible to access the extended documentation for both the wrapper and the backend in use. -.. attention:: +.. attention:: :func:`~spey.get_backend` function is a wrapper between the PDF prescription and ``spey`` package. - Once initialised, all PDF prescriptions are defined with :obj:`~spey.StatisticalModel` class + Once initialised, all PDF prescriptions are defined with :obj:`~spey.StatisticalModel` class which provides a backend agnostic interface, i.e. all PDF prescriptions will have the same functionality. Default Plug-ins ---------------- All default plug-ins are defined using the following main likelihood structure -.. math:: +.. math:: - \mathcal{L}(\mu,\theta) = \underbrace{\prod_{i\in{\rm bins}} - \mathcal{M}(n_i|\lambda_i(\mu, \theta))}_{\rm main}\cdot + \mathcal{L}(\mu,\theta) = \underbrace{\prod_{i\in{\rm bins}} + \mathcal{M}(n_i|\lambda_i(\mu, \theta))}_{\rm main}\cdot \underbrace{\prod_{j\in{\rm nui}}\mathcal{C}(\theta_j)}_{\rm constraint} \ , The first term represents the primary model, and the second represents the constraint model. @@ -92,17 +92,17 @@ The first term represents the primary model, and the second represents the const ``'default_pdf.uncorrelated_background'`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This is a basic PDF where the background is assumed to be not correlated, where the PDF has been -given as +This is a basic PDF where the background is assumed to be not correlated, where the PDF has been +given as -.. math:: +.. math:: - \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i + + \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i + \theta^i\sigma_b^i) \cdot \prod_{j\in{\rm nui}}\mathcal{N}(\theta^j|0, 1)\ , -where :math:`\mu, \theta` are the parameter of interest (signal strength) and nuisance parameters, +where :math:`\mu, \theta` are the parameter of interest (signal strength) and nuisance parameters, the signal and background yields are given as :math:`n_s^i` and :math:`n_b^i\pm\sigma_b^i` respectively. -Additionally, absolute uncertainties are modeled as Gaussian distributions. This model can be +Additionally, absolute uncertainties are modeled as Gaussian distributions. This model can be used as follows .. code-block:: python3 @@ -127,7 +127,7 @@ used as follows * ``analysis`` (optional): Unique identifier for the analysis. * ``xsection`` (optional): Cross-section value for the signal hypothesis. Units determined by the user. -This particular example implements a two-bin histogram with uncorrelated bins. The exclusion CL +This particular example implements a two-bin histogram with uncorrelated bins. The exclusion CL (:math:`1-CL_s`) can be computed via :func:`spey.StatisticalModel.exclusion_confidence_level` function. .. code-block:: python3 @@ -146,9 +146,9 @@ API description. This plugin embeds the correlations between each bin using a covariance matrix provided by the user which employs the following PDF structure -.. math:: +.. math:: - \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i + + \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i + \theta^i\sigma_b^i) \cdot \prod_{j\in{\rm nui}}\mathcal{N}(\theta^j|0, \rho)\ , Notice that the only difference between the uncorrelated case is the constraint term, which includes @@ -194,31 +194,31 @@ as expected. ``'default_pdf.third_moment_expansion'`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This plug-in implements the third-moment expansion presented in :cite:`Buckley:2018vdr`, which expands the +This plug-in implements the third-moment expansion presented in :cite:`Buckley:2018vdr`, which expands the the main model using the diagonal elements of the third moments -.. math:: +.. math:: \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + \bar{n}_b^i + B_i\theta_i + S_i\theta_i^2) \cdot \prod_{j\in{\rm nui}}\mathcal{N}(\theta^j|0, \rho)\ , where :math:`\bar{n}_b^i,\ B_i,\ S_i` and :math:`\rho` are defined as -.. math:: +.. math:: - S_i = -sign(m^{(3)}_i) \sqrt{2 diag(\Sigma)_i^2} + S_i = -sign(m^{(3)}_i) \sqrt{2 diag(\Sigma)_i^2} \times\cos\left( \frac{4\pi}{3} + - \frac{1}{3}\arctan\left(\sqrt{ \frac{8(diag(\Sigma)_i^2)^3}{(m^{(3)}_i)^2} - 1}\right) \right)\ , + \frac{1}{3}\arctan\left(\sqrt{ \frac{8(diag(\Sigma)_i^2)^3}{(m^{(3)}_i)^2} - 1}\right) \right)\ , + +.. math:: -.. math:: - - B_i = \sqrt{diag{\Sigma}_i - 2 S_i^2}\ , - -.. math:: + B_i = \sqrt{diag{\Sigma}_i - 2 S_i^2}\ , + +.. math:: \bar{n}_b^i = n_b^i - S_i\ , - -.. math:: + +.. math:: \rho_{ij} = \frac{1}{4S_iS_j} \left( \sqrt{(B_iB_j)^2 + 8S_iS_j\Sigma_{ij}} - B_iB_j \right) @@ -256,7 +256,7 @@ reduced the exclusion limit. * ``covariance_matrix``: Covariance matrix which captures the correlations and absolute uncertainty values of the background hypothesis. For absolute uncertainty :math:`\sigma_b`; :math:`\sigma_b = \sqrt{{\rm diag}(\Sigma)}`. The covariance matrix should be a square matrix and both dimensions should match the number of ``background_yields`` given as input. - * ``third_moment``: Diagonal elements of the third moments. These can be computed using + * ``third_moment``: Diagonal elements of the third moments. These can be computed using :func:`~spey.backends.default_pdf.third_moment.compute_third_moments` function; however this function computes third moments using Bifurcated Gaussian, which may not be suitable for every case. * ``analysis`` (optional): Unique identifier for the analysis. @@ -267,18 +267,18 @@ reduced the exclusion limit. ``'default_pdf.effective_sigma'`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The skewness of the PDF distribution can also be captured by building an effective variance -from the upper (:math:`\sigma^+`) and lower (:math:`\sigma^-`) uncertainty envelops as a +The skewness of the PDF distribution can also be captured by building an effective variance +from the upper (:math:`\sigma^+`) and lower (:math:`\sigma^-`) uncertainty envelops as a the function of nuisance parameters, -.. math:: +.. math:: \sigma_{\rm eff}^i(\theta^i) = \sqrt{\sigma^+_i\sigma^-_i + (\sigma^+_i - \sigma^-_i)(\theta^i - n_b^i)} -This method has been proposed in :cite:`Barlow:2004wg` for Gaussian models which can be +This method has been proposed in :cite:`Barlow:2004wg` for Gaussian models which can be generalised for the Poisson distribution by modifying :math:`\lambda_i(\mu, \theta)` as follows -.. math:: +.. math:: \mathcal{L}(\mu, \theta) = \prod_{i\in{\rm bins}}{\rm Poiss}(n^i|\mu n_s^i + n_b^i + \theta^i\sigma_{\rm eff}^i(\theta^i)) \cdot \prod_{j\in{\rm nui}}\mathcal{N}(\theta^j|0, \rho)\ , @@ -300,7 +300,7 @@ iterating over the same example, this PDF can be utilised as follows; ... ) where ``absolute_uncertainty_envelops`` refers to each bin's upper and lower uncertainty envelopes. -Once again, the exclusion limit can be computed as +Once again, the exclusion limit can be computed as .. code-block:: python3 @@ -312,12 +312,12 @@ Once again, the exclusion limit can be computed as * ``signal_yields``: keyword for signal yields. It can take one or more values as a list or NumPy array. * ``background_yields``: keyword for background-only expectations. It can take one or more values as a list or NumPy array. * ``data``: keyword for observations. It can take one or more values as a list or NumPy array. - * ``correlation_matrix``: The correlation matrix should be a square matrix, and both dimensions + * ``correlation_matrix``: The correlation matrix should be a square matrix, and both dimensions should match the number of ``background_yields`` given as input. If only the covariance matrix is available, - one can use :func:`~spey.helper_functions.covariance_to_correlation` function to convert the covariance matrix to + one can use :func:`~spey.helper_functions.covariance_to_correlation` function to convert the covariance matrix to a correlation matrix. - * ``absolute_uncertainty_envelops``: This is a list of upper and lower uncertainty envelops where the first element of each - input should be the upper uncertainty, and the second element should be the lower uncertainty envelop, e.g., + * ``absolute_uncertainty_envelops``: This is a list of upper and lower uncertainty envelops where the first element of each + input should be the upper uncertainty, and the second element should be the lower uncertainty envelop, e.g., for background given as :math:`{n_b}_{-\sigma^-}^{+\sigma^+}` the input should be [(:math:`|\sigma^+|`, :math:`|\sigma^-|`)]. * ``analysis`` (optional): Unique identifier for the analysis. * ``xsection`` (optional): Cross-section value for the signal hypothesis. Units determined by the user. @@ -441,4 +441,4 @@ External Plug-ins * `pyhf documentation `_ :cite:`pyhf_joss`. * `Spey-pyhf plug-in GitHub Repository `_ * ``spey-fastprof``: TBA :cite:`Berger:2023bat`. -* `fastprof documentation `_. \ No newline at end of file +* `fastprof documentation `_. diff --git a/docs/quick_start.rst b/docs/quick_start.rst index 3006393..1a451b5 100644 --- a/docs/quick_start.rst +++ b/docs/quick_start.rst @@ -19,20 +19,20 @@ Installation >>> pip install spey -Python >=3.8 is required. spey heavily relies on `numpy `_, -`scipy `_ and `autograd `_ -which are all packaged during the installation with the necessary versions. Note that some +Python >=3.8 is required. spey heavily relies on `numpy `_, +`scipy `_ and `autograd `_ +which are all packaged during the installation with the necessary versions. Note that some versions may be restricted due to numeric stability and validation. What is Spey? ------------- -Spey is a plug-in-based statistics tool designed to consolidate a wide range of -likelihood prescriptions in a comprehensive platform. Spey empowers users to integrate -different statistical models seamlessly and explore -their properties through a unified interface by offering a flexible workspace. -To ensure compatibility with existing and future -statistical model prescriptions, Spey adopts a versatile plug-in system. This approach enables +Spey is a plug-in-based statistics tool designed to consolidate a wide range of +likelihood prescriptions in a comprehensive platform. Spey empowers users to integrate +different statistical models seamlessly and explore +their properties through a unified interface by offering a flexible workspace. +To ensure compatibility with existing and future +statistical model prescriptions, Spey adopts a versatile plug-in system. This approach enables developers to propose and integrate their statistical model prescriptions, thereby expanding the capabilities and applicability of Spey. @@ -42,8 +42,8 @@ the capabilities and applicability of Spey. First Steps ----------- -First, one needs to choose which backend to work with. By default, Spey is shipped with various types of -likelihood prescriptions which can be checked via :func:`~spey.AvailableBackends` +First, one needs to choose which backend to work with. By default, Spey is shipped with various types of +likelihood prescriptions which can be checked via :func:`~spey.AvailableBackends` function .. code-block:: python3 @@ -74,21 +74,21 @@ statistical models: ... background_yields=background_yields, ... data=data, ... absolute_uncertainties=background_unc, - ... analysis="single_bin", + ... analysis="single_bin", ... xsection=0.123, ... ) where ``data`` indicates the observed events, ``signal_yields`` and ``background_yields`` represents -yields for signal and background samples and ``background_unc`` shows the absolute uncertainties on -the background events, i.e. :math:`2.0\pm1.1` in this particular case. Note that we also introduced +yields for signal and background samples and ``background_unc`` shows the absolute uncertainties on +the background events, i.e. :math:`2.0\pm1.1` in this particular case. Note that we also introduced ``analysis`` and ``xsection`` information which are optional where the ``analysis`` indicates an unique identifier for the statistical model, and ``xsection`` is the cross-section value of the signal, which is only used for the computation of the excluded cross-section value. -During the computation of any probability distribution, Spey relies on the so-called "expectation type". +During the computation of any probability distribution, Spey relies on the so-called "expectation type". This can be set via :obj:`~spey.ExpectationType`, which includes three different expectation modes. -* :obj:`~spey.ExpectationType.observed`: Indicates that the computation of the log-probability will be +* :obj:`~spey.ExpectationType.observed`: Indicates that the computation of the log-probability will be achieved by fitting the statistical model on the experimental data. For the exclusion limit computation, this will tell the package to compute observed :math:`1-CL_s` values. :obj:`~spey.ExpectationType.observed` has been set as default throughout the package. @@ -98,7 +98,7 @@ This can be set via :obj:`~spey.ExpectationType`, which includes three different the statistical model on the background and checking :math:`\pm1\sigma` and :math:`\pm2\sigma` fluctuations. * :obj:`~spey.ExpectationType.apriori`: Indicates that the observation has never taken place and the theoretical - SM computation is the absolute truth. Thus, it replaces observed values in the statistical model with the + SM computation is the absolute truth. Thus, it replaces observed values in the statistical model with the background values and computes the log-probability accordingly. Similar to :obj:`~spey.ExpectationType.aposteriori` Exclusion limit computation will return expected limits. @@ -112,12 +112,12 @@ To compute the observed exclusion limit for the above example, one can type >>> # 1-CLs (aposteriori): [0.6959976874809755, 0.5466491036450178, 0.3556261845401908, 0.2623335168616665, 0.2623335168616665] >>> # 1-CLs (observed): [0.40145846656558726] -Note that :obj:`~spey.ExpectationType.apriori` and :obj:`~spey.ExpectationType.aposteriori` expectation types +Note that :obj:`~spey.ExpectationType.apriori` and :obj:`~spey.ExpectationType.aposteriori` expectation types resulted in a list of 5 elements which indicates :math:`-2\sigma,\ -1\sigma,\ 0,\ +1\sigma,\ +2\sigma` standard deviations -from the background hypothesis. :obj:`~spey.ExpectationType.observed`, on the other hand, resulted in a single value, which is -the observed exclusion limit. Notice that the bounds on :obj:`~spey.ExpectationType.aposteriori` are slightly more potent than -:obj:`~spey.ExpectationType.apriori`; this is due to the data value has been replaced with background yields, -which are larger than the observations. :obj:`~spey.ExpectationType.apriori` is mainly used in theory +from the background hypothesis. :obj:`~spey.ExpectationType.observed`, on the other hand, resulted in a single value, which is +the observed exclusion limit. Notice that the bounds on :obj:`~spey.ExpectationType.aposteriori` are slightly more potent than +:obj:`~spey.ExpectationType.apriori`; this is due to the data value has been replaced with background yields, +which are larger than the observations. :obj:`~spey.ExpectationType.apriori` is mainly used in theory collaborations to estimate the difference from the Standard Model rather than the experimental observations. .. note:: @@ -140,11 +140,11 @@ One can play the same game using the same backend for multi-bin histograms as fo ... background_yields=background_yields, ... data=data, ... absolute_uncertainties=background_unc, - ... analysis="multi_bin", + ... analysis="multi_bin", ... xsection=0.123, ... ) -Note that our statistical model still represents individual bins of the histograms independently however, it sums up the +Note that our statistical model still represents individual bins of the histograms independently however, it sums up the log-likelihood of each bin. Hence, all bins are completely uncorrelated from each other. Computing the exclusion limits for each :obj:`~spey.ExpectationType` will yield @@ -178,7 +178,7 @@ This can be achieved by including a value for ``poi_test`` argument >>> plt.text(0.5,0.96, r"$95\%\ {\rm CL}$") >>> plt.show() -Here in the first line, we extract :math:`1-CL_s` values per POI for :obj:`~spey.ExpectationType.aposteriori` +Here in the first line, we extract :math:`1-CL_s` values per POI for :obj:`~spey.ExpectationType.aposteriori` expectation type, and we plot specific standard deviations, which provides the following plot: .. image:: ./figs/brazilian_plot.png @@ -194,7 +194,7 @@ The excluded value of POI can also be retrieved by :func:`~spey.StatisticalModel >>> # POI UL: 0.920 which is the exact point where the red curve and black dashed line meet. The upper limit for the :math:`\pm1\sigma` -or :math:`\pm2\sigma` bands can be extracted by setting ``expected_pvalue`` to ``"1sigma"`` or ``"2sigma"`` +or :math:`\pm2\sigma` bands can be extracted by setting ``expected_pvalue`` to ``"1sigma"`` or ``"2sigma"`` respectively, e.g. .. code:: python @@ -202,10 +202,10 @@ respectively, e.g. >>> stat_model.poi_upper_limit(expected=spey.ExpectationType.aposteriori, expected_pvalue="1sigma") >>> # [0.5507713378348318, 0.9195052042538805, 1.4812721449679866] -At a lower level, one can extract the likelihood information for the statistical model by calling +At a lower level, one can extract the likelihood information for the statistical model by calling :func:`~spey.StatisticalModel.likelihood` and :func:`~spey.StatisticalModel.maximize_likelihood` functions. By default, these will return negative log-likelihood values, but this can be changed via ``return_nll=False`` -argument. +argument. .. code:: python :linenos: @@ -241,6 +241,6 @@ as follows :alt: Likelihood distribution for a multi-bin statistical model. Notice the slight difference between likelihood distributions because of the use of different expectation types. -The dots on the likelihood distribution represent the point where the likelihood is maximised. Since for an +The dots on the likelihood distribution represent the point where the likelihood is maximised. Since for an :obj:`~spey.ExpectationType.apriori` likelihood distribution observed and background values are the same, the likelihood -should peak at :math:`\mu=0`. \ No newline at end of file +should peak at :math:`\mu=0`. diff --git a/docs/release_notes.rst b/docs/release_notes.rst index adca2f1..066701c 100644 --- a/docs/release_notes.rst +++ b/docs/release_notes.rst @@ -4,4 +4,4 @@ Release Notes .. toctree:: :maxdepth: 2 - releases/changelog-v0.1 \ No newline at end of file + releases/changelog-v0.1 diff --git a/docs/requirements.txt b/docs/requirements.txt index 007038c..e8c3f37 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,4 +8,4 @@ sphinx-togglebutton>=0.3.0 myst-parser sphinx-rtd-size sphinx-book-theme -myst-nb \ No newline at end of file +myst-nb diff --git a/docs/tuto_plugin.rst b/docs/tuto_plugin.rst index 80338f7..250e65a 100644 --- a/docs/tuto_plugin.rst +++ b/docs/tuto_plugin.rst @@ -5,4 +5,4 @@ Building a Plug-in :maxdepth: 2 Neutrino Experiment (example) - Simple Poisson (example) \ No newline at end of file + Simple Poisson (example) diff --git a/docs/tutorials.rst b/docs/tutorials.rst index f86f667..20242ee 100644 --- a/docs/tutorials.rst +++ b/docs/tutorials.rst @@ -10,4 +10,4 @@ Kitchen Sink tutorials/gradients tutorials/signal_unc tuto_plugin - Introduction to Spey (PyHEP 2023) \ No newline at end of file + Introduction to Spey (PyHEP 2023) diff --git a/setup.py b/setup.py index 3ad5dfa..929b3ef 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ from setuptools import setup, find_packages -with open("README.md", mode="r", encoding="utf-8") as f: +with open("README.md", encoding="utf-8") as f: long_description = f.read() -with open("src/spey/_version.py", mode="r", encoding="UTF-8") as f: +with open("src/spey/_version.py", encoding="UTF-8") as f: version = f.readlines()[-1].split()[-1].strip("\"'") requirements = [ diff --git a/src/spey/__init__.py b/src/spey/__init__.py index ab5de3f..ffafa8e 100644 --- a/src/spey/__init__.py +++ b/src/spey/__init__.py @@ -78,7 +78,7 @@ def set_log_level(level: Literal[0, 1, 2, 3]) -> None: log.setLevel(log_dict[level]) -def version() -> Text: +def version() -> str: """ Version of ``spey`` package @@ -88,7 +88,7 @@ def version() -> Text: return __version__ -def _get_backend_entrypoints() -> Dict[Text, pkg_resources.EntryPoint]: +def _get_backend_entrypoints() -> Dict[str, pkg_resources.EntryPoint]: """Collect plugin entries""" return { entry.name: entry @@ -96,7 +96,7 @@ def _get_backend_entrypoints() -> Dict[Text, pkg_resources.EntryPoint]: } -_backend_entries: Dict[Text, pkg_resources.EntryPoint] = _get_backend_entrypoints() +_backend_entries: Dict[str, pkg_resources.EntryPoint] = _get_backend_entrypoints() # ! Preinitialise backends, it might be costly to scan the system everytime @@ -105,7 +105,7 @@ def reset_backend_entries() -> None: _backend_entries = _get_backend_entrypoints() -def AvailableBackends() -> List[Text]: # pylint: disable=C0103 +def AvailableBackends() -> List[str]: # 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 @@ -122,7 +122,7 @@ def AvailableBackends() -> List[Text]: # pylint: disable=C0103 return [*_backend_entries.keys()] -def get_backend(name: Text) -> Callable[[Any], StatisticalModel]: +def get_backend(name: str) -> Callable[[Any], StatisticalModel]: """ Statistical model backend retreiver. Available backend names can be found via :func:`~spey.AvailableBackends` function. @@ -210,7 +210,7 @@ def get_backend(name: Text) -> Callable[[Any], StatisticalModel]: ) -def get_backend_metadata(name: Text) -> Dict[Text, Any]: +def get_backend_metadata(name: str) -> Dict[str, Any]: """ Retreive metadata about the backend. This includes citation information, doi, author names etc. Available backend names can be found via @@ -265,7 +265,7 @@ def get_backend_metadata(name: Text) -> Dict[Text, Any]: ) -def get_backend_bibtex(name: Text) -> Dict[Text, List[Text]]: +def get_backend_bibtex(name: str) -> Dict[str, List[str]]: """ Retreive BibTex entry for backend plug-in if available. @@ -326,7 +326,7 @@ def get_backend_bibtex(name: Text) -> Dict[Text, List[Text]]: return out -def cite() -> List[Text]: +def cite() -> List[str]: """Retreive BibTex information for Spey""" try: arxiv = textwrap.indent(get_bibtex("inspire/arxiv", "2307.06996"), " " * 4) diff --git a/src/spey/backends/default_pdf/__init__.py b/src/spey/backends/default_pdf/__init__.py index eecd7d5..da8fd70 100644 --- a/src/spey/backends/default_pdf/__init__.py +++ b/src/spey/backends/default_pdf/__init__.py @@ -55,13 +55,13 @@ class DefaultPDFBase(BackendBase): :func:`autograd.numpy.array` function. """ - name: Text = "default_pdf.base" + name: str = "default_pdf.base" """Name of the backend""" - version: Text = __version__ + version: str = __version__ """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = __version__ + spey_requires: str = __version__ """Spey version required for the backend""" __slots__ = [ @@ -80,7 +80,7 @@ def __init__( covariance_matrix: Optional[ Union[np.ndarray, Callable[[np.ndarray], np.ndarray]] ] = None, - signal_uncertainty_configuration: Optional[Dict[Text, Any]] = None, + signal_uncertainty_configuration: Optional[Dict[str, Any]] = None, ): self.data = np.array(data, dtype=np.float64) self.signal_yields = np.array(signal_yields, dtype=np.float64) @@ -457,13 +457,13 @@ class UncorrelatedBackground(DefaultPDFBase): >>> print("1-CLs : %.3f" % tuple(stat_model.exclusion_confidence_level())) """ - name: Text = "default_pdf.uncorrelated_background" + name: str = "default_pdf.uncorrelated_background" """Name of the backend""" - version: Text = DefaultPDFBase.version + version: str = DefaultPDFBase.version """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = DefaultPDFBase.spey_requires + spey_requires: str = DefaultPDFBase.spey_requires """Spey version required for the backend""" def __init__( @@ -472,7 +472,7 @@ def __init__( background_yields: List[float], data: List[int], absolute_uncertainties: List[float], - signal_uncertainty_configuration: Optional[Dict[Text, Any]] = None, + signal_uncertainty_configuration: Optional[Dict[str, Any]] = None, ): super().__init__( signal_yields=signal_yields, @@ -586,13 +586,13 @@ class CorrelatedBackground(DefaultPDFBase): >>> print(statistical_model.exclusion_confidence_level()) """ - name: Text = "default_pdf.correlated_background" + name: str = "default_pdf.correlated_background" """Name of the backend""" - version: Text = DefaultPDFBase.version + version: str = DefaultPDFBase.version """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = DefaultPDFBase.spey_requires + spey_requires: str = DefaultPDFBase.spey_requires """Spey version required for the backend""" def __init__( @@ -601,7 +601,7 @@ def __init__( background_yields: np.ndarray, data: np.ndarray, covariance_matrix: np.ndarray, - signal_uncertainty_configuration: Optional[Dict[Text, Any]] = None, + signal_uncertainty_configuration: Optional[Dict[str, Any]] = None, ): super().__init__( signal_yields=signal_yields, @@ -663,17 +663,17 @@ class ThirdMomentExpansion(DefaultPDFBase): ``third_moment`` should also have three inputs. """ - name: Text = "default_pdf.third_moment_expansion" + name: str = "default_pdf.third_moment_expansion" """Name of the backend""" - version: Text = DefaultPDFBase.version + version: str = DefaultPDFBase.version """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = DefaultPDFBase.spey_requires + spey_requires: str = DefaultPDFBase.spey_requires """Spey version required for the backend""" - doi: List[Text] = ["10.1007/JHEP04(2019)064"] + doi: List[str] = ["10.1007/JHEP04(2019)064"] """Citable DOI for the backend""" - arXiv: List[Text] = ["1809.05548"] + arXiv: List[str] = ["1809.05548"] """arXiv reference for the backend""" def __init__( @@ -683,7 +683,7 @@ def __init__( data: np.ndarray, covariance_matrix: np.ndarray, third_moment: np.ndarray, - signal_uncertainty_configuration: Optional[Dict[Text, Any]] = None, + signal_uncertainty_configuration: Optional[Dict[str, Any]] = None, ): third_moments = np.array(third_moment) @@ -803,17 +803,17 @@ class EffectiveSigma(DefaultPDFBase): * third_moments (``List[float]``): diagonal elemetns of the third moment """ - name: Text = "default_pdf.effective_sigma" + name: str = "default_pdf.effective_sigma" """Name of the backend""" - version: Text = DefaultPDFBase.version + version: str = DefaultPDFBase.version """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = DefaultPDFBase.spey_requires + spey_requires: str = DefaultPDFBase.spey_requires """Spey version required for the backend""" - doi: List[Text] = ["10.1142/9781860948985_0013"] + doi: List[str] = ["10.1142/9781860948985_0013"] """Citable DOI for the backend""" - arXiv: List[Text] = ["physics/0406120"] + arXiv: List[str] = ["physics/0406120"] """arXiv reference for the backend""" def __init__( @@ -823,7 +823,7 @@ def __init__( data: np.ndarray, correlation_matrix: np.ndarray, absolute_uncertainty_envelops: List[Tuple[float, float]], - signal_uncertainty_configuration: Optional[Dict[Text, Any]] = None, + signal_uncertainty_configuration: Optional[Dict[str, Any]] = None, ): assert len(absolute_uncertainty_envelops) == len( background_yields diff --git a/src/spey/backends/default_pdf/simple_pdf.py b/src/spey/backends/default_pdf/simple_pdf.py index 93c6dba..a8a9c1f 100644 --- a/src/spey/backends/default_pdf/simple_pdf.py +++ b/src/spey/backends/default_pdf/simple_pdf.py @@ -1,10 +1,11 @@ """This file contains basic likelihood implementations""" -from typing import Callable, List, Optional, Text, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union -from autograd import hessian +from autograd import hessian, jacobian from autograd import numpy as np from autograd import value_and_grad +from scipy.optimize import NonlinearConstraint from spey._version import __version__ from spey.backends.distributions import MainModel @@ -18,13 +19,13 @@ class SimplePDFBase(BackendBase): """Base structure for simple PDFs""" - name: Text = "__simplepdf_base__" + name: str = "__simplepdf_base__" """Name of the backend""" - version: Text = __version__ + version: str = __version__ """Version of the backend""" - author: Text = "SpeysideHEP" + author: str = "SpeysideHEP" """Author of the backend""" - spey_requires: Text = __version__ + spey_requires: str = __version__ """Spey version required for the backend""" __slots__ = [ @@ -49,6 +50,8 @@ def __init__( """main model""" self._main_kwargs = {} """Keyword arguments for main model""" + self.constraints = [] + """Constraints to be used during optimisation process""" minimum_poi = -np.inf if self.is_alive: @@ -297,15 +300,17 @@ class Poisson(SimplePDFBase): signal_yields (``List[float]``): signal yields background_yields (``List[float]``): background yields data (``List[int]``): data + absolute_uncertainties (``List[float]``, optional): background uncertainties to be added. + :math:`\mu n_s^i + n_b^i + \theta^i\sigma^i`. """ - name: Text = "default_pdf.poisson" + name: str = "default_pdf.poisson" """Name of the backend""" - version: Text = SimplePDFBase.version + version: str = SimplePDFBase.version """Version of the backend""" - author: Text = SimplePDFBase.author + author: str = SimplePDFBase.author """Author of the backend""" - spey_requires: Text = SimplePDFBase.spey_requires + spey_requires: str = SimplePDFBase.spey_requires """Spey version required for the backend""" def __init__( @@ -313,11 +318,48 @@ def __init__( signal_yields: List[float], background_yields: List[float], data: List[int], + absolute_uncertainties: Optional[List[float]] = None, ): super().__init__( signal_yields=signal_yields, background_yields=background_yields, data=data ) + if absolute_uncertainties is not None: + self.absolute_uncertainties = np.array(absolute_uncertainties) + assert len(self.absolute_uncertainties) == len( + self.data + ), "Invalid input dimension." + + def lam(pars: np.ndarray) -> np.ndarray: + return ( + pars[0] * self.signal_yields + + self.background_yields + + pars[slice(1, len(self.data) + 1)] * self.absolute_uncertainties + ) + + self._main_model = MainModel(lam) + + self._config = ModelConfig( + poi_index=0, + minimum_poi=self._config.minimum_poi, + suggested_init=[1.0] * (len(self.data) + 1), + suggested_bounds=[(self._config.minimum_poi, 10)] + + [(None, None)] * len(self.data), + ) + + def constraint(pars: np.ndarray) -> np.ndarray: + """Compute the constraint term""" + return ( + self.background_yields + + pars[slice(1, len(self.data) + 1)] * self.background_yields + ) + + jac_constr = jacobian(constraint) + + self.constraints.append( + NonlinearConstraint(constraint, 0.0, np.inf, jac=jac_constr) + ) + class Gaussian(SimplePDFBase): r""" @@ -339,13 +381,13 @@ class Gaussian(SimplePDFBase): absolute_uncertainties (``List[float]``): absolute uncertainties on the background """ - name: Text = "default_pdf.normal" + name: str = "default_pdf.normal" """Name of the backend""" - version: Text = SimplePDFBase.version + version: str = SimplePDFBase.version """Version of the backend""" - author: Text = SimplePDFBase.author + author: str = SimplePDFBase.author """Author of the backend""" - spey_requires: Text = SimplePDFBase.spey_requires + spey_requires: str = SimplePDFBase.spey_requires """Spey version required for the backend""" __slots__ = ["absolute_uncertainties"] @@ -389,13 +431,13 @@ class MultivariateNormal(SimplePDFBase): """ - name: Text = "default_pdf.multivariate_normal" + name: str = "default_pdf.multivariate_normal" """Name of the backend""" - version: Text = SimplePDFBase.version + version: str = SimplePDFBase.version """Version of the backend""" - author: Text = SimplePDFBase.author + author: str = SimplePDFBase.author """Author of the backend""" - spey_requires: Text = SimplePDFBase.spey_requires + spey_requires: str = SimplePDFBase.spey_requires """Spey version required for the backend""" __slots__ = ["covariance_matrix"] diff --git a/src/spey/backends/default_pdf/uncertainty_synthesizer.py b/src/spey/backends/default_pdf/uncertainty_synthesizer.py index 99520a9..fff3028 100644 --- a/src/spey/backends/default_pdf/uncertainty_synthesizer.py +++ b/src/spey/backends/default_pdf/uncertainty_synthesizer.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Text, Tuple +from typing import Any, Dict, List, Tuple import autograd.numpy as np @@ -11,7 +11,7 @@ def constraint_from_corr( correlation_matrix: List[List[float]], size: int, domain: slice -) -> List[Dict[Text, Any]]: +) -> List[Dict[str, Any]]: """ Derive constraints from inputs @@ -51,7 +51,7 @@ def signal_uncertainty_synthesizer( correlation_matrix: List[List[float]] = None, third_moments: List[float] = None, domain: slice = None, -) -> Dict[Text, np.ndarray]: +) -> Dict[str, np.ndarray]: """ Synthesize signal uncertainties diff --git a/src/spey/backends/distributions.py b/src/spey/backends/distributions.py index 6c818a2..3408f38 100644 --- a/src/spey/backends/distributions.py +++ b/src/spey/backends/distributions.py @@ -2,7 +2,7 @@ import logging import warnings -from typing import Any, Callable, Dict, List, Literal, Text, Union +from typing import Any, Callable, Dict, List, Literal, Union import autograd.numpy as np from autograd.scipy.special import gammaln @@ -257,7 +257,7 @@ class ConstraintModel: * ``"kwargs"``: Input keyword arguments for the distribution """ - def __init__(self, pdf_descriptions: List[Dict[Text, Any]]): + def __init__(self, pdf_descriptions: List[Dict[str, Any]]): self._pdfs = [] distributions = {"normal": Normal, "multivariatenormal": MultivariateNormal} diff --git a/src/spey/base/backend_base.py b/src/spey/base/backend_base.py index 236c033..4d4b8e3 100644 --- a/src/spey/base/backend_base.py +++ b/src/spey/base/backend_base.py @@ -1,7 +1,7 @@ """Abstract Methods for backend objects""" from abc import ABC, abstractmethod -from typing import Callable, List, Optional, Text, Tuple, Union +from typing import Callable, List, Optional, Tuple, Union import numpy as np @@ -250,7 +250,7 @@ def asimov_negative_loglikelihood( self, poi_test: float = 1.0, expected: ExpectationType = ExpectationType.observed, - test_statistics: Text = "qtilde", + test_statistics: str = "qtilde", **kwargs, ) -> Tuple[float, np.ndarray]: r""" @@ -353,7 +353,7 @@ def minimize_negative_loglikelihood( def minimize_asimov_negative_loglikelihood( self, expected: ExpectationType = ExpectationType.observed, - test_statistics: Text = "qtilde", + test_statistics: str = "qtilde", **kwargs, ) -> Tuple[float, np.ndarray]: r""" diff --git a/src/spey/base/hypotest_base.py b/src/spey/base/hypotest_base.py index d4a53bd..0783b7f 100644 --- a/src/spey/base/hypotest_base.py +++ b/src/spey/base/hypotest_base.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from functools import partial -from typing import Any, Callable, Dict, List, Literal, Optional, Text, Tuple, Union +from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union import numpy as np import tqdm @@ -841,7 +841,7 @@ def poi_upper_limit( hig_init: Optional[float] = 1.0, expected_pvalue: Literal["nominal", "1sigma", "2sigma"] = "nominal", maxiter: int = 10000, - optimiser_arguments: Optional[Dict[Text, Any]] = None, + optimiser_arguments: Optional[Dict[str, Any]] = None, ) -> Union[float, List[float]]: r""" Compute the upper limit for the parameter of interest i.e. :math:`\mu`. diff --git a/src/spey/base/model_config.py b/src/spey/base/model_config.py index f8b261b..e4ca2be 100644 --- a/src/spey/base/model_config.py +++ b/src/spey/base/model_config.py @@ -37,7 +37,7 @@ class ModelConfig: minimum_poi: float r""" minimum value parameter of interest can take to ensure :math:`N^{\rm bkg}+\mu N^{\rm sig}\geq0`. - This value can be set to :math:`-\infty` but note that optimiser will take it as a lower bound so + This value can be set to :math:`-\infty` but note that optimiser will take it as a lower bound so such low value might effect the convergence of the optimisation algorithm especially for relatively flat objective surfaceses. Hence we suggest setting the minimum value to @@ -49,7 +49,7 @@ class ModelConfig: """Suggested initialisation for parameters""" suggested_bounds: List[Tuple[float, float]] """Suggested upper and lower bounds for parameters""" - parameter_names: Optional[List[Text]] = None + parameter_names: Optional[List[str]] = None """Names of the parameters""" suggested_fixed: Optional[List[bool]] = None """Suggested fixed values""" diff --git a/src/spey/base/recorder.py b/src/spey/base/recorder.py index f6fedf5..aa4ba3c 100644 --- a/src/spey/base/recorder.py +++ b/src/spey/base/recorder.py @@ -68,7 +68,9 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.play() - def get_poi_test(self, expected: ExpectationType, poi_test: float) -> Union[float, bool]: + def get_poi_test( + self, expected: ExpectationType, poi_test: float + ) -> Union[float, bool]: """Retrieve NLL value for given poi test and expectation value""" poi_test = np.float32(poi_test) if self.is_on() and not self._freeze_record: @@ -77,7 +79,9 @@ def get_poi_test(self, expected: ExpectationType, poi_test: float) -> Union[floa else: return False - def get_maximum_likelihood(self, expected: ExpectationType) -> Union[Tuple[float, float], bool]: + def get_maximum_likelihood( + self, expected: ExpectationType + ) -> Union[Tuple[float, float], bool]: """Retrieve maximum likelihood and fit param""" if self.is_on() and not self._freeze_record: return self._maximum_likelihood_record[str(expected)] @@ -90,7 +94,9 @@ def record_poi_test( if not self._freeze_record and self.is_on(): poi_test = np.float32(poi_test) negative_loglikelihood = np.float32(negative_loglikelihood) - self._poi_test_record[str(expected)].update({poi_test: negative_loglikelihood}) + self._poi_test_record[str(expected)].update( + {poi_test: negative_loglikelihood} + ) def record_maximum_likelihood( self, expected: ExpectationType, poi_test: float, negative_loglikelihood: float @@ -98,4 +104,7 @@ def record_maximum_likelihood( if not self._freeze_record and self.is_on(): poi_test = np.float32(poi_test) negative_loglikelihood = np.float32(negative_loglikelihood) - self._maximum_likelihood_record[str(expected)] = (poi_test, negative_loglikelihood) + self._maximum_likelihood_record[str(expected)] = ( + poi_test, + negative_loglikelihood, + ) diff --git a/src/spey/combiner/uncorrelated_statistics_combiner.py b/src/spey/combiner/uncorrelated_statistics_combiner.py index 9056728..ae15c4b 100644 --- a/src/spey/combiner/uncorrelated_statistics_combiner.py +++ b/src/spey/combiner/uncorrelated_statistics_combiner.py @@ -1,5 +1,5 @@ """ -Statistical Model combiner class: this class combines likelihoods +Statistical Model combiner class: this class combines likelihoods of different statistical models for hypothesis testing """ @@ -73,7 +73,7 @@ def append(self, statistical_model: StatisticalModel) -> None: else: raise TypeError(f"Can not append type {type(statistical_model)}.") - def remove(self, analysis: Text) -> None: + def remove(self, analysis: str) -> None: """ Remove an analysis from the stack. @@ -104,7 +104,7 @@ def statistical_models(self) -> Tuple[StatisticalModel]: return tuple(self._statistical_models) @property - def analyses(self) -> List[Text]: + def analyses(self) -> List[str]: """ List of the unique identifiers of the statistical models within the stack. @@ -151,7 +151,7 @@ def is_chi_square_calculator_available(self) -> bool: r"""Check if :math:`\chi^2` calculator is available for the backend""" return all(model.is_chi_square_calculator_available for model in self) - def __getitem__(self, item: Union[Text, int]) -> StatisticalModel: + def __getitem__(self, item: Union[str, int]) -> StatisticalModel: """Retrieve a statistical model""" if isinstance(item, int): if item < len(self): @@ -175,7 +175,7 @@ def __len__(self): """Number of statistical models within the stack""" return len(self._statistical_models) - def items(self) -> Iterator[Tuple[Text, StatisticalModel]]: + def items(self) -> Iterator[Tuple[str, StatisticalModel]]: """ Returns a generator that returns analysis name and :obj:`~spey.StatisticalModel` every iteration. @@ -201,8 +201,8 @@ def likelihood( poi_test: float = 1.0, expected: ExpectationType = ExpectationType.observed, return_nll: bool = True, - data: Optional[Dict[Text, List[float]]] = None, - statistical_model_options: Optional[Dict[Text, Dict]] = None, + data: Optional[Dict[str, List[float]]] = None, + statistical_model_options: Optional[Dict[str, Dict]] = None, **kwargs, ) -> float: r""" @@ -277,10 +277,10 @@ def likelihood( def generate_asimov_data( self, expected: ExpectationType = ExpectationType.observed, - test_statistic: Text = "qtilde", - statistical_model_options: Optional[Dict[Text, Dict]] = None, + test_statistic: str = "qtilde", + statistical_model_options: Optional[Dict[str, Dict]] = None, **kwargs, - ) -> Dict[Text, List[float]]: + ) -> Dict[str, List[float]]: r""" Generate Asimov data for the statistical model. This function calls :func:`~spey.StatisticalModel.generate_asimov_data` function for each statistical model with @@ -361,8 +361,8 @@ def asimov_likelihood( poi_test: float = 1.0, expected: ExpectationType = ExpectationType.observed, return_nll: bool = True, - test_statistics: Text = "qtilde", - statistical_model_options: Optional[Dict[Text, Dict]] = None, + test_statistics: str = "qtilde", + statistical_model_options: Optional[Dict[str, Dict]] = None, **kwargs, ) -> float: r""" @@ -436,10 +436,10 @@ def maximize_likelihood( return_nll: bool = True, expected: ExpectationType = ExpectationType.observed, allow_negative_signal: bool = True, - data: Optional[Dict[Text, List[float]]] = None, + data: Optional[Dict[str, List[float]]] = None, initial_muhat_value: Optional[float] = None, par_bounds: Optional[List[Tuple[float, float]]] = None, - statistical_model_options: Optional[Dict[Text, Dict]] = None, + statistical_model_options: Optional[Dict[str, Dict]] = None, **optimiser_options, ) -> Tuple[float, float]: r""" @@ -561,10 +561,10 @@ def maximize_asimov_likelihood( self, return_nll: bool = True, expected: ExpectationType = ExpectationType.observed, - test_statistics: Text = "qtilde", + test_statistics: str = "qtilde", initial_muhat_value: Optional[float] = None, par_bounds: Optional[List[Tuple[float, float]]] = None, - statistical_model_options: Optional[Dict[Text, Dict]] = None, + statistical_model_options: Optional[Dict[str, Dict]] = None, **optimiser_options, ) -> Tuple[float, float]: r""" diff --git a/src/spey/hypothesis_testing/asymptotic_calculator.py b/src/spey/hypothesis_testing/asymptotic_calculator.py index 34505eb..7e2ec90 100644 --- a/src/spey/hypothesis_testing/asymptotic_calculator.py +++ b/src/spey/hypothesis_testing/asymptotic_calculator.py @@ -13,31 +13,31 @@ def __dir__(): def compute_asymptotic_confidence_level( - sqrt_qmuA: float, delta_test_statistic: float, test_stat: Text = "qtilde" + sqrt_qmuA: float, delta_test_statistic: float, test_stat: str = "qtilde" ) -> Tuple[List[float], List[float]]: r""" Compute p values i.e. :math:`p_{s+b}`, :math:`p_b` and :math:`p_s` - + .. math:: - + p_{s+b}&=& \int_{-\infty}^{-\sqrt{q_{\mu,A}} - \Delta q_\mu} \mathcal{N}(x| 0, 1) dx \\ p_{b}&=& \int_{-\infty}^{-\Delta q_\mu} \mathcal{N}(x| 0, 1) dx \\ p_{s} &=& p_{s+b}/ p_{b} where :math:`q_\mu` stands for the test statistic and A stands for Assimov. - + .. math:: - - \Delta q_\mu = \begin{cases} + + \Delta q_\mu = \begin{cases} \sqrt{q_{\mu}} - \sqrt{q_{\mu,A}}, & \text{if}\ \sqrt{q_{\mu}} \leq \sqrt{q_{\mu,A}} \\ - \frac{\sqrt{q_{\mu}} - \sqrt{q_{\mu,A}}}{2\ \sqrt{q_{\mu,A}}}, & \text{otherwise} + \frac{\sqrt{q_{\mu}} - \sqrt{q_{\mu,A}}}{2\ \sqrt{q_{\mu,A}}}, & \text{otherwise} \end{cases} - Note that the CDF has a cutoff at :math:`-\sqrt{q_{\mu,A}}`, hence if + Note that the CDF has a cutoff at :math:`-\sqrt{q_{\mu,A}}`, hence if :math:`p_{s\ {\rm or}\ s+b} < -\sqrt{q_{\mu,A}}` p-value will not be computed. .. seealso:: - + eq. 66 of :xref:`1007.1727` Args: @@ -65,10 +65,10 @@ def compute_asymptotic_confidence_level( Returns: ``Tuple[List[float], List[float]]``: returns p-values and expected p-values. - + .. seealso:: - :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, + :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, :func:`~spey.hypothesis_testing.utils.pvalues`, :func:`~spey.hypothesis_testing.utils.expected_pvalues` """ diff --git a/src/spey/hypothesis_testing/test_statistics.py b/src/spey/hypothesis_testing/test_statistics.py index 93b94eb..2992419 100644 --- a/src/spey/hypothesis_testing/test_statistics.py +++ b/src/spey/hypothesis_testing/test_statistics.py @@ -62,7 +62,7 @@ def qmu( ) -> float: r""" Test statistic :math:`q_{\mu}`, see eq. (54) of :xref:`1007.1727` - + .. math:: q_{\mu} = \begin{cases} @@ -117,7 +117,7 @@ def q0( def get_test_statistic( - test_stat: Text, + test_stat: str, ) -> Callable[[float, float, float, Callable[[float], float]], float]: r""" Retreive the test statistic function @@ -171,7 +171,7 @@ def compute_teststatistics( logpdf: Callable[[float], float], maximum_asimov_likelihood: Tuple[float, float], asimov_logpdf: Callable[[float], float], - teststat: Text, + teststat: str, ) -> Tuple[float, float, float]: r""" Compute test statistics diff --git a/src/spey/hypothesis_testing/toy_calculator.py b/src/spey/hypothesis_testing/toy_calculator.py index 0d6d1ef..5606e0c 100644 --- a/src/spey/hypothesis_testing/toy_calculator.py +++ b/src/spey/hypothesis_testing/toy_calculator.py @@ -16,7 +16,7 @@ def compute_toy_confidence_level( signal_like_test_statistic: List[float], background_like_test_statistic: List[float], test_statistic: float, - test_stat: Text = "qtilde", + test_stat: str = "qtilde", ) -> Tuple[List[float], List[float]]: r""" Compute confidence limits i.e. :math:`CL_{s+b}`, :math:`CL_b` and :math:`CL_s` diff --git a/src/spey/hypothesis_testing/utils.py b/src/spey/hypothesis_testing/utils.py index c1b5b34..923d31c 100644 --- a/src/spey/hypothesis_testing/utils.py +++ b/src/spey/hypothesis_testing/utils.py @@ -35,7 +35,7 @@ def pvalues( signal + background and background-only model hypotheses. .. math:: - + p_{s+b}&=& \int_{-\infty}^{-\sqrt{q_{\mu,A}} - \Delta q_\mu} \mathcal{N}(x| 0, 1) dx \\ p_{b}&=& \int_{-\infty}^{-\Delta q_\mu} \mathcal{N}(x| 0, 1) dx \\ p_{s} &=& p_{s+b}/ p_{b} @@ -53,10 +53,10 @@ def pvalues( ``Tuple[float, float, float]``: The p-values for the test statistic corresponding to the :math:`CL_{s+b}`, :math:`CL_{b}`, and :math:`CL_{s}`. - + .. seealso:: - - :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, + + :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, :func:`~spey.hypothesis_testing.asymptotic_calculator.compute_asymptotic_confidence_level`, :func:`~spey.hypothesis_testing.utils.expected_pvalues` """ @@ -81,12 +81,12 @@ def expected_pvalues( background only hypothesis :math:`\mu=0` at :math:`(-2,-1,0,1,2)\sigma`. .. math:: - + p_{s+b}&=& \int_{-\infty}^{-\sqrt{q_{\mu,A}} - N\sigma} \mathcal{N}(x| 0, 1) dx \\ p_{b}&=& \int_{-\infty}^{-N\sigma} \mathcal{N}(x| 0, 1) dx \\ p_{s} &=& p_{s+b}/ p_{b} - where :math:`q_\mu` stands for the test statistic and A stands for Assimov. + where :math:`q_\mu` stands for the test statistic and A stands for Assimov. :math:`N\sigma\in[-2,-1,0,1,2]`. Args: @@ -99,10 +99,10 @@ def expected_pvalues( ``List[List]``: The p-values for the test statistic corresponding to the :math:`CL_{s+b}`, :math:`CL_{b}`, and :math:`CL_{s}`. - + .. seealso:: - - :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, + + :func:`~spey.hypothesis_testing.test_statistics.compute_teststatistics`, :func:`~spey.hypothesis_testing.asymptotic_calculator.compute_asymptotic_confidence_level`, :func:`~spey.hypothesis_testing.utils.pvalues` """ diff --git a/src/spey/interface/__init__.py b/src/spey/interface/__init__.py index b481f6a..a0a8b3a 100644 --- a/src/spey/interface/__init__.py +++ b/src/spey/interface/__init__.py @@ -1 +1 @@ -__all__ = ["statistical_model"] \ No newline at end of file +__all__ = ["statistical_model"] diff --git a/src/spey/interface/statistical_model.py b/src/spey/interface/statistical_model.py index a3305be..02d0ff5 100644 --- a/src/spey/interface/statistical_model.py +++ b/src/spey/interface/statistical_model.py @@ -60,7 +60,7 @@ class StatisticalModel(HypothesisTestingBase): def __init__( self, backend: BackendBase, - analysis: Text, + analysis: str, xsection: float = np.nan, ntoys: int = 1000, ): @@ -68,7 +68,7 @@ def __init__( self._backend: BackendBase = backend self.xsection: float = xsection """Value of the cross section, unit is defined by the user.""" - self.analysis: Text = analysis + self.analysis: str = analysis """Unique identifier as analysis name""" super().__init__(ntoys=ntoys) @@ -92,7 +92,7 @@ def backend(self) -> BackendBase: return self._backend @property - def backend_type(self) -> Text: + def backend_type(self) -> str: """Return type of the backend""" return getattr(self.backend, "name", self.backend.__class__.__name__) @@ -117,7 +117,7 @@ def is_chi_square_calculator_available(self) -> bool: return True @property - def available_calculators(self) -> List[Text]: + def available_calculators(self) -> List[str]: """ Retruns available calculator names i.e. ``'toy'``, ``'asymptotic'`` and ``'chi_square'``. @@ -311,7 +311,7 @@ def likelihood( def generate_asimov_data( self, expected: ExpectationType = ExpectationType.observed, - test_statistic: Text = "qtilde", + test_statistic: str = "qtilde", init_pars: Optional[List[float]] = None, par_bounds: Optional[List[Tuple[float, float]]] = None, **kwargs, @@ -386,7 +386,7 @@ def asimov_likelihood( poi_test: float = 1.0, expected: ExpectationType = ExpectationType.observed, return_nll: bool = True, - test_statistics: Text = "qtilde", + test_statistics: str = "qtilde", init_pars: Optional[List[float]] = None, par_bounds: Optional[List[Tuple[float, float]]] = None, **kwargs, @@ -517,7 +517,7 @@ def maximize_asimov_likelihood( self, return_nll: bool = True, expected: ExpectationType = ExpectationType.observed, - test_statistics: Text = "qtilde", + test_statistics: str = "qtilde", init_pars: Optional[List[float]] = None, par_bounds: Optional[List[Tuple[float, float]]] = None, **kwargs, @@ -790,7 +790,7 @@ def statistical_model_wrapper( def wrapper( *args, - analysis: Text = "__unknown_analysis__", + analysis: str = "__unknown_analysis__", xsection: float = np.nan, ntoys: int = 1000, **kwargs, diff --git a/src/spey/system/webutils.py b/src/spey/system/webutils.py index c766aa6..1596eed 100644 --- a/src/spey/system/webutils.py +++ b/src/spey/system/webutils.py @@ -21,9 +21,9 @@ class ConnectionError(Exception): def get_bibtex( home: Literal["inspire/arxiv", "inspire/doi", "doi", "zenodo"], - identifier: Text, + identifier: str, timeout: int = 5, -) -> Text: +) -> str: """ Retreive BibTex information from InspireHEP, Zenodo or DOI.org