Skip to content

Commit

Permalink
Fix bounds parsing in Scipy optimizers and warn when unsupported (#155)
Browse files Browse the repository at this point in the history
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
  • Loading branch information
edoaltamura and woodsp-ibm authored Apr 23, 2024
1 parent 3859d63 commit da55fb0
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
23 changes: 21 additions & 2 deletions qiskit_algorithms/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2017, 2023.
# (C) Copyright IBM 2017, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -10,7 +10,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Exception for errors raised by Algorithms module."""
"""Exception and warnings for errors raised by Algorithms module."""

from qiskit.exceptions import QiskitError

Expand All @@ -19,3 +19,22 @@ class AlgorithmError(QiskitError):
"""For Algorithm specific errors."""

pass


class QiskitAlgorithmsWarning(UserWarning):
"""Base class for warnings raised by Qiskit Algorithms."""

def __init__(self, *message):
"""Set the error message."""
super().__init__(" ".join(message))
self.message = " ".join(message)

def __str__(self):
"""Return the message."""
return repr(self.message)


class QiskitAlgorithmsOptimizersWarning(QiskitAlgorithmsWarning):
"""For Algorithm specific warnings."""

pass
18 changes: 16 additions & 2 deletions qiskit_algorithms/optimizers/scipy_optimizer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2018, 2023.
# (C) Copyright IBM 2018, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -76,6 +76,17 @@ def __init__(
self._max_evals_grouped = max_evals_grouped
self._kwargs = kwargs

if "bounds" in self._kwargs:
raise RuntimeError(
"Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not "
"supported in SciPyOptimizer constructor kwargs."
)
if "bounds" in self._options:
raise RuntimeError(
"Optimizer bounds should be passed to SciPyOptimizer.minimize() and not as "
"options."
)

def get_support_level(self):
"""Return support level dictionary"""
return {
Expand Down Expand Up @@ -116,9 +127,12 @@ def minimize(
jac: Callable[[POINT], POINT] | None = None,
bounds: list[tuple[float, float]] | None = None,
) -> OptimizerResult:
# Remove ignored parameters to suppress the warning of scipy.optimize.minimize

# Remove ignored bounds to suppress the warning of scipy.optimize.minimize
if self.is_bounds_ignored:
bounds = None

# Remove ignored gradient to suppress the warning of scipy.optimize.minimize
if self.is_gradient_ignored:
jac = None

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
Resolved the issue with multiply-defined Scipy bounds in Optimizers. In line with Scipy, now only the ``minimize()``
method supports the ``bounds`` keyword. An error is raised when trying to pass ``bounds`` in the Optimizer constructor
via ``kwargs`` or ``options``.
37 changes: 35 additions & 2 deletions test/optimizers/test_optimizers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2018, 2023.
# (C) Copyright IBM 2018, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -149,7 +149,7 @@ def test_gsls(self):
max_eval=10000,
min_step_size=1.0e-12,
)
x_0 = [1.3, 0.7, 0.8, 1.9, 1.2]
x_0 = np.asarray([1.3, 0.7, 0.8, 1.9, 1.2])

algorithm_globals.random_seed = 1
res = optimizer.minimize(rosen, x_0)
Expand Down Expand Up @@ -183,6 +183,39 @@ def callback(x):
self.run_optimizer(optimizer, max_nfev=10000)
self.assertTrue(values) # Check the list is nonempty.

def test_scipy_optimizer_parse_bounds(self):
"""
Test the parsing of bounds in SciPyOptimizer.minimize method. Verifies that the bounds are
correctly parsed and set within the optimizer object.
Raises:
AssertionError: If any of the assertions fail.
AssertionError: If a TypeError is raised unexpectedly while parsing bounds.
"""
try:
# Initialize SciPyOptimizer instance with SLSQP method
optimizer = SciPyOptimizer("SLSQP")

# Call minimize method with a simple lambda function and bounds
optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])

# Assert that "bounds" is not present in optimizer options and kwargs
self.assertFalse("bounds" in optimizer._options)
self.assertFalse("bounds" in optimizer._kwargs)

except TypeError:
# This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
self.fail(
"TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...)."
)

# Finally, expect exceptions if bounds are parsed incorrectly, i.e. differently than as in Scipy
with self.assertRaises(RuntimeError):
_ = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
with self.assertRaises(RuntimeError):
_ = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})

# ESCH and ISRES do not do well with rosen
@data(
(CRS, True),
Expand Down

0 comments on commit da55fb0

Please sign in to comment.