Skip to content

Commit

Permalink
Merge pull request #86 from zfit/pyhfcomp
Browse files Browse the repository at this point in the history
add pyhf compatibility layer
  • Loading branch information
jonas-eschle authored Oct 31, 2024
2 parents 3003009 + ce295fe commit fde5d7c
Show file tree
Hide file tree
Showing 22 changed files with 290 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Major Features and Improvements
-------------------------------
- add a RooFit compatibility layer and automatically convert losses, also inside minimizers (through ``SimpleLoss.from_any``)
- `TF-PWA <https://tf-pwa.readthedocs.io/en/latest/>`_ support for loss functions. Minimizer can directly minimize the loss function of a model.
- `pyhf <https://pyhf.readthedocs.io/en/stable/>`_ support for loss functions. Minimizer can directly minimize the loss function of a model.

Breaking changes
------------------
Expand Down
32 changes: 32 additions & 0 deletions docs/api/static/zfit_physics.pyhf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
pyhf
=======================

The pure Python package `pyhf <https://pyhf.readthedocs.io/en/stable/>`_ is a statistical fitting package for high energy physics for purely binned, templated fits. It is a Python implementation of the HistFactory schema.
The connection to zfit is done via the loss function, which can be created from a pyhf model.

Import the module with:

.. code-block:: python
import zfit_physics.pyhf as zpyhf
The loss function can be created from a ``data`` and a ``pdf`` model with

.. code-block:: python
nll = zpyhf.loss.nll_from_pyhf(data, pdf)
which optionally takes already created :py:class:~`zfit.core.interfaces.ZfitParameter` as arguments.




Loss
++++++++++++

.. automodule:: zfit_physics.pyhf.loss
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_argus.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_argus
==========

.. automodule:: zfit_physics.models.pdf_argus
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_cmsshape.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_cmsshape
=============

.. automodule:: zfit_physics.models.pdf_cmsshape
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_conv.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_conv
=========

.. automodule:: zfit_physics.models.pdf_conv
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_cruijff.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_cruijff
============

.. automodule:: zfit_physics.models.pdf_cruijff
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_erfexp.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_erfexp
===========

.. automodule:: zfit_physics.models.pdf_erfexp
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_example.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_example
============

.. automodule:: zfit_physics.models.pdf_example
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_kde.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_kde
========

.. automodule:: zfit_physics.models.pdf_kde
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_novosibirsk.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_novosibirsk
================

.. automodule:: zfit_physics.models.pdf_novosibirsk
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_relbw.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_relbw
==========

.. automodule:: zfit_physics.models.pdf_relbw
:members:
:undoc-members:
:show-inheritance:
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.models.pdf_tsallis.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf\_tsallis
============

.. automodule:: zfit_physics.models.pdf_tsallis
:members:
:undoc-members:
:show-inheritance:
24 changes: 24 additions & 0 deletions docs/api/zfit_physics.models.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
models
======

.. automodule:: zfit_physics.models
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

.. toctree::
:maxdepth: 4

zfit_physics.models.pdf_argus
zfit_physics.models.pdf_cmsshape
zfit_physics.models.pdf_conv
zfit_physics.models.pdf_cruijff
zfit_physics.models.pdf_erfexp
zfit_physics.models.pdf_example
zfit_physics.models.pdf_kde
zfit_physics.models.pdf_novosibirsk
zfit_physics.models.pdf_relbw
zfit_physics.models.pdf_tsallis
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.pdf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf
===

.. automodule:: zfit_physics.pdf
:members:
:undoc-members:
:show-inheritance:
24 changes: 24 additions & 0 deletions docs/api/zfit_physics.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
zfit\_physics package
=====================

.. automodule:: zfit_physics
:members:
:undoc-members:
:show-inheritance:

Subpackages
-----------

.. toctree::
:maxdepth: 4

zfit_physics.models
zfit_physics.unstable

Submodules
----------

.. toctree::
:maxdepth: 4

zfit_physics.pdf
7 changes: 7 additions & 0 deletions docs/api/zfit_physics.unstable.pdf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pdf
===

.. automodule:: zfit_physics.unstable.pdf
:members:
:undoc-members:
:show-inheritance:
15 changes: 15 additions & 0 deletions docs/api/zfit_physics.unstable.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
unstable
========

.. automodule:: zfit_physics.unstable
:members:
:undoc-members:
:show-inheritance:

Submodules
----------

.. toctree::
:maxdepth: 4

zfit_physics.unstable.pdf
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,17 @@ dynamic = ["version"]
[project.optional-dependencies]

tfpwa = ["tfpwa@git+https://github.com/jiangyi15/tf-pwa"]

all = ["zfit-physics[tfpwa]"]
pyhf = [
"pyhf",
]
all = ["zfit-physics[tfpwa,pyhf]"]
test = [
"pytest",
"pytest-cov",
"pytest-rerunfailures",
"pytest-xdist",
"zfit-physics[all]",
"contextlib_chdir", # backport of chdir from Python 3.11
"contextlib_chdir", # backport of chdir from Python 3.11
]
dev = [
"bumpversion>=0.5.3",
Expand Down
4 changes: 2 additions & 2 deletions src/zfit_physics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
# TODO(release): add more, Anton etc
]

__all__ = ["pdf", "unstable"]

from . import pdf, unstable

__all__ = ["pdf", "unstable"]
3 changes: 3 additions & 0 deletions src/zfit_physics/pyhf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .loss import nll_from_pyhf

__all__ = ["nll_from_pyhf"]
47 changes: 47 additions & 0 deletions src/zfit_physics/pyhf/loss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from __future__ import annotations

import numpy as np
import zfit
from zfit.util.container import convert_to_container


def nll_from_pyhf(data, pdf, *, params=None, init_pars=None, par_bounds=None, fixed_params=None, errordef=None):
"""Create a zfit loss function from a pyhf pdf and data.
Args:
data: Data to be used in the negative log-likelihood (NLL) calculation.
pdf: Probability density function model.
params: Optional; Model parameters for the fit. If None, initial parameters, bounds, and fixed status are recommended by the pdf.
init_pars: Optional; Initial parameter values. Ignored if params is provided.
par_bounds: Optional; Parameter bounds. Ignored if params is provided.
fixed_params: Optional; Boolean indicators of parameter fixed statuses. Ignored if params is provided.
errordef: Optional; Error definition, by default set to 0.5.
"""
if params is None:
init_pars = init_pars or pdf.config.suggested_init()
par_bounds = par_bounds or pdf.config.suggested_bounds()
fixed_params = fixed_params or pdf.config.suggested_fixed()

from pyhf.infer import mle

mle._validate_fit_inputs(init_pars, par_bounds, fixed_params)

params = [
zfit.Parameter(f"param_{i}", init, bound[0], bound[1], floating=not is_fixed)
for i, (init, is_fixed, bound) in enumerate(zip(init_pars, fixed_params, par_bounds))
]
else:
if init_pars is not None or par_bounds is not None or fixed_params is not None:
msg = "If `params` are given, `init_pars`, `par_bounds` and `fixed_params` must be None."
raise ValueError(msg)
params = convert_to_container(params)

if errordef is None:
errordef = 0.5

def nll_func(params, *, data=data, pdf=pdf, errordef=errordef):
params = np.asarray(params)
return mle.twice_nll(params, data, pdf) * errordef

return zfit.loss.SimpleLoss(func=nll_func, params=params, errordef=errordef, gradient="num", jit=False)
53 changes: 53 additions & 0 deletions tests/pyhf/test_pyhf_loss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import numpy as np
import pyhf
import zfit
from pyhf.simplemodels import uncorrelated_background

import zfit_physics.pyhf as zpyhf


def generate_source_static(n_bins):
"""Create the source structure for the given number of bins.
Args:
n_bins: `list` of number of bins
Returns:
source
"""
scale = 10
binning = [n_bins, -0.5, n_bins + 0.5]
data = np.random.poisson(size=n_bins, lam=scale * 120).tolist()
bkg = np.random.poisson(size=n_bins, lam=scale * 100).tolist()
bkgerr = np.random.normal(size=n_bins, loc=scale * 10.0, scale=3).tolist()
sig = np.random.poisson(size=n_bins, lam=scale * 30).tolist()

source = {
"binning": binning,
"bindata": {"data": data, "bkg": bkg, "bkgerr": bkgerr, "sig": sig},
}
return source


def test_nll_from_pyhf_simple():
nbins = 50
source = generate_source_static(nbins)

signp = source["bindata"]["sig"]
bkgnp = source["bindata"]["bkg"]
uncnp = source["bindata"]["bkgerr"]
datanp = source["bindata"]["data"]

pdf = uncorrelated_background(signp, bkgnp, uncnp)
data = datanp + pdf.config.auxdata

nll = zpyhf.loss.nll_from_pyhf(data, pdf)

minimizer = zfit.minimize.Minuit(verbosity=7)
resultz = minimizer.minimize(nll)

values, fmin = pyhf.infer.mle.fit(
data, pdf, pdf.config.suggested_init(), pdf.config.suggested_bounds(), return_fitted_val=True
)
assert np.allclose(resultz.fmin, fmin / 2, atol=1e-4)
np.testing.assert_allclose(resultz.values, values, atol = 5e-3)

0 comments on commit fde5d7c

Please sign in to comment.