Skip to content

Commit

Permalink
Merge pull request #156 from scikit-hep/py312
Browse files Browse the repository at this point in the history
enh: upgrade to Python312
  • Loading branch information
jonas-eschle authored Apr 16, 2024
2 parents 9bfdd00 + 1b5696a commit 4ec799e
Show file tree
Hide file tree
Showing 11 changed files with 75 additions and 108 deletions.
21 changes: 10 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,17 @@ jobs:
os:
- ubuntu-latest
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
include:
- os: windows-latest
python-version: "3.8"
- os: windows-latest
python-version: "3.11"
- os: macos-latest
python-version: "3.8"
python-version: "3.9"
- os: macos-latest
python-version: "3.11"
python-version: "3.9"
- os: macos-14
python-version: "3.12" # old versions not supported
name: Check Python ${{ matrix.python-version }} ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
Expand All @@ -68,14 +66,15 @@ jobs:
${{ runner.os }}-pip-
- name: Install package
run: python -m pip install -e .[test] pytest-xdist # for multiprocessing

run: |
python -m pip install --upgrade pip
python -m pip install -e .[test] pytest-xdist # for multiprocessing
- name: Test package
run: python -m pytest --doctest-modules --cov=hepstats --cov-report=xml -n3
run: python -m pytest --doctest-modules --cov=hepstats --cov-report=xml -n auto

- name: Upload coverage to Codecov
if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # technically not needed, but prevents failures: https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954
Expand Down
5 changes: 2 additions & 3 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ dependencies:
- numpy
- scipy
- iminuit
- tensorflow>=2.4
- tensorflow-probability
#- zfit
#- zfit # todo: conda-forge is 0.18.1, we need 0.20.0
- asdf
- matplotlib
- pip:
- .
- zfit >=0.6.4
- zfit >=0.20.0
12 changes: 7 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ classifiers =
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Expand All @@ -43,7 +42,7 @@ install_requires =
scipy
tqdm
uhi
python_requires = >=3.8
python_requires = >=3.9
package_dir =
= src

Expand All @@ -52,8 +51,9 @@ where = src

[options.extras_require]
dev =
black
zfit
%(docs)s
%(test)s
pre-commit
docs =
matplotlib
pydata-sphinx-theme
Expand All @@ -65,7 +65,9 @@ test =
pytest
pytest-cov
pytest-runner
zfit
zfit>=0.20.0
zfit =
zfit>=0.20.0

[tool:pytest]
junit_family = xunit2
Expand Down
21 changes: 8 additions & 13 deletions src/hepstats/hypotests/hypotests_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import numpy as np

from ..utils.fit import get_nevents
from ..utils.fit import get_nevents, set_values_once
from ..utils.fit.api_check import is_valid_data, is_valid_fitresult, is_valid_loss, is_valid_minimizer, is_valid_pdf
from .parameters import POI

Expand Down Expand Up @@ -121,8 +121,7 @@ def set_params_to_bestfit(self):
"""
Set the values of the parameters in the models to the best fit values
"""
for param in self.parameters:
param.set_value(self.bestfit.params[param]["value"])
set_values_once(self.parameters, self.bestfit)

def lossbuilder(self, model, data, weights=None, oldloss=None):
"""Method to build a new loss function.
Expand Down Expand Up @@ -160,7 +159,7 @@ def lossbuilder(self, model, data, weights=None, oldloss=None):

if weights is not None:
for d, w in zip(data, weights):
d.set_weights(w)
d = d.with_weights(w)

if hasattr(oldloss, "create_new"):
loss = oldloss.create_new(model=model, data=data, constraints=self.constraints)
Expand Down Expand Up @@ -196,15 +195,11 @@ def __init__(self, input, minimizer, sampler, sample):
self._sample = sample
self._toys_loss = {}

def sampler(self, floating_params=None):
def sampler(self):
"""
Create sampler with models.
Args:
floating_params: floating parameters in the sampler
Example with `zfit`:
>>> sampler = calc.sampler(floating_params=[zfit.Parameter("mean")])
>>> sampler = calc.sampler()
"""
self.set_params_to_bestfit()
nevents = []
Expand All @@ -215,7 +210,7 @@ def sampler(self, floating_params=None):
else:
nevents.append(nevents_data)

return self._sampler(self.loss.model, nevents, floating_params)
return self._sampler(self.loss.model, nevents)

def sample(self, sampler, ntoys, poi: POI, constraints=None):
"""
Expand All @@ -230,7 +225,7 @@ def sample(self, sampler, ntoys, poi: POI, constraints=None):
Example with `zfit`:
>>> mean = zfit.Parameter("mean")
>>> sampler = calc.sampler(floating_params=[mean])
>>> sampler = calc.sampler()
>>> sample = calc.sample(sampler, 1000, POI(mean, 1.2))
Returns:
Expand Down Expand Up @@ -258,6 +253,6 @@ def toys_loss(self, parameter_name: str):
"""
if parameter_name not in self._toys_loss:
parameter = self.get_parameter(parameter_name)
sampler = self.sampler(floating_params=[parameter])
sampler = self.sampler()
self._toys_loss[parameter.name] = self.lossbuilder(self.model, sampler)
return self._toys_loss[parameter_name]
18 changes: 10 additions & 8 deletions src/hepstats/hypotests/toyutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import warnings
from collections.abc import Callable
from contextlib import ExitStack
from pathlib import Path

import asdf
import numpy as np
import zfit.param
from tqdm.auto import tqdm

from ..utils import base_sample, base_sampler, pll
Expand Down Expand Up @@ -217,7 +217,7 @@ def ntoys(self, poigen: POI, poieval: POIarray) -> int:
except KeyError:
return 0

def generate_and_fit_toys(
def generate_and_fit_toys( # TODO PROFILE THIS
self,
ntoys: int,
poigen: POI,
Expand Down Expand Up @@ -263,6 +263,7 @@ def generate_and_fit_toys(
ntrials = 0

progressbar = tqdm(total=ntoys)
minimum = None

for i in range(ntoys):
ntrials += 1
Expand All @@ -280,13 +281,12 @@ def generate_and_fit_toys(
)
param_dict = next(samples)

with ExitStack() as stack:
for param, value in param_dict.items():
stack.enter_context(param.set_value(value))

with zfit.param.set_values(param_dict):
for _ in range(2):
try:
minimum = minimizer.minimize(loss=toys_loss)
minimum = minimizer.minimize(
loss=toys_loss
) # TODO: , init=minimum use previous minimum as starting point for parameter uncertainties
converged = minimum.converged
if converged:
break
Expand All @@ -301,7 +301,9 @@ def generate_and_fit_toys(
msg = f"{nfailures} out of {ntrials} fits failed or didn't converge."
warnings.warn(msg, FitFailuresWarning, stacklevel=2)
continue

if minimum is None:
msg = "No minimum found."
raise RuntimeError(msg)
bestfit[i] = minimum.params[param]["value"]
nll_bestfit[i] = pll(minimizer, toys_loss, POI(param, bestfit[i]))

Expand Down
10 changes: 9 additions & 1 deletion src/hepstats/utils/fit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
from .diverse import get_value, eval_pdf, pll, array2dataset, get_nevents, set_values
from .diverse import (
get_value,
eval_pdf,
pll,
array2dataset,
get_nevents,
set_values,
set_values_once,
)
from .sampling import base_sampler, base_sample
18 changes: 15 additions & 3 deletions src/hepstats/utils/fit/diverse.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from contextlib import ExitStack, contextmanager
from contextlib import ExitStack, contextmanager, suppress

import numpy as np

Expand All @@ -14,6 +14,17 @@ def get_value(value):
return np.array(value)


def set_values_once(params, values):
with suppress(ImportError):
import zfit

return zfit.param.set_values(params, values) # more efficient

for p, v in zip(params, values):
p.set_value(v)
return None


def eval_pdf(model, x, params=None, allow_extended=False):
"""Compute pdf of model at a given point x and for given parameters values"""

Expand All @@ -33,8 +44,9 @@ def pdf(model, x):
return pdf(model, x)


def pll(minimizer, loss, pois) -> float:
def pll(minimizer, loss, pois, init=None) -> float:
"""Compute minimum profile likelihood for fixed given parameters values."""
del init # unused currently

with ExitStack() as stack:
for p in pois:
Expand All @@ -43,7 +55,7 @@ def pll(minimizer, loss, pois) -> float:
param.floating = False

if any(param_loss.floating for param_loss in loss.get_params()):
minimum = minimizer.minimize(loss=loss)
minimum = minimizer.minimize(loss=loss) # TODO: add init?
value = minimum.fmin
else:
value = get_value(loss.value())
Expand Down
32 changes: 7 additions & 25 deletions src/hepstats/utils/fit/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
from .diverse import get_value


def base_sampler(models, nevents, floating_params=None):
def base_sampler(models, nevents):
"""
Creates samplers from models.
Args:
models (list(model)): models to sample
nevents (list(int)): number of in each sampler
floating_params (list(parameter), optionnal): floating parameter in the samplers
Returns:
Samplers
Expand All @@ -24,24 +23,10 @@ def base_sampler(models, nevents, floating_params=None):
assert all(is_valid_pdf(m) for m in models)
assert len(nevents) == len(models)

if floating_params:
floating_params_names = [f.name for f in floating_params]

samplers = []
fixed_params = []
for m in models:

def to_fix(p):
if floating_params:
return p.name in floating_params_names
else:
return False

fixed = [p for p in m.get_params() if not to_fix(p)]
fixed_params.append(fixed)

for i, (m, p) in enumerate(zip(models, fixed_params)):
sampler = m.create_sampler(n=nevents[i], fixed_params=p)
for i, m in enumerate(models):
sampler = m.create_sampler(n=nevents[i])
samplers.append(sampler)

return samplers
Expand Down Expand Up @@ -72,13 +57,10 @@ def base_sample(samplers, ntoys, parameter=None, value=None, constraints=None):
continue

for i in range(ntoys):
if not (parameter is None or value is None):
with parameter.set_value(value):
for s in samplers:
s.resample()
else:
for s in samplers:
s.resample()
params = None if parameter is None or value is None else {parameter: value}

for s in samplers:
s.resample(params=params)

if constraints is not None:
yield {param: value[i] for param, value in sampled_constraints.items()}
Expand Down
2 changes: 1 addition & 1 deletion tests/hypotests/test_calculators.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def test_frequentist_calculator_one_poi(constraint):
assert calc.ntoysnull == 100
assert calc.ntoysalt == 100

samplers = calc.sampler(floating_params=[mean])
samplers = calc.sampler()
assert all(is_valid_data(s) for s in samplers)
loss = calc.toys_loss(mean.name)
assert is_valid_loss(loss)
Loading

0 comments on commit 4ec799e

Please sign in to comment.