• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

qiskit-community / qiskit-optimization / 24491703353

01 Apr 2026 05:06AM UTC coverage: 91.999%. Remained the same
24491703353

push

github

web-flow
Fix lint (#715)

6048 of 6574 relevant lines covered (92.0%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.21
/qiskit_optimization/optimizers/scipy_optimizer.py
1
# This code is part of a Qiskit project.
2
#
3
# (C) Copyright IBM 2018, 2026.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""Wrapper class of scipy.optimize.minimize."""
14

15
from __future__ import annotations
1✔
16

17
from collections.abc import Callable
1✔
18
from typing import Any
1✔
19

20
import numpy as np
1✔
21
from scipy.optimize import minimize
1✔
22

23
from ..utils.validation import validate_min
1✔
24
from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT
1✔
25

26

27
class SciPyOptimizer(Optimizer):
1✔
28
    """A general Qiskit Optimizer wrapping scipy.optimize.minimize.
29

30
    For further detail, please refer to
31
    https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
32
    """
33

34
    _bounds_support_methods = {"l-bfgs-b", "tnc", "slsqp", "powell", "trust-constr"}
1✔
35
    _gradient_support_methods = {
1✔
36
        "cg",
37
        "bfgs",
38
        "newton-cg",
39
        "l-bfgs-b",
40
        "tnc",
41
        "slsqp",
42
        "dogleg",
43
        "trust-ncg",
44
        "trust-krylov",
45
        "trust-exact",
46
        "trust-constr",
47
    }
48

49
    def __init__(
1✔
50
        self,
51
        method: str | Callable,
52
        options: dict[str, Any] | None = None,
53
        max_evals_grouped: int = 1,
54
        **kwargs,
55
    ):
56
        """
57
        Args:
58
            method: Type of solver.
59
            options: A dictionary of solver options.
60
            kwargs: additional kwargs for scipy.optimize.minimize.
61
            max_evals_grouped: Max number of default gradient evaluations performed simultaneously.
62
        """
63
        self._method = method.lower() if isinstance(method, str) else method
1✔
64
        # Set support level
65
        if self._method in self._bounds_support_methods:
1✔
66
            self._bounds_support_level = OptimizerSupportLevel.supported
1✔
67
        else:
68
            self._bounds_support_level = OptimizerSupportLevel.ignored
1✔
69
        if self._method in self._gradient_support_methods:
1✔
70
            self._gradient_support_level = OptimizerSupportLevel.supported
1✔
71
        else:
72
            self._gradient_support_level = OptimizerSupportLevel.ignored
1✔
73
        self._initial_point_support_level = OptimizerSupportLevel.required
1✔
74

75
        self._options = options if options is not None else {}
1✔
76
        validate_min("max_evals_grouped", max_evals_grouped, 1)
1✔
77
        self._max_evals_grouped = max_evals_grouped
1✔
78
        self._kwargs = kwargs
1✔
79

80
        if "bounds" in self._kwargs:
1✔
81
            raise RuntimeError(
1✔
82
                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not  "
83
                "supported in SciPyOptimizer constructor kwargs."
84
            )
85
        if "bounds" in self._options:
1✔
86
            raise RuntimeError(
1✔
87
                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and not as "
88
                "options."
89
            )
90

91
    def get_support_level(self):
1✔
92
        """Return support level dictionary"""
93
        return {
×
94
            "gradient": self._gradient_support_level,
95
            "bounds": self._bounds_support_level,
96
            "initial_point": self._initial_point_support_level,
97
        }
98

99
    @property
1✔
100
    def settings(self) -> dict[str, Any]:
1✔
101
        options = self._options.copy()
1✔
102
        if hasattr(self, "_OPTIONS"):
1✔
103
            # all _OPTIONS should be keys in self._options, but add a failsafe here
104
            attributes = [
1✔
105
                option
106
                for option in self._OPTIONS  # pylint: disable=no-member
107
                if option in options.keys()
108
            ]
109

110
            settings = {attr: options.pop(attr) for attr in attributes}
1✔
111
        else:
112
            settings = {}
1✔
113

114
        settings["max_evals_grouped"] = self._max_evals_grouped
1✔
115
        settings["options"] = options
1✔
116
        settings.update(self._kwargs)
1✔
117

118
        # the subclasses don't need the "method" key as the class type specifies the method
119
        if self.__class__ == SciPyOptimizer:
1✔
120
            settings["method"] = self._method
1✔
121

122
        return settings
1✔
123

124
    def minimize(
1✔
125
        self,
126
        fun: Callable[[POINT], float],
127
        x0: POINT,
128
        jac: Callable[[POINT], POINT] | None = None,
129
        bounds: list[tuple[float, float]] | None = None,
130
    ) -> OptimizerResult:
131
        # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
132
        if self.is_bounds_ignored:
1✔
133
            bounds = None
1✔
134

135
        # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
136
        if self.is_gradient_ignored:
1✔
137
            jac = None
1✔
138

139
        if self.is_gradient_supported and jac is None and self._max_evals_grouped > 1:
1✔
140
            if "eps" in self._options:
1✔
141
                epsilon = self._options["eps"]
×
142
            else:
143
                epsilon = (
1✔
144
                    1e-8
145
                    if self._method in {"l-bfgs-b", "tnc"}
146
                    else np.sqrt(np.finfo(float).eps)  # pylint: disable=no-member
147
                )
148
            jac = Optimizer.wrap_function(
1✔
149
                Optimizer.gradient_num_diff, (fun, epsilon, self._max_evals_grouped)
150
            )
151

152
        # Workaround for L_BFGS_B because it does not accept np.ndarray.
153
        # See https://github.com/Qiskit/qiskit/pull/6373.
154
        if jac is not None and self._method == "l-bfgs-b":
1✔
155
            jac = self._wrap_gradient(jac)
×
156

157
        # Starting in scipy 1.9.0 maxiter is deprecated and maxfun (added in 1.5.0)
158
        # should be used instead
159
        swapped_deprecated_args = False
1✔
160
        if self._method == "tnc" and "maxiter" in self._options:
1✔
161
            swapped_deprecated_args = True
×
162
            self._options["maxfun"] = self._options.pop("maxiter")
×
163

164
        raw_result = minimize(
1✔
165
            fun=fun,
166
            x0=x0,
167
            method=self._method,
168
            jac=jac,
169
            bounds=bounds,
170
            options=self._options,
171
            **self._kwargs,
172
        )
173
        if swapped_deprecated_args:
1✔
174
            self._options["maxiter"] = self._options.pop("maxfun")
×
175

176
        result = OptimizerResult()
1✔
177
        result.x = raw_result.x
1✔
178
        result.fun = raw_result.fun
1✔
179
        result.nfev = raw_result.nfev
1✔
180
        result.njev = raw_result.get("njev", None)
1✔
181
        result.nit = raw_result.get("nit", None)
1✔
182

183
        return result
1✔
184

185
    @staticmethod
1✔
186
    def _wrap_gradient(gradient_function):
1✔
187
        def wrapped_gradient(x):
×
188
            gradient = gradient_function(x)
×
189
            if isinstance(gradient, np.ndarray):
×
190
                return gradient.tolist()
×
191
            return gradient
×
192

193
        return wrapped_gradient
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc