Skip to content

Commit

Permalink
Minuit.interactive outside of Jupyter notebooks (#1056)
Browse files Browse the repository at this point in the history
Implementation of a PyQt6 widget for `minuit.interactive` (Issue #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] <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Adrian Peter Krone <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Louis Varriano <[email protected]>
Co-authored-by: Hans Dembinski <[email protected]>
Co-authored-by: SamuelBorden <[email protected]>
Co-authored-by: Hans Dembinski <[email protected]>
  • Loading branch information
8 people authored Feb 6, 2025
1 parent a8acd57 commit 859f252
Show file tree
Hide file tree
Showing 13 changed files with 752 additions and 148 deletions.
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: tlambert03/setup-qt-libs@v1
- uses: astral-sh/setup-uv@v4
- run: uv pip install --system nox
- run: nox -s cov
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def pypy(session: nox.Session) -> None:


# Python-3.12 provides coverage info faster
@nox.session(python="3.12", venv_backend="uv", reuse_venv=True)
@nox.session(venv_backend="uv", reuse_venv=True)
def cov(session: nox.Session) -> None:
"""Run covage and place in 'htmlcov' directory."""
session.install("--only-binary=:all:", "-e.[test,doc]")
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ test = [
"ipywidgets",
# needed by ipywidgets >= 8.0.6
"ipykernel",
"PySide6",
"joblib",
"jacobi",
"matplotlib",
Expand All @@ -52,6 +53,8 @@ test = [
"numba-stats; platform_python_implementation=='CPython'",
"pytest",
"pytest-xdist",
"pytest-xvfb",
"pytest-qt",
"scipy",
"tabulate",
"boost_histogram",
Expand Down Expand Up @@ -101,6 +104,7 @@ pydocstyle.convention = "numpy"

[tool.ruff.lint.per-file-ignores]
"test_*.py" = ["B", "D"]
"conftest.py" = ["B", "D"]
"*.ipynb" = ["D"]
"automatic_differentiation.ipynb" = ["F821"]
"cython.ipynb" = ["F821"]
Expand Down
17 changes: 2 additions & 15 deletions src/iminuit/ipywidget.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""Interactive fitting widget for Jupyter notebooks."""

from .util import _widget_guess_initial_step, _make_finite
import warnings
import numpy as np
from typing import Dict, Any, Callable
import sys

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

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


def _make_finite(x: float) -> float:
sign = -1 if x < 0 else 1
if abs(x) == np.inf:
return sign * sys.float_info.max
return x


def _guess_initial_step(val: float, vmin: float, vmax: float) -> float:
if np.isfinite(vmin) and np.isfinite(vmax):
return 1e-2 * (vmax - vmin)
return 1e-2


def _round(x: float) -> float:
return float(f"{x:.1g}")
19 changes: 14 additions & 5 deletions src/iminuit/minuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2341,10 +2341,14 @@ def interactive(
**kwargs,
):
"""
Return fitting widget (requires ipywidgets, IPython, matplotlib).
Interactive GUI for fitting.
A fitting widget is returned which can be displayed and manipulated in a
Jupyter notebook to find good starting parameters and to debug the fit.
Starts a fitting application (requires PySide6, matplotlib) in which the
fit is visualized and the parameters can be manipulated to find good
starting parameters and to debug the fit.
When called in a Jupyter notebook (requires ipywidgets, IPython, matplotlib),
a fitting widget is returned instead, which can be displayed.
Parameters
----------
Expand All @@ -2371,9 +2375,14 @@ def interactive(
--------
Minuit.visualize
"""
from iminuit.ipywidget import make_widget

plot = self._visualize(plot)

if mutil.is_jupyter():
from iminuit.ipywidget import make_widget

else:
from iminuit.qtwidget import make_widget

return make_widget(self, plot, kwargs, raise_on_exception)

def _free_parameters(self) -> Set[str]:
Expand Down
Loading

0 comments on commit 859f252

Please sign in to comment.