qiskit-community / qiskit-algorithms

A library of quantum algorithms for Qiskit.
https://qiskit-community.github.io/qiskit-algorithms/
Apache License 2.0
111 stars 54 forks source link

Fix bounds parsing in Scipy optimizers and warn when unsupported #155

Closed edoaltamura closed 5 months ago

edoaltamura commented 7 months ago

Summary

Fixes the parsing of bounds to the Scipy optimizers, which previously raised the error below.

TypeError: scipy.optimize._minimize.minimize() got multiple values for keyword argument 'bounds'

The present PR fixes this behaviour documented in https://github.com/qiskit-community/qiskit-machine-learning/issues/570 and further discussed in https://github.com/qiskit-community/qiskit-algorithms/issues/57.

Details and comments

This PR contains two updates. The first one gathers the bounds from _options or _kwargs if present, then copies them ont bounds, finally it deletes the key in the dictionaries, avoiding clashes in the https://github.com/edoaltamura/qiskit-algorithms/blob/77abbcbbeedba111cfde75df0cfea2940c9790dc/qiskit_algorithms/optimizers/scipy_optimizer.py#L165. The changes are below: https://github.com/edoaltamura/qiskit-algorithms/blob/77abbcbbeedba111cfde75df0cfea2940c9790dc/qiskit_algorithms/optimizers/scipy_optimizer.py#L121-L127

The second update returns a warning when trying to assign a non-None bound value with an optimizer method that doesn't support it. The class QiskitAlgorithmsOptimizersWarning is defined in: https://github.com/edoaltamura/qiskit-algorithms/blob/77abbcbbeedba111cfde75df0cfea2940c9790dc/qiskit_algorithms/exceptions.py#L24

For the bounds parsing, the derived class QiskitAlgorithmsWarning is defined and used: https://github.com/edoaltamura/qiskit-algorithms/blob/77abbcbbeedba111cfde75df0cfea2940c9790dc/qiskit_algorithms/exceptions.py#L36

✅ I have added the tests to cover my changes. ✅ I have updated the documentation accordingly. ✅ I have read the CONTRIBUTING document.

CLAassistant commented 7 months ago

CLA assistant check
All committers have signed the CLA.

woodsp-ibm commented 7 months ago

This will need a test case that used to fail that does not know so it can be tested around things now work as expected and continue to do so. And if (see discussion below) it raises a UserWarning a test to check that happens as expected.


Discussion:

As the optimizers were historically they called scipy.optimize.minimize - well they still do for the scipy ones. To have these as objects that could be created and passed, rather than have to deal with functions and partials, that would not have fitted with the design when these were first created, values that needed to end up in the options field, ie optimizer specific values beyond the set of "standard" values supplied through the parameters to minimize were listed as the option fields by the specific optimizer subclass so they could be taken out of the parameters from the optimizer init and bundled into the options. So options is supposed to be only what can go in options for scipy. And that is not bounds. Also bounds was not exposed as something that could be directly set by the user when creating an optimizer - more it came through or was derived from the problem. VQE for instance calls the optimizer with bounds as it gets from the ansatz. Now I can see the value in allowing a user to set the bounds - and perhaps if set overrides anything an algorithm might pass.

So to me bounds ought not to be passed in options as that should strictly just be the scipy minimize options. I think options was exposed here as not all possible options had been exposed as parameters, since initially we made a choice on the ones we expected to be used for the problems that were then being addressed. Having bounds as kwargs in the init seems ok, then does one choose to enforce that as the bounds if an algorithm is setting it via some other mechanism, like VQE does, when it calls minimize. Or let bounds on the minimize override that a user might pass in via kwargs - or have another parameter that you can set to say which overrides which.

Last I am not sure I would want to check if bounds is supported and raise a user error (you don;t check other stuff like jac and it seems like if we did it would be a rabbit hole to get lost in). This scipy base class was designed to be more flexible and allow you to pass the method which could be a new optimizer added to scipy. In which case the lookup will default to unsupported even if its supported. For the methods we know about of course its possible, my take would be to not to have any checks and let it fail, if that's what happens, in the place that knows best i.e. scipy.

Dealing with the bounds need some thought/discussion - hence the issue raised i.e. #57. Maybe a simple change could tide things over for now and allow something. @Cryoris You were more involved in this ScipyOptimizer than I, any thoughts?

edoaltamura commented 7 months ago

Thanks, @woodsp-ibm, I'll work on the two tests you suggested.

coveralls commented 7 months ago

Pull Request Test Coverage Report for Build 8804213726

Details


Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit_algorithms/exceptions.py 6 9 66.67%
<!-- Total: 10 13 76.92% -->
Files with Coverage Reduction New Missed Lines %
qiskit_algorithms/optimizers/gsls.py 1 76.86%
<!-- Total: 1 -->
Totals Coverage Status
Change from base Build 8634991708: -0.04%
Covered Lines: 6496
Relevant Lines: 7169

💛 - Coveralls
edoaltamura commented 7 months ago

In the fork, options can accept bounds, however, I could remove it to stay fully consistent with Scipy. I've also allowed the bounds argument in minimize to override bounds from __init__; a test for this is included. When bounds are used together with a method that doesn't support them, I'm raising a warning and setting bounds=None - this is a gentler interface than raising an error, but that's possible too.

Cryoris commented 7 months ago

I agree with @woodsp-ibm above, we should follow SciPy's interface as closely as possible, which includes the same usage of options. However, having kwargs in the initializer indeed currently enables passing bounds in two places. It would be good to keep this fix of your PR, though I would argue that the bounds passed in minimize should not overwrite what was in kwargs.

edoaltamura commented 7 months ago

I have implemented the suggested changes and adapted the bounds parsing to be as close as possible to the Scipy usage. As in Scipy, bounds can only be parsed in the minimize() method. If parsed in __init__() now exceptions are raised and a message suggests the intended use.

edoaltamura commented 7 months ago

@woodsp-ibm are there any further changes you would suggest for this PR?

edoaltamura commented 7 months ago

I have applied the suggested changes and the PR is now ready. What's your feedback, and what are the next steps?

woodsp-ibm commented 7 months ago

Whatever this nets out as, since its a change that affects behavior from a end user perspective - well at least in terms of the error they get is more meaningful, hopefully, this should have a release note too I think.

edoaltamura commented 6 months ago

I agree that self._bounds is no longer necessary in this implementation. I've removed it in both instances and added release notes as suggested. Once the CI has run, this PR is ready for merge. I look forward to your advice on the next steps.