Skip to content

Commit 859f252

Browse files
adryyanpre-commit-ci[bot]Adrian Peter Kronedependabot[bot]lvarriano
authored
Minuit.interactive outside of Jupyter notebooks (scikit-hep#1056)
Implementation of a PyQt6 widget for `minuit.interactive` (Issue scikit-hep#771 ). In order to make the plotting work properly, I changed the API for the `visualize` methods of the build-in cost functions so that they optionally accept matplotlib `Axes` objects (and for `CostSum`s a `Figure`). If these arguments don't receive a value, `pyplot` is used. This should keep the disruption of existing code as minimal as I could manage. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Adrian Peter Krone <adrian.krone@ptoton.me> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Louis Varriano <139183994+lvarriano@users.noreply.github.com> Co-authored-by: Hans Dembinski <HDembinski@users.noreply.github.com> Co-authored-by: SamuelBorden <sjborden@uw.edu> Co-authored-by: Hans Dembinski <hans.dembinski@gmail.com>
1 parent a8acd57 commit 859f252

13 files changed

+752
-148
lines changed

.github/workflows/coverage.yml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ jobs:
3232
- uses: actions/setup-python@v5
3333
with:
3434
python-version: "3.12"
35+
- uses: tlambert03/setup-qt-libs@v1
3536
- uses: astral-sh/setup-uv@v4
3637
- run: uv pip install --system nox
3738
- run: nox -s cov

noxfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def pypy(session: nox.Session) -> None:
6767

6868

6969
# Python-3.12 provides coverage info faster
70-
@nox.session(python="3.12", venv_backend="uv", reuse_venv=True)
70+
@nox.session(venv_backend="uv", reuse_venv=True)
7171
def cov(session: nox.Session) -> None:
7272
"""Run covage and place in 'htmlcov' directory."""
7373
session.install("--only-binary=:all:", "-e.[test,doc]")

pyproject.toml

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ test = [
4444
"ipywidgets",
4545
# needed by ipywidgets >= 8.0.6
4646
"ipykernel",
47+
"PySide6",
4748
"joblib",
4849
"jacobi",
4950
"matplotlib",
@@ -52,6 +53,8 @@ test = [
5253
"numba-stats; platform_python_implementation=='CPython'",
5354
"pytest",
5455
"pytest-xdist",
56+
"pytest-xvfb",
57+
"pytest-qt",
5558
"scipy",
5659
"tabulate",
5760
"boost_histogram",
@@ -101,6 +104,7 @@ pydocstyle.convention = "numpy"
101104

102105
[tool.ruff.lint.per-file-ignores]
103106
"test_*.py" = ["B", "D"]
107+
"conftest.py" = ["B", "D"]
104108
"*.ipynb" = ["D"]
105109
"automatic_differentiation.ipynb" = ["F821"]
106110
"cython.ipynb" = ["F821"]

src/iminuit/ipywidget.py

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Interactive fitting widget for Jupyter notebooks."""
22

3+
from .util import _widget_guess_initial_step, _make_finite
34
import warnings
45
import numpy as np
56
from typing import Dict, Any, Callable
6-
import sys
77

88
with warnings.catch_warnings():
99
# ipywidgets produces deprecation warnings through use of internal APIs :(
@@ -148,7 +148,7 @@ class Parameter(widgets.HBox):
148148
def __init__(self, minuit, par):
149149
val = minuit.values[par]
150150
vmin, vmax = minuit.limits[par]
151-
step = _guess_initial_step(val, vmin, vmax)
151+
step = _widget_guess_initial_step(val, vmin, vmax)
152152
vmin2 = vmin if np.isfinite(vmin) else val - 100 * step
153153
vmax2 = vmax if np.isfinite(vmax) else val + 100 * step
154154

@@ -277,18 +277,5 @@ def reset(self, value, limits=None):
277277
return widgets.HBox([out, ui])
278278

279279

280-
def _make_finite(x: float) -> float:
281-
sign = -1 if x < 0 else 1
282-
if abs(x) == np.inf:
283-
return sign * sys.float_info.max
284-
return x
285-
286-
287-
def _guess_initial_step(val: float, vmin: float, vmax: float) -> float:
288-
if np.isfinite(vmin) and np.isfinite(vmax):
289-
return 1e-2 * (vmax - vmin)
290-
return 1e-2
291-
292-
293280
def _round(x: float) -> float:
294281
return float(f"{x:.1g}")

src/iminuit/minuit.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -2341,10 +2341,14 @@ def interactive(
23412341
**kwargs,
23422342
):
23432343
"""
2344-
Return fitting widget (requires ipywidgets, IPython, matplotlib).
2344+
Interactive GUI for fitting.
23452345
2346-
A fitting widget is returned which can be displayed and manipulated in a
2347-
Jupyter notebook to find good starting parameters and to debug the fit.
2346+
Starts a fitting application (requires PySide6, matplotlib) in which the
2347+
fit is visualized and the parameters can be manipulated to find good
2348+
starting parameters and to debug the fit.
2349+
2350+
When called in a Jupyter notebook (requires ipywidgets, IPython, matplotlib),
2351+
a fitting widget is returned instead, which can be displayed.
23482352
23492353
Parameters
23502354
----------
@@ -2371,9 +2375,14 @@ def interactive(
23712375
--------
23722376
Minuit.visualize
23732377
"""
2374-
from iminuit.ipywidget import make_widget
2375-
23762378
plot = self._visualize(plot)
2379+
2380+
if mutil.is_jupyter():
2381+
from iminuit.ipywidget import make_widget
2382+
2383+
else:
2384+
from iminuit.qtwidget import make_widget
2385+
23772386
return make_widget(self, plot, kwargs, raise_on_exception)
23782387

23792388
def _free_parameters(self) -> Set[str]:

0 commit comments

Comments
 (0)