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

Add Subspace Search Variational Quantum Eigensolver (SSVQE) #8792

Closed
wants to merge 30 commits into from

Conversation

JoelHBierman
Copy link

@JoelHBierman JoelHBierman commented Sep 26, 2022

Summary

This continues the work of PR #8770 which was closed. This code is now re-written for SSVQE to use primitives. The SSVQE algorithm (https://arxiv.org/abs/1810.09434) is a straightforward generalization of VQE to find low-lying eigenstates of a hermitian operator. Specifically, this pull request implements the version of SSVQE that carries out one optimization procedure using weights. Whereas VQE minimizes the expectation value of an operator with respect to a parameterized ansatz state, SSVQE takes a set of mutually orthogonal initial states, applies the same parameterized ansatz circuit to all of them, then minimizes a weighted sum of the expectation values of the operator with respect to these states. Because inner products of states are invariant under unitary transformations, the set of states remains orthogonal under ideal conditions and thus the global minimum corresponds to the low-lying eigenstates. An example of how a user might use this class:

from qiskit import QuantumCircuit
from qiskit.opflow import Z
from qiskit.primitives import Estimator
from qiskit.circuit.library import RealAmplitudes
from qiskit.algorithms.optimizers import SPSA
from qiskit.algorithms.eigensolvers import SSVQE

operator = Z^Z
input_states = [QuantumCircuit(2), QuantumCircuit(2)]
input_states[0].x(0)
input_states[1].x(1)

ssvqe_instance = SSVQE(k=2,
                            estimator=Estimator(),
                            optimizer=SPSA(),
                            ansatz=RealAmplitudes(2),
                            initial_states=input_states)

result = ssvqe_instance.compute_eigenvalues(operator)

Details and comments

Because of its similarity to VQE in structure, the code for SSVQE is mostly duplicate code from VQE. It is essentially VQE code that has been modified to handle lists of QuantumCircuits where it is necessary in order to calculate the weighted sum objective function. Tests have been added for this class which are mostly duplicate from the tests for VQD. These two methods are both low-lying eigensolvers, so they should be required to pass similar tests.

@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Sep 26, 2022
@qiskit-bot
Copy link
Collaborator

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:

@JoelHBierman
Copy link
Author

JoelHBierman commented Sep 26, 2022

I am noticing that updating the code to use primitives results in a circular import error that prevents me from running the unit tests. This results in the following trackback error:

Failed to import test module: test.python.opflow.test_gradients
Traceback (most recent call last):
  File "/home/collabor/jhb57/anaconda3/envs/Qiskit-SSVQE-Dev/lib/python3.9/unittest/loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "/home/collabor/jhb57/anaconda3/envs/Qiskit-SSVQE-Dev/lib/python3.9/unittest/loader.py", line 377, in _get_module_from_name
    __import__(name)
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/test/python/opflow/test_gradients.py", line 27, in <module>
    from qiskit.algorithms import VQE
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/__init__.py", line 302, in <module>
    from .eigen_solvers import NumPyEigensolver, Eigensolver, EigensolverResult, VQD, VQDResult, SSVQE, SSVQEResult
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/eigen_solvers/__init__.py", line 18, in <module>
    from .ssvqe import SSVQE, SSVQEResult
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/eigen_solvers/ssvqe.py", line 26, in <module>
    from qiskit.algorithms.gradients import BaseEstimatorGradient
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/gradients/__init__.py", line 64, in <module>
    from .finite_diff_estimator_gradient import FiniteDiffEstimatorGradient
  File "/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/gradients/finite_diff_estimator_gradient.py", line 21, in <module>
    from qiskit.algorithms import AlgorithmError
ImportError: cannot import name 'AlgorithmError' from partially initialized module 'qiskit.algorithms' (most likely due to a circular import) (/home/collabor/jhb57/Desktop/Qiskit_Dev/qiskit-terra-ssvqe/qiskit/algorithms/__init__.py)

My current understanding of what is happening is that this is due to the gradient implementation now being located in qiskit.algorithms instead of qiskit.opflow. i.e. the file containing SSVQE imports BaseEstimatorGradient, which requires running the __init__.py file in qiskit/algorithms, which contains imports for SSVQE, which then tries to import BaseEstimatorGradient, resulting in it getting stuck in a loop. Unless there is another fix that I can't see (which is very possible), this could result in similar issues for VQE (which uses a gradient implementation) and VQD (which uses both a gradient implementation and a fidelity implementation, which is also in qiskit.algorithms). Unlike VQE and VQD, calculating gradients and fidelities are rarely used as standalone algorithms, but rather as subroutines of a larger algorithm. This might motivate moving these implementations to a new directory such as qiskit.algorithm_subroutines. (Although this might not entirely avoid this circular import problem if you wanted to do something like say, calculate the gradient of a fidelity.)

@woodsp-ibm woodsp-ibm added the mod: algorithms Related to the Algorithms module label Sep 26, 2022
@woodsp-ibm
Copy link
Member

For the import issue we have the VQD #8640 and VQE #8702 PRs which presumably avoid the problem. Maybe take a look there.

I mentioned before this will need to end up in algorithms.eigensolvers (no underscore) which will be there once the VQD PR is merged. The imports/docs for these have to be dealt with a little differently since we have the same named algos and cannot, for the moment, import/doc the new ones at the qiskit.algorithms level. We can sort the docs, ie init file docstring content later though but this will need to be more associated with the new refactored primitive based algos in the new location as the existing algos will be removed in the future..

@JoelHBierman
Copy link
Author

Okay, I will take a closer look to see how this circular import can be resolved. This issue seems specific to the gradients module using primitives because commenting out any mention of gradients seems to make this import error go away. I will also see what progress I can make regarding tests that don't require gradients.

@woodsp-ibm
Copy link
Member

I saw VQE has a test which included using gradient. Maybe that's of help. We should get more of the PRs I mentioned merged in the next day or so, for the imminent release, at which point things should be less busy and hopefully I'll have more time to investigate with you.

@JoelHBierman
Copy link
Author

After temporarily commenting out lines involving the estimator gradient, I was able to get the tests adapted from VQD to pass locally. Aside from fixing the circular import, the next thing on the list to do is to write some SSVQE-specific tests if those are called for and to make sure those pass as well.

@woodsp-ibm
Copy link
Member

write some SSVQE-specific tests if those are called for

The unit tests should cover the functionality of the algorithm so via its public API all the internal code is exercised i.e. tested, that things work as expected for normal as well as error paths. That may well require SSVQE specifics.

@JoelHBierman JoelHBierman reopened this Sep 29, 2022
@JoelHBierman
Copy link
Author

JoelHBierman commented Sep 29, 2022

The main aspect that separates this method from others is that the input states are required to be mutually orthogonal. If they are not, the entire method falls apart. Because of this, the input states probably deserve some attention both in terms of tests and handling erroneous user input.

Immediately I can see that the method _check_operator_initial_states would need to be covered by a new test (and possibly other functionality as well). I wrote this check for the initial states separate from _check_operator_ansatz because this method will change the number of qubits for the ansatz if it can do so. I was not sure if combining these two checks by checking the composition of the ansatz and the initial state would prevent this. If there is a better way to handle this, let me know. (It is an annoying bit of duplicate code from _check_operator_ansatz.)

Another thing to consider is: What kind of errors or warning messages should there be (if any) for if the user input for the initial states is not mutually orthogonal? We might want to explicitly check and return an error for small systems (for instance, <= 8 qubits or so) but only show a warning message that the orthogonality was not checked for larger systems since we might not want this computational overhead of calculating the inner products of exponentially large vectors. This checking threshold could also be an optional argument set by the user. Depending on what we do here, this would need a corresponding test. Or we could just leave this to the documentation and leave the checking of orthogonality as user responsibility.

@woodsp-ibm
Copy link
Member

woodsp-ibm commented Sep 29, 2022

My take would be to do the check for now have some parameter that allows you to turn it off, for now default it on. If you turn it off you assure orthogonality and if not the algo may not perform as expected. If we want to expose some public method to check such that you can do this once somewhere and then disable the algo check that seems fine too.

If we can avoid duplicating code and just have separate checks, where the above would be new code, then that would be better I think.

@JoelHBierman JoelHBierman changed the title [WIP] Add Subspace Search Variational Quantum Eigensolver (SSVQE) Add Subspace Search Variational Quantum Eigensolver (SSVQE) Sep 30, 2022
@JoelHBierman
Copy link
Author

Let me know if there are any other changes you recommend making to the code. It looks like it's at the point where it passes all tests so far, but it looks like there are two sets of tests that I don't have permission to run.

@woodsp-ibm
Copy link
Member

Thanks for the work so far. I'll try and get to this a bit later this week. Things have been rather busy with getting the imminent release 0.22 done, and I am backed up with few other priority things I need to take care of first.

@coveralls
Copy link

Pull Request Test Coverage Report for Build 3162211642

  • 187 of 204 (91.67%) changed or added relevant lines in 2 files are covered.
  • 244 unchanged lines in 21 files lost coverage.
  • Overall coverage increased (+0.1%) to 84.677%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/algorithms/eigensolvers/ssvqe.py 186 203 91.63%
Files with Coverage Reduction New Missed Lines %
qiskit/algorithms/eigen_solvers/eigen_solver.py 1 97.67%
qiskit/algorithms/observables_evaluator.py 1 95.65%
qiskit/algorithms/optimizers/spsa.py 1 93.51%
qiskit/transpiler/passes/utils/check_map.py 1 96.88%
qiskit/extensions/quantum_initializer/squ.py 2 79.78%
qiskit/transpiler/passmanager_config.py 2 96.92%
qiskit/primitives/base_sampler.py 3 89.29%
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 3 93.75%
qiskit/primitives/base_estimator.py 4 89.19%
qiskit/transpiler/preset_passmanagers/common.py 4 96.64%
Totals Coverage Status
Change from base Build 3150876259: 0.1%
Covered Lines: 61671
Relevant Lines: 72831

💛 - Coveralls

@woodsp-ibm woodsp-ibm added this to the 0.23.0 milestone Oct 5, 2022
Comment on lines 107 to 116
callback (Callable[[int, np.ndarray, Sequence[float], dict[str, Any]], None] | None): A
function that can access the intermediate data at each optimization step. These data are
the evaluation count, the optimizer parameters for the ansatz, the evaluated mean
energies, and the metadata dictionary.
weight_vector (Sequence[float]): A 1D array of real positive-valued numbers to assign
as weights to each of the expectation values. If ``None``, then SSVQE will default
to [k, k-1, ..., 1].
initial_states (Sequence[QuantumCircuit]): An optional list of mutually orthogonal
initial states. If ``None``, then SSVQE will set these to be a list of mutually
orthogonal computational basis states.
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps it might be nicer to maintain the order of these arguments as in the init.

Copy link
Author

Choose a reason for hiding this comment

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

I agree. I have now changed this.

Comment on lines 154 to 159
check_input_states_orthogonality: A boolean that sets whether or not to check
that the value of initial_states passed consists of a mutually orthogonal
set of states. If ``True``, then SSVQE will check that these states are mutually
orthogonal and return an error if they are not. This is set to ``True`` by default,
but setting this to ``False`` may be desirable for larger numbers of qubits to avoid
exponentially large computational overhead before the simulation even starts.
Copy link
Contributor

Choose a reason for hiding this comment

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

Shall we also add it to attributes docs above?

Copy link
Author

Choose a reason for hiding this comment

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

This has now been added.


self.weight_vector = self._check_weight_vector(self.weight_vector)

start_time = time()
Copy link
Contributor

Choose a reason for hiding this comment

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

This line seems to be overwritten by line 238.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I have now fixed this.

initialized_ansatz_list: list[QuantumCircuit],
operator: BaseOperator | PauliSumOp,
) -> tuple[Callable[[np.ndarray], float | list[float]], dict]:
"""Returns a function handle to evaluates the weighted energy sum at given parameters
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"""Returns a function handle to evaluates the weighted energy sum at given parameters
"""Returns a function handle to evaluate the weighted energy sum at given parameters

Copy link
Author

Choose a reason for hiding this comment

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

I have now fixed this typo.


return evaluate_weighted_energy_sum

def _get_evalute_gradient( # check this implementation
Copy link
Contributor

Choose a reason for hiding this comment

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

Was the comment addressed already?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, this should now be addressed, so I have removed this comment. If I remember correctly, I added this note to myself because I was still learning how primitives work. There are now tests that cover the primitive implementation of gradients in SSVQE, but if there is more that should be done to make sure this works properly, let me know.

aux_ops = aux_operators

num_aux_ops = len(aux_ops)
aux_job = self.estimator.run([ansatz] * num_aux_ops, aux_ops)
Copy link
Contributor

Choose a reason for hiding this comment

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

We could surround it with a try block just like for other primitives in this class.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I have added this.

Comment on lines 495 to 501
result.eigenvalues = (
self.estimator.run(
initialized_ansatz_list, [operator] * self.k, [optimizer_result.x] * self.k
)
.result()
.values
)
Copy link
Contributor

Choose a reason for hiding this comment

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

We could surround it with a try block just like for other primitives in this class.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I have added this.

qiskit/algorithms/eigensolvers/ssvqe.py Show resolved Hide resolved
.. code-block:: python

from qiskit import QuantumCircuit
from qiskit.opflow import Z
Copy link
Contributor

Choose a reason for hiding this comment

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

We try to limit the use of opflow now. Could you use Pauli("ZZ")?

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I have now changed this.

ref_eval_count = [1, 2, 3]
ref_mean = [[-1.07, -1.44], [-1.45, -1.06], [-1.37, -0.94]]

np.testing.assert_array_almost_equal(history["eval_count"], ref_eval_count, decimal=0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need _almost part in this check?

Copy link
Author

Choose a reason for hiding this comment

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

I do not think it is necessary, so I have now removed it.

Comment on lines 130 to 133
optimizer: Optimizer | Minimizer = None,
initial_point: Sequence[float] = None,
initial_states: list[QuantumCircuit] = None,
weight_vector: Sequence[float] | Sequence[int] = None,
Copy link
Contributor

Choose a reason for hiding this comment

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

If we default them to None, shall we also add | None typhint?

Copy link
Author

Choose a reason for hiding this comment

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

I agree. I have now added this.

Comment on lines 160 to 161
that the value of initial_states passed consists of a mutually orthogonal
set of states. If ``True``, then SSVQE will check that these states are mutually
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
that the value of initial_states passed consists of a mutually orthogonal
set of states. If ``True``, then SSVQE will check that these states are mutually
that the value of ``initial_states`` passed consists of a mutually orthogonal
set of states. If ``True``, then SSVQE will check that these states are mutually

Copy link
Author

Choose a reason for hiding this comment

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

changed

If ``None``, then SSVQE will set these to be a list of mutually orthogonal
computational basis states.
weight_vector: An optional list or array of real positive numbers with length
equal to the value of *num_states* to be used in the weighted energy summation
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
equal to the value of *num_states* to be used in the weighted energy summation
equal to the value of ``num_states`` to be used in the weighted energy summation

Please check other places.

Copy link
Author

Choose a reason for hiding this comment

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

I have now changed this where I have found it.

Comment on lines 295 to 299
Args:
operator: The operator whose energy levels to evaluate.
return_expectation: If True, return the ``ExpectationBase`` expectation converter used
in the construction of the expectation value. Useful e.g. to evaluate other
operators with the same expectation value converter.
Copy link
Contributor

Choose a reason for hiding this comment

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

Args are outdated.

Copy link
Author

Choose a reason for hiding this comment

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

updated

Comment on lines 300 to 302
Returns:
Weighted energy sum of the hamiltonian of each parameter, and, optionally, the expectation
converter.
Copy link
Contributor

Choose a reason for hiding this comment

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

Expectation converter is outdated.

Copy link
Author

Choose a reason for hiding this comment

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

updated

Comment on lines +138 to +139
"""
Args:
Copy link
Contributor

Choose a reason for hiding this comment

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

We could also give a code example somewhere in the docs here, just like in the release notes.

Copy link
Author

Choose a reason for hiding this comment

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

Okay, I will work on adding this. I am also considering re-writing the introduction docstring to be more concise and to the point, rather than being a few large blocks of texts. (Similar in style to what the current docstring for VQE with primitives.)

@mtreinish mtreinish removed this from the 0.23.0 milestone Jan 17, 2023
@woodsp-ibm
Copy link
Member

First I apologise that this has been sitting around a while with no further response from us.,

Thank you for your interest in contributing to Qiskit. It has however been decided that the algorithms are to be moved out of Qiskit (see #10406) and the plan is a new community repository that will contain these going forwards. As such I am going to close this PR as it stands.

If you still have interest in pursuing this then might I suggest a community project in the ecosystem for this. See https://qiskit.org/ecosystem/ and follow/click the "Join the ecosystem" for more information on what that would entail.

@woodsp-ibm woodsp-ibm closed this Jul 11, 2023
@JoelHBierman
Copy link
Author

Hi,

I will have to re-familiarize myself with this code and make sure that it can pass tests for the most up-to-date versions of Qiskit. I also imagine that since the github repo on which this was kept includes all of qiskit terra in addition to the SSVQE algorithm, it will have to be refactored as a standalone package (with only SSVQE and not the rest of Qiskit terra) that can be installed using pip. Does this sound about right?

@woodsp-ibm
Copy link
Member

Hi, if this algorithm code is put on a repo it would just be SSEVQE in my mind, and its unit test. A requirements/setup created there can say its dependent on qiskit (qiskit-terra is being renamed to qiskit though the former is still valid for compatibility at present) and on qiskit_algorithms. If you have some setup.py in the repo then people can clone the code and pip install that. Once installed it would be importable/usuable from that package, whatever you call it. And yes you may want to check against the latest algorithms code as it has been a while - they no longer use opflow it now being deprecated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Community PR PRs from contributors that are not 'members' of the Qiskit repo mod: algorithms Related to the Algorithms module priority: low
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants