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

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

Merged
merged 46 commits into from
Apr 23, 2024

Conversation

edoaltamura
Copy link
Contributor

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 qiskit-community/qiskit-machine-learning#570 and further discussed in #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
Copy link

CLAassistant commented Feb 14, 2024

CLA assistant check
All committers have signed the CLA.

@woodsp-ibm
Copy link
Member

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
Copy link
Contributor Author

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

@coveralls
Copy link

coveralls commented Feb 15, 2024

Pull Request Test Coverage Report for Build 8804213726

Details

  • 10 of 13 (76.92%) changed or added relevant lines in 2 files are covered.
  • 1 unchanged line in 1 file lost coverage.
  • Overall coverage decreased (-0.04%) to 90.612%

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

💛 - Coveralls

@edoaltamura
Copy link
Contributor Author

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
Copy link
Collaborator

Cryoris commented Feb 21, 2024

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
Copy link
Contributor Author

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.

requirements.txt Outdated Show resolved Hide resolved
edoaltamura and others added 2 commits February 24, 2024 18:59
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
@edoaltamura
Copy link
Contributor Author

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

Copy link
Member

@woodsp-ibm woodsp-ibm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Options was always meant to mean options as per the scipy optimizer in question. I guess if people are trying to get bounds in through there - maybe the options docstring could be clearer. Do we want to detect/add a warning since its a common error - is it that common? If you did that directly to scipy and added it in the options that is how it would fail. I guess you are unlikely to do that since its mnimize exposes the bounds same as we do here. It just that when constructing one of these that no bound can be specified at that point and bounds comes down to be supplied by whatever algo is calling minimize however it gets bounds.

I will not that the base class has a set_options so even with the check here, done in constructor, if it were set that way it would fail since options is only checked when first passed there and not if updated.

qiskit_algorithms/optimizers/scipy_optimizer.py Outdated Show resolved Hide resolved
qiskit_algorithms/optimizers/scipy_optimizer.py Outdated Show resolved Hide resolved
qiskit_algorithms/optimizers/scipy_optimizer.py Outdated Show resolved Hide resolved
@woodsp-ibm
Copy link
Member

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 and others added 3 commits March 7, 2024 14:08
@edoaltamura
Copy link
Contributor Author

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.

@mergify mergify bot merged commit da55fb0 into qiskit-community:main Apr 23, 2024
21 checks passed
BrunoRosendo pushed a commit to BrunoRosendo/qiskit-algorithms that referenced this pull request May 15, 2024
…kit-community#155)

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants