Skip to content

Commit

Permalink
AdaptVQE threshold improvements (Qiskit#9921)
Browse files Browse the repository at this point in the history
* Pending-deprecate AdaptVQE.threshold in favor of gradient_threshold

* Adds the AdaptVQE.eigenvalue_threshold attribute

* Add release note

* Apply suggestions from code review

Co-authored-by: Julien Gacon <[email protected]>

* Leverage deprecate_func on AdaptVQE.threshold property

* Test pending deprecation of AdaptVQE.threshold

* Add missing deprecate_func decorator to property setter

* Improve documentation of AdaptVQE.eigenvalue_threshold w.r.t. final iteration

* Adds a unittest for SparsePauliOp support in AdaptVQE

* Attempt to fix test flakiness

* Pick better operator pool for gradients in flaky unittest

---------

Co-authored-by: Julien Gacon <[email protected]>
Co-authored-by: ElePT <[email protected]>
  • Loading branch information
3 people authored and giacomoRanieri committed Apr 16, 2023
1 parent 7732021 commit b2518b6
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 18 deletions.
101 changes: 87 additions & 14 deletions qiskit/algorithms/minimum_eigensolvers/adapt_vqe.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -27,6 +27,7 @@
from qiskit.quantum_info.operators.base_operator import BaseOperator
from qiskit.opflow import OperatorBase, PauliSumOp
from qiskit.circuit.library import EvolvedOperatorAnsatz
from qiskit.utils.deprecation import deprecate_arg, deprecate_func
from qiskit.utils.validation import validate_min

from .minimum_eigensolver import MinimumEigensolver
Expand Down Expand Up @@ -84,39 +85,86 @@ class AdaptVQE(VariationalAlgorithm, MinimumEigensolver):
Attributes:
solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues.
It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type
:class:`qiskit.circuit.library.EvolvedOperatorAnsatz`.
threshold: the convergence threshold for the algorithm. Once all gradients have an absolute
value smaller than this threshold, the algorithm terminates.
:class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`.
gradient_threshold: once all gradients have an absolute value smaller than this threshold,
the algorithm has converged and terminates.
eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from one
iteration to the next, the algorithm has converged and terminates. When this case
occurs, the excitation included in the final iteration did not result in a significant
improvement of the eigenvalue and, thus, the results from this iteration are not
considered.
max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the
algorithm is not bound in its number of iterations.
"""

@deprecate_arg(
"threshold",
since="0.24.0",
pending=True,
new_alias="gradient_threshold",
)
def __init__(
self,
solver: VQE,
*,
threshold: float = 1e-5,
gradient_threshold: float = 1e-5,
eigenvalue_threshold: float = 1e-5,
max_iterations: int | None = None,
threshold: float | None = None, # pylint: disable=unused-argument
) -> None:
"""
Args:
solver: a :class:`~.VQE` instance used internally to compute the minimum eigenvalues.
It is a requirement that the :attr:`~.VQE.ansatz` of this solver is of type
:class:`qiskit.circuit.library.EvolvedOperatorAnsatz`.
threshold: the convergence threshold for the algorithm. Once all gradients have an
absolute value smaller than this threshold, the algorithm terminates.
:class:`~qiskit.circuit.library.EvolvedOperatorAnsatz`.
gradient_threshold: once all gradients have an absolute value smaller than this
threshold, the algorithm has converged and terminates.
eigenvalue_threshold: once the eigenvalue has changed by less than this threshold from
one iteration to the next, the algorithm has converged and terminates. When this
case occurs, the excitation included in the final iteration did not result in a
significant improvement of the eigenvalue and, thus, the results from this iteration
are not considered.
max_iterations: the maximum number of iterations for the adaptive loop. If ``None``, the
algorithm is not bound in its number of iterations.
threshold: once all gradients have an absolute value smaller than this threshold, the
algorithm has converged and terminates. Defaults to ``1e-5``.
"""
validate_min("threshold", threshold, 1e-15)
validate_min("gradient_threshold", gradient_threshold, 1e-15)
validate_min("eigenvalue_threshold", eigenvalue_threshold, 1e-15)

self.solver = solver
self.threshold = threshold
self.gradient_threshold = gradient_threshold
self.eigenvalue_threshold = eigenvalue_threshold
self.max_iterations = max_iterations
self._tmp_ansatz: EvolvedOperatorAnsatz | None = None
self._excitation_pool: list[OperatorBase] = []
self._excitation_list: list[OperatorBase] = []

@property
@deprecate_func(
since="0.24.0",
pending=True,
is_property=True,
additional_msg="Instead, use the gradient_threshold attribute.",
)
def threshold(self) -> float:
"""The threshold for the gradients.
Once all gradients have an absolute value smaller than this threshold, the algorithm has
converged and terminates.
"""
return self.gradient_threshold

@threshold.setter
@deprecate_func(
since="0.24.0",
pending=True,
is_property=True,
additional_msg="Instead, use the gradient_threshold attribute.",
)
def threshold(self, threshold: float) -> None:
self.gradient_threshold = threshold

@property
def initial_point(self) -> Sequence[float] | None:
"""Returns the initial point of the internal :class:`~.VQE` solver."""
Expand All @@ -134,7 +182,7 @@ def supports_aux_operators(cls) -> bool:
def _compute_gradients(
self,
theta: list[float],
operator: OperatorBase,
operator: BaseOperator | OperatorBase,
) -> list[tuple[complex, dict[str, Any]]]:
"""
Computes the gradients for all available excitation operators.
Expand Down Expand Up @@ -208,6 +256,8 @@ def compute_minimum_eigenvalue(
self.solver.ansatz = self._tmp_ansatz.initial_state

prev_op_indices: list[int] = []
prev_raw_vqe_result: VQEResult | None = None
raw_vqe_result: VQEResult | None = None
theta: list[float] = []
max_grad: tuple[complex, dict[str, Any] | None] = (0.0, None)
self._excitation_list = []
Expand All @@ -229,7 +279,7 @@ def compute_minimum_eigenvalue(
str(max_grad_index),
)
# log gradients
if np.abs(max_grad[0]) < self.threshold:
if np.abs(max_grad[0]) < self.gradient_threshold:
if iteration == 1:
raise QiskitError(
"All gradients have been evaluated to lie below the convergence threshold "
Expand All @@ -256,12 +306,35 @@ def compute_minimum_eigenvalue(
)
self._excitation_list.append(self._excitation_pool[max_grad_index])
theta.append(0.0)
# run VQE on current Ansatz
# setting up the ansatz for the VQE iteration
self._tmp_ansatz.operators = self._excitation_list
self.solver.ansatz = self._tmp_ansatz
self.solver.initial_point = theta
# evaluating the eigenvalue with the internal VQE
prev_raw_vqe_result = raw_vqe_result
raw_vqe_result = self.solver.compute_minimum_eigenvalue(operator)
theta = raw_vqe_result.optimal_point.tolist()
# checking convergence based on the change in eigenvalue
if iteration > 1:
eigenvalue_diff = np.abs(raw_vqe_result.eigenvalue - history[-1])
if eigenvalue_diff < self.eigenvalue_threshold:
logger.info(
"AdaptVQE terminated successfully with a final change in eigenvalue: %s",
str(eigenvalue_diff),
)
termination_criterion = TerminationCriterion.CONVERGED
logger.debug(
"Reverting the addition of the last excitation to the ansatz since it "
"resulted in a change of the eigenvalue below the configured threshold."
)
self._excitation_list.pop()
theta.pop()
self._tmp_ansatz.operators = self._excitation_list
self.solver.ansatz = self._tmp_ansatz
self.solver.initial_point = theta
raw_vqe_result = prev_raw_vqe_result
break
# appending the computed eigenvalue to the tracking history
history.append(raw_vqe_result.eigenvalue)
logger.info("Current eigenvalue: %s", str(raw_vqe_result.eigenvalue))
else:
Expand All @@ -284,7 +357,7 @@ def compute_minimum_eigenvalue(
)
result.aux_operators_evaluated = aux_values

logger.info("The final energy is: %s", str(result.eigenvalue))
logger.info("The final eigenvalue is: %s", str(result.eigenvalue))
self.solver.ansatz.operators = self._excitation_pool
return result

Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/adapt-vqe-thresholds-239ed9f250c63e71.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
features:
- |
Adds the new :attr:`~qiskit.algorithms.minimum_eigensolvers.AdaptVQE.eigenvalue_threshold`
setting which allows configuring a new kind of threshold to terminate the algorithm once
the eigenvalue changes less than a set value.
- |
Adds the new :attr:`~qiskit.algorithms.minimum_eigensolvers.AdaptVQE.gradient_threshold`
setting which will replace the :attr:`~qiskit.algorithms.minimum_eigensolvers.AdaptVQE.threshold`
in the future.
59 changes: 55 additions & 4 deletions test/python/algorithms/minimum_eigensolvers/test_adapt_vqe.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
# (C) Copyright IBM 2022, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -56,7 +56,7 @@ def setUp(self):
("ZXZX", -0.04523279994605788),
]
)
excitation_pool = [
self.excitation_pool = [
PauliSumOp(
SparsePauliOp(["IIIY", "IIZY"], coeffs=[0.5 + 0.0j, -0.5 + 0.0j]), coeff=1.0
),
Expand All @@ -83,7 +83,7 @@ def setUp(self):
self.initial_state = QuantumCircuit(QuantumRegister(4))
self.initial_state.x(0)
self.initial_state.x(1)
self.ansatz = EvolvedOperatorAnsatz(excitation_pool, initial_state=self.initial_state)
self.ansatz = EvolvedOperatorAnsatz(self.excitation_pool, initial_state=self.initial_state)
self.optimizer = SLSQP()

def test_default(self):
Expand All @@ -96,11 +96,26 @@ def test_default(self):
self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6)
np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6)

def test_with_quantum_info(self):
"""Test behavior with quantum_info-based operators."""
ansatz = EvolvedOperatorAnsatz(
[op.primitive for op in self.excitation_pool],
initial_state=self.initial_state,
)

calc = AdaptVQE(VQE(Estimator(), ansatz, self.optimizer))
res = calc.compute_minimum_eigenvalue(operator=self.h2_op.primitive)

expected_eigenvalue = -1.85727503

self.assertAlmostEqual(res.eigenvalue, expected_eigenvalue, places=6)
np.testing.assert_allclose(res.eigenvalue_history, [expected_eigenvalue], rtol=1e-6)

def test_converged(self):
"""Test to check termination criteria"""
calc = AdaptVQE(
VQE(Estimator(), self.ansatz, self.optimizer),
threshold=1e-3,
gradient_threshold=1e-3,
)
res = calc.compute_minimum_eigenvalue(operator=self.h2_op)

Expand All @@ -116,6 +131,42 @@ def test_maximum(self):

self.assertEqual(res.termination_criterion, TerminationCriterion.MAXIMUM)

def test_eigenvalue_threshold(self):
"""Test for the eigenvalue_threshold attribute."""
operator = PauliSumOp.from_list(
[
("XX", 1.0),
("ZX", -0.5),
("XZ", -0.5),
]
)
ansatz = EvolvedOperatorAnsatz(
[
PauliSumOp.from_list([("YZ", 0.4)]),
PauliSumOp.from_list([("ZY", 0.5)]),
],
initial_state=QuantumCircuit(2),
)

calc = AdaptVQE(
VQE(Estimator(), ansatz, self.optimizer),
eigenvalue_threshold=1,
)
res = calc.compute_minimum_eigenvalue(operator)

self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED)

def test_threshold_attribute(self):
"""Test the (pending deprecated) threshold attribute"""
with self.assertWarns(PendingDeprecationWarning):
calc = AdaptVQE(
VQE(Estimator(), self.ansatz, self.optimizer),
threshold=1e-3,
)
res = calc.compute_minimum_eigenvalue(operator=self.h2_op)

self.assertEqual(res.termination_criterion, TerminationCriterion.CONVERGED)

@data(
([1, 1], True),
([1, 11], False),
Expand Down

0 comments on commit b2518b6

Please sign in to comment.