-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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 NumPyEigensolver
with SparsePauliOp
#9101
Fix NumPyEigensolver
with SparsePauliOp
#9101
Conversation
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 3873960414
💛 - Coveralls |
It might be good to try using the sparse eigensolver (and the sparse matrix multiplication in the eval_aux_ops) when possible. Right now this is implemented for the |
f022fd3
to
5c133d4
Compare
5c133d4
to
7abf0c2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, some minor comments on structure below !
@@ -110,68 +108,50 @@ def _check_set_k(self, operator: BaseOperator | PauliSumOp) -> None: | |||
self._k = self._in_k | |||
|
|||
def _solve(self, operator: BaseOperator | PauliSumOp) -> None: | |||
if isinstance(operator, PauliSumOp): | |||
sp_mat = operator.to_spmatrix() | |||
if isinstance(operator, Operator): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a more general comment. BaseOperator | PauliSumOp
was chosen to be the same as that which Estimator supports. However from what I can see BaseOperator provides no standard way to access the data comprising the operator as needed here. An Operator has data, and Pauli types seems to have to_matrix(). ScalarOp is also a BaseOperator and while it has to_matrix does not have a sparse attribute hence would fail if used here. Looking at Estimator when it does an expectation, using StateVector, it may end up creating an Operator, depending on type, where its constructor inspects the object being passed for various aspects to build it out - I think ScalarOp would pass through this. I guess I am wondering more about the if its not an Operator and then its not PauliSumOp we are in the realm of all other BaseOperators that are not the Operator subclass thereof. The to_matrix(sparse=True) looks like for the Pauli based ones like PauliTable, SparsePauliOp that this is implemented in these - but again this is a specific set of these. This logic certainly now captures the Pauli types like SparsePauliOp of BaseOperator, which it did not before. Just wondering whether we can/should do better - maybe at least wrap the to_matrix(sparse=Try) in a try/except and raise perhaps an error if that can't be called that the operator type is not supported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like a try/except is the best option, as there are several operator classes that do not subclass BasePauli
but do allow sparse=True
. It will also give a more appropriate error when a parameterized operator is passed in. From what I can see all BaseOperator
classes, other than Operator
, do expose a to_matrix
method. Would it be worth adding this method to Operator
and just returning data
? Clifford
and CNOTDihedral
already effectively do this via to_operator
. Could we perhaps also make to_matrix
a required method of BaseOperator
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since primitives - especially the remote ones, I would think, need access to the data to be able to do the computation the fact that BaseOperator is the designated type but provides no defined mechanisms, indeed seems problematic. I am tagging @ikkoham and @t-imamichi to bring them into the conversation more generally. The reference Estimator relies on StateVector being able to compute an estimation of the operator (it and the operators being part of quantum_info) - for other primitives, that may use an aspect of it, like here, it can be different.
Here specifically, in the near term, I think a try/except to be able to raise a more meaningful error. Longer term it seems to me that whatever the type we indicate should have suitable method(s) for the purposes we have/envision. Whether BaseOperator (or something else) is updated or perhaps new mixin/Protocol(s) added seems like it should be the subject of a separate issue/PR to discuss and hopefully address these wider needs and revisit this try/catch later as warrants.
7abf0c2
to
8f6f5b3
Compare
7c8374e
to
f106c12
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code LGTM. I thought that maybe it would be possible to wrap all inputs in a SparsePauliOp
not to have to deal with each case separately, but I could not find a better way to do it. I have followed a simular principle on this PR for VQD #9245.
@@ -109,69 +108,64 @@ def _check_set_k(self, operator: BaseOperator | PauliSumOp) -> None: | |||
else: | |||
self._k = self._in_k | |||
|
|||
def _solve(self, operator: BaseOperator | PauliSumOp) -> None: | |||
def _solve(self, operator: BaseOperator | PauliSumOp) -> tuple[np.ndarray, np.ndarray]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, you could add a check here for num_qubits
, and raise an error if it's None
, to cover all of these operator types that don't make that much sense (like ScalarOp
).
for key, operator in key_op_iterator: | ||
if operator is None: | ||
continue | ||
value = 0.0 | ||
|
||
op_matrix = None | ||
if isinstance(operator, PauliSumOp): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder that the check for num_qubits
should also go here.
("ZZ", -0.01128010425623538), | ||
("XX", 0.18093119978423156), | ||
] | ||
from qiskit.quantum_info import Operator, SparsePauliOp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add one test for other BaseOperator
subclasses? I'm not sure what others would be reasonable... Pauli
? I don't think it's worth it to repeat all of the tests 7 times, but having a simple test for these extra clases would probably make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are additional outliers that cause issues. For example PauliList
and PauliTable
, which also subclass BaseOperator
but will cause an error in NumPyEigensolver
. We could accommodate these, but they would require individual logic. This is easy enough, but seems like a lot of extra code for functionality that no one is likely to need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I don't think it makes sense to add logic for edge cases... but we could check that the error raised is informative enough and these cases are accounted for (even if through raising an error).
ca03d5c
to
f6a37e7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! @Cryoris, if the CI doesn't break, I would say it's mergeable 😇
* Use to_matrix instead for non-Operator BaseOperator children. * Make NumPyEigensolver stateless w.r.t. results. * Build operators as SparsePauliOps rather than PauliSumOps.
Test NumPyMinimumEigensolver with: * SparsePauliOp * Operator * PauliSumOp
f6a37e7
to
1beb7c4
Compare
* Fix NumPyEigensolver for SparsePauliOp * Use to_matrix instead for non-Operator BaseOperator children. * Make NumPyEigensolver stateless w.r.t. results. * Build operators as SparsePauliOps rather than PauliSumOps. * Test NumPyMinimumEigensolver with different ops Test NumPyMinimumEigensolver with: * SparsePauliOp * Operator * PauliSumOp * add releasenote * Use sparse matrix computation for non-Operator * Use sparse matmul for non-Operator * improve structure * try dense matrix for all BaseOperators or raise ex * remove unused import * test Operator.to_matrix() * use instance to access sparse * raise error for invalid num_qubits. pauliop/scalarop tests Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> Co-authored-by: Julien Gacon <gaconju@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix NumPyEigensolver for SparsePauliOp * Use to_matrix instead for non-Operator BaseOperator children. * Make NumPyEigensolver stateless w.r.t. results. * Build operators as SparsePauliOps rather than PauliSumOps. * Test NumPyMinimumEigensolver with different ops Test NumPyMinimumEigensolver with: * SparsePauliOp * Operator * PauliSumOp * add releasenote * Use sparse matrix computation for non-Operator * Use sparse matmul for non-Operator * improve structure * try dense matrix for all BaseOperators or raise ex * remove unused import * test Operator.to_matrix() * use instance to access sparse * raise error for invalid num_qubits. pauliop/scalarop tests Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> Co-authored-by: Julien Gacon <gaconju@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix NumPyEigensolver for SparsePauliOp * Use to_matrix instead for non-Operator BaseOperator children. * Make NumPyEigensolver stateless w.r.t. results. * Build operators as SparsePauliOps rather than PauliSumOps. * Test NumPyMinimumEigensolver with different ops Test NumPyMinimumEigensolver with: * SparsePauliOp * Operator * PauliSumOp * add releasenote * Use sparse matrix computation for non-Operator * Use sparse matmul for non-Operator * improve structure * try dense matrix for all BaseOperators or raise ex * remove unused import * test Operator.to_matrix() * use instance to access sparse * raise error for invalid num_qubits. pauliop/scalarop tests Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> Co-authored-by: Julien Gacon <gaconju@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
* Fix NumPyEigensolver for SparsePauliOp * Use to_matrix instead for non-Operator BaseOperator children. * Make NumPyEigensolver stateless w.r.t. results. * Build operators as SparsePauliOps rather than PauliSumOps. * Test NumPyMinimumEigensolver with different ops Test NumPyMinimumEigensolver with: * SparsePauliOp * Operator * PauliSumOp * add releasenote * Use sparse matrix computation for non-Operator * Use sparse matmul for non-Operator * improve structure * try dense matrix for all BaseOperators or raise ex * remove unused import * test Operator.to_matrix() * use instance to access sparse * raise error for invalid num_qubits. pauliop/scalarop tests Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> Co-authored-by: Julien Gacon <gaconju@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Summary
Fixes an issue where
NumPyEigensolver
and by extensionNumPyMinimumEigensolver
did not support allBaseOperator
instances, in particularSparsePauliOp
. This was because it required the data attribute, whichOperator
exposes but e.g.SparsePauliOp
does not. Closes #9091.Details and comments
For all
BaseOperator
child classes, other thanOperator
,to_matrix
is used to get theNumPy
matrix for the operator and compute the eigenvalues and eigenstates. If anOperator
instance, we still usedata
.Additionally this PR:
NumPyEigensolver
to be stateless w.r.t. results. I.e. the result is no longer stored.NumPyEigensolver
withSparsePauliOp
s.NumPyMinimumEigensolver
withSparsePauliOp
s andOperator
s.