Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to pass options to SciPy minimizers #1060

Merged
merged 17 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions src/iminuit/minuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ def scipy(
hess: Any = None,
hessp: Any = None,
constraints: Iterable = None,
options: Dict[str, Any] = {},
lvarriano marked this conversation as resolved.
Show resolved Hide resolved
) -> "Minuit":
"""
Minimize with SciPy algorithms.
Expand Down Expand Up @@ -1014,6 +1015,10 @@ def scipy(
as the original fcn, see hess parameter for details. No parameters may be
omitted in the signature, even if those parameters are not used in the
constraint.
options : dict, optional
A dictionary of solver options to pass to the SciPy minimizer through the
`options` parameter of :func:`scipy.optimize.minimize`. See each solver
method for the options it accepts.

Notes
-----
Expand All @@ -1027,6 +1032,9 @@ def scipy(
criterion is evaluated only after the original algorithm already stopped. This
means that usually SciPy minimizers will use more iterations than Migrad and
the tolerance :attr:`tol` has no effect on SciPy minimizers.

You can specify convergence tolerance and other options for the SciPy minimizers
through the `options` parameter.
"""
try:
from scipy.optimize import (
Expand All @@ -1043,6 +1051,9 @@ def scipy(
if ncall is None:
ncall = self._migrad_maxcall()

if type(options) is not dict:
raise ValueError("options must be a dictionary")

lvarriano marked this conversation as resolved.
Show resolved Hide resolved
cfree = ~np.array(self.fixed[:], dtype=bool)
cpar = np.array(self.values[:])
no_fixed_parameters = np.all(cfree)
Expand Down Expand Up @@ -1224,18 +1235,29 @@ def __call__(self, par, v):
else:
method = "BFGS"

# copy to avoid mutable problems
scipy_options = dict(options)
lvarriano marked this conversation as resolved.
Show resolved Hide resolved

# attempt to set default number of function evaluations if not provided
# various workarounds for API inconsistencies in scipy.optimize.minimize
options = {"maxiter": ncall}
if "maxiter" not in options:
scipy_options["maxiter"] = ncall
if method in (
"Nelder-Mead",
"Powell",
):
options["maxfev"] = ncall
del options["maxiter"]
if "maxfev" not in options:
scipy_options["maxfev"] = ncall

if "maxiter" not in options:
del scipy_options["maxiter"]

if method in ("L-BFGS-B", "TNC"):
options["maxfun"] = ncall
del options["maxiter"]
if "maxfun" not in options:
scipy_options["maxfun"] = ncall

if "maxiter" not in options:
del scipy_options["maxiter"]
HDembinski marked this conversation as resolved.
Show resolved Hide resolved

if method in ("COBYLA", "SLSQP", "trust-constr") and constraints is None:
constraints = ()
Expand All @@ -1255,7 +1277,7 @@ def __call__(self, par, v):
hess=hess,
hessp=hessp,
constraints=constraints,
options=options,
options=scipy_options,
)
if self.print_level > 0:
print(r)
Expand Down
6 changes: 6 additions & 0 deletions tests/test_scipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ def test_bad_constraint():
m.scipy(constraints=[{}])


def test_bad_options():
m = Minuit(fcn, a=1, b=2)
with pytest.raises(ValueError):
m.scipy(options=[])


lvarriano marked this conversation as resolved.
Show resolved Hide resolved
def test_high_print_level(capsys):
m = Minuit(fcn, a=1, b=2)
m.scipy()
Expand Down