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

Migrating AdaptVQE to Qiskit Terra #7930

Merged
merged 66 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
192355b
added adapt_vqe
fs1132429 Apr 13, 2022
7f15dbd
implemented most of the suggested changes
fs1132429 Apr 15, 2022
609a8e1
implemented the suggested changes
fs1132429 Apr 16, 2022
3304230
added the changes
fs1132429 Apr 18, 2022
65e6107
.
fs1132429 Apr 24, 2022
673083d
made the suggested changes
fs1132429 Apr 24, 2022
4d8e22d
added the code
fs1132429 May 25, 2022
a570435
test added
fs1132429 May 25, 2022
76df1fd
added adapt_vqe
fs1132429 May 25, 2022
a926e6d
.
fs1132429 May 29, 2022
24d82b8
.
fs1132429 May 29, 2022
2b331e6
added changes
fs1132429 Jun 7, 2022
eeb888d
added changes
fs1132429 Jun 7, 2022
4016183
added changes
fs1132429 Jun 7, 2022
be79ee5
.
fs1132429 Jun 7, 2022
d6e1cbe
Merge branch 'adapt_vqe2' into adapt_vqe
fs1132429 Jun 7, 2022
8c8fd11
added the changes
fs1132429 Jun 8, 2022
4a3cfc2
.
fs1132429 Jun 8, 2022
692c5a4
added
fs1132429 Jun 8, 2022
85eaddc
formatting done
fs1132429 Jun 15, 2022
073fbb6
Update adapt_vqe.py
fs1132429 Jun 15, 2022
6aa09e6
made the suggested changes
fs1132429 Jun 29, 2022
f5e7aaf
made the changes
fs1132429 Jul 5, 2022
972b925
lint changes
fs1132429 Jul 5, 2022
b025167
removed initial point from init
fs1132429 Jul 5, 2022
45f9489
black changes
fs1132429 Jul 5, 2022
5806d95
added inline definition
fs1132429 Jul 5, 2022
7cd5a64
made the changes
fs1132429 Jul 12, 2022
4a16b84
removed sys import
fs1132429 Jul 12, 2022
9c588e7
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
f818a77
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
7eb5c6a
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
f3a3057
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
29ed4d2
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
561cb06
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
dc87bc0
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
b08fa47
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
982e239
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
031b7bd
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
c0fba12
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
3fa1e64
Update qiskit/algorithms/minimum_eigen_solvers/adapt_vqe.py
fs1132429 Jul 12, 2022
2356367
added
fs1132429 Jul 12, 2022
83a163f
added
fs1132429 Jul 12, 2022
45d06b0
added
fs1132429 Jul 12, 2022
c72cc8b
.
fs1132429 Jul 18, 2022
f0d7783
.
fs1132429 Jul 18, 2022
f57c118
made final changes
fs1132429 Jul 19, 2022
9fab0fb
fix: evaluate gradients via commutator relation
mrossinek Jul 22, 2022
69ebf6d
Merge pull request #14 from mrossinek/adapt_vqe
fs1132429 Jul 22, 2022
c9381d5
.
fs1132429 Jul 25, 2022
392c519
Merge branch 'adapt_vqe' of https://github.com/fs1132429/qiskit-terra…
fs1132429 Aug 1, 2022
885bed9
made the changes
fs1132429 Aug 2, 2022
8ad2ff2
.
fs1132429 Aug 9, 2022
cb42abc
fix: AdaptVQE gradient computation unittest
mrossinek Sep 2, 2022
c9b8557
Merge branch 'main' into adapt_vqe
mrossinek Sep 2, 2022
a09332d
Merge branch 'main' into adapt_vqe
mrossinek Sep 3, 2022
ff471aa
fix: remove unused import
mrossinek Sep 3, 2022
9974290
Merge branch 'main' into adapt_vqe
mrossinek Sep 27, 2022
1abb4c8
refactor: migrate AdaptVQE to primitive-based module
mrossinek Sep 23, 2022
d1fe000
Add a release note
mrossinek Sep 27, 2022
3fffc34
Apply suggestions from code review
mrossinek Sep 27, 2022
a3615d5
Pass parameters to estimate_observables method
mrossinek Sep 27, 2022
8b96c1a
Apply suggestions from code review
mrossinek Sep 28, 2022
8e47ab9
More changes based on code review
mrossinek Sep 28, 2022
4d254da
Avoid deep-copying the ansatz
mrossinek Sep 28, 2022
1983023
Merge branch 'main' into adapt_vqe
mergify[bot] Sep 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
332 changes: 332 additions & 0 deletions qiskit/algorithms/minimum_eigen_solvers/adaptvqe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
# This code is part of Qiskit.
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
#
# (C) Copyright IBM 2020, 2022.
#
# 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
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""A ground state calculation employing the AdaptVQE algorithm."""
# figure out which ansatz is used by qiskit nature
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
from __future__ import annotations
from typing import Optional, List, Tuple, Union, Callable

import copy
import re
import logging

import numpy as np
from qiskit import QiskitError

from qiskit.algorithms import VQE
from qiskit.algorithms.list_or_dict import ListOrDict
from qiskit.algorithms.minimum_eigen_solvers.vqe import VQEResult
from qiskit.circuit import QuantumCircuit
from qiskit.opflow import OperatorBase, PauliSumOp, ExpectationBase, CircuitSampler
from qiskit.opflow.gradients import GradientBase, Gradient
from qiskit.algorithms.minimum_eigen_solvers import MinimumEigensolver
from qiskit.circuit.library import EvolvedOperatorAnsatz, RealAmplitudes, PauliEvolutionGate
from qiskit.circuit.library import NLocal
from qiskit.utils.validation import validate_min


from qiskit.algorithms.optimizers import Optimizer
from qiskit.utils import QuantumInstance
from qiskit.providers import Backend

# from .minimum_eigensolver_factories import MinimumEigensolverFactory
# from .ground_state_eigensolver import GroundStateEigensolver

fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
logger = logging.getLogger(__name__)


class AdaptVQE(VQE):
"""A ground state calculation employing the AdaptVQE algorithm.

The performance of AdaptVQE can significantly depend on the choice of gradient method, QFI
solver (if applicable) and the epsilon value.
"""

# pylint: disable=unused-argument
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
def __init__(
self,
ansatz: Optional[QuantumCircuit] = None,
threshold: float = 1e-5,
max_iterations: Optional[int] = None,
gradient: Optional[GradientBase] = None,
optimizer: Optional[Optimizer] = None,
initial_point: Optional[np.ndarray] = None,
expectation: Optional[ExpectationBase] = None,
include_custom: bool = False,
max_evals_grouped: int = 1,
callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None,
quantum_instance: Optional[Union[QuantumInstance, Backend]] = None,
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
excitation_pool: List[Union[OperatorBase, QuantumCircuit]] = None,
operator: OperatorBase = None,
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
"""
Args:
qubit_converter: a class that converts second quantized operator to qubit operator
solver: a factory for the VQE solver employing a UCCSD ansatz.
threshold: the energy convergence threshold. It has a minimum value of 1e-15.
max_iterations: the maximum number of iterations of the AdaptVQE algorithm.
gradient: a class that converts operator expression to the first-order gradient based
on the method mentioned.
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
"""
validate_min("threshold", threshold, 1e-15)
super().__init__(
ansatz=None,
optimizer=optimizer,
initial_point=initial_point,
gradient=gradient,
expectation=expectation,
include_custom=include_custom,
max_evals_grouped=max_evals_grouped,
callback=callback,
quantum_instance=quantum_instance,
)

if isinstance(gradient, float):
gradient = Gradient(grad_method="fin_diff", epsilon=gradient)
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved

if gradient is None:
gradient = Gradient(grad_method="param_shift")
self._threshold = threshold
self._max_iterations = max_iterations
self._gradient = gradient
self._excitation_pool = excitation_pool
self._operator = operator
self._tmp_ansatz = ansatz

self._excitation_pool: List[OperatorBase] = []
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
self._excitation_list: List[OperatorBase] = []

@property
def gradient(self) -> Optional[GradientBase]:
"""Returns the gradient."""
return self._gradient

@gradient.setter
def gradient(self, grad: Optional[GradientBase] = None) -> None:
"""Sets the gradient."""
self._gradient = grad
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved

def returns_groundstate(self) -> bool:
"""Whether this class returns only the ground state energy or also the ground state itself."""
return True
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved

def _compute_gradients(
self,
theta: List[float],
) -> List[Tuple[float, PauliSumOp]]:
"""
Computes the gradients for all available excitation operators.

Args:
theta: List of (up to now) optimal parameters
vqe: The variational quantum eigensolver instance used for solving

Returns:
List of pairs consisting of gradient and excitation operator.
"""
res = []
# compute gradients for all excitation in operator pool
sampler = CircuitSampler(self.quantum_instance)
for exc in self._excitation_pool:
# add next excitation to ansatz
self._tmp_ansatz.operators = self._excitation_list + [exc]
# the ansatz needs to be decomposed for the gradient to work
self.ansatz = self._tmp_ansatz.decompose()
param_sets = list(self.ansatz.parameters)
# zip will only iterate the length of the shorter list
theta1 = dict(zip(self.ansatz.parameters, theta))
op, expectation = self.construct_expectation(
theta1, self._operator, return_expectation=True
)
# compute gradient
state_grad = self.gradient.convert(operator=op, params=param_sets)
# Assign the parameters and evaluate the gradient
value_dict = {param_sets[-1]: 0.0}
state_grad_result = sampler.convert(state_grad, params=value_dict).eval()
logger.info("Gradient computed : %s", str(state_grad_result))
res.append((np.abs(state_grad_result[-1]), exc))
return res, expectation

@staticmethod
def _check_cyclicity(indices: List[int]) -> bool:
"""
Auxiliary function to check for cycles in the indices of the selected excitations.

Args:
indices: The list of chosen gradient indices.
Returns:
Whether repeating sequences of indices have been detected.
"""
cycle_regex = re.compile(r"(\b.+ .+\b)( \b\1\b)+")
match = cycle_regex.search(" ".join(map(str, indices)))
logger.debug("Cycle detected: %s", match)
return match is not None or (len(indices) > 1 and indices[-2] == indices[-1])

def solve(
self,
aux_operators: Optional[ListOrDict[PauliSumOp]] = None,
) -> AdaptVQEResult:
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
"""Computes the ground state.

Args:
aux_operators: Additional auxiliary operators to evaluate.

Raises:
QiskitError: if a solver other than VQE or a ansatz other than UCCSD is provided
or if the algorithm finishes due to an unforeseen reason.
ValueError: If the grouped property object returned by the driver does not contain a
main property as requested by the problem being solved (`problem.main_property_name`)
QiskitError: If the user-provided `aux_operators` contain a name which clashes
with an internally constructed auxiliary operator. Note: the names used for the
internal auxiliary operators correspond to the `Property.name` attributes which
generated the respective operators.
QiskitError: If the chosen gradient method appears to result in all-zero gradients.

Returns:
An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime
information about the AdaptVQE algorithm like the number of iterations, finishing
criterion, and the final maximum gradient.
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
"""
# if not isinstance(self.ansatz, EvolvedOperatorAnsatz):
# raise QiskitNatureError("The AdaptVQE algorithm requires the use of the evolved operator ansatz")

fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
# We construct the ansatz once to be able to extract the full set of excitation operators.
self._tmp_ansatz._build()
self._excitation_pool = copy.deepcopy(self._tmp_ansatz.operators)
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved

threshold_satisfied = False
alternating_sequence = False
max_iterations_exceeded = False
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
finishing_criterion = ""
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
prev_op_indices: List[int] = []
theta: List[float] = []
max_grad: Tuple[float, Optional[PauliSumOp]] = (0.0, None)
iteration = 0
while self._max_iterations is None or iteration < self._max_iterations:
iteration += 1
logger.info("--- Iteration #%s ---", str(iteration))
# compute gradients

cur_grads, expectation = self._compute_gradients(theta)
# pick maximum gradient
max_grad_index, max_grad = max(
enumerate(cur_grads), key=lambda item: np.abs(item[1][0])
)
# store maximum gradient's index for cycle detection
prev_op_indices.append(max_grad_index)
# log gradients
if logger.isEnabledFor(logging.INFO):
gradlog = f"\nGradients in iteration #{str(iteration)}"
gradlog += "\nID: Excitation Operator: Gradient <(*) maximum>"
for i, grad in enumerate(cur_grads):
gradlog += f"\n{str(i)}: {str(grad[1])}: {str(grad[0])}"
if grad[1] == max_grad[1]:
gradlog += "\t(*)"
logger.info(gradlog)
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
if np.abs(max_grad[0]) < self._threshold:
if iteration == 1:
raise QiskitError(
"Gradient choice is not suited as it leads to all zero gradients gradients. "
"Try a different gradient method."
)
logger.info(
"Adaptive VQE terminated successfully with a final maximum gradient: %s",
str(np.abs(max_grad[0])),
)
threshold_satisfied = True
finishing_criterion = "Threshold converged"
break
# check indices of picked gradients for cycles
if self._check_cyclicity(prev_op_indices):
logger.info("Alternating sequence found. Finishing.")
logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0])))
alternating_sequence = True
finishing_criterion = "Aborted due to cyclicity"
break
# add new excitation to self._ansatz
self._excitation_list.append(max_grad[1])
theta.append(0.0)
# run VQE on current Ansatz
self._tmp_ansatz.operators = self._excitation_list
self.ansatz = self._tmp_ansatz
self.initial_point = theta
raw_vqe_result = self.compute_minimum_eigenvalue(self._operator)
theta = raw_vqe_result.optimal_point.tolist()
else:
# reached maximum number of iterations
max_iterations_exceeded = True
finishing_criterion = "Maximum number of iterations reached"
logger.info("Maximum number of iterations reached. Finishing.")
logger.info("Final maximum gradient: %s", str(np.abs(max_grad[0])))

# once finished evaluate auxiliary operators if any
if aux_operators is not None:
aux_values = self._eval_aux_ops(
raw_vqe_result.eigenstate, aux_operators, expectation=expectation
)
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
else:
aux_values = None
raw_vqe_result.aux_operator_eigenvalues = aux_values

if finishing_criterion == False:
raise QiskitError("The algorithm finished due to an unforeseen reason!")

electronic_result = self._get_eigenstate(theta)

result = AdaptVQEResult()
result.combine(electronic_result)
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
fs1132429 marked this conversation as resolved.
Show resolved Hide resolved
result.num_iterations = iteration
result.final_max_gradient = max_grad[0]
result.finishing_criterion = finishing_criterion

logger.info("The final energy is: %s", str(result.computed_energies[0]))
return result


class AdaptVQEResult(VQEResult):
"""AdaptVQE Result."""

def __init__(self) -> None:
super().__init__()
self._num_iterations: int = 0
self._final_max_gradient: float = 0.0
self._finishing_criterion: str = ""

@property
def num_iterations(self) -> int:
"""Returns number of iterations"""
return self._num_iterations

@num_iterations.setter
def num_iterations(self, value: int) -> None:
"""Sets number of iterations"""
self._num_iterations = value

@property
def final_max_gradient(self) -> float:
"""Returns final maximum gradient"""
return self._final_max_gradient

@final_max_gradient.setter
def final_max_gradient(self, value: float) -> None:
"""Sets final maximum gradient"""
self._final_max_gradient = value

@property
def finishing_criterion(self) -> str:
"""Returns finishing criterion"""
return self._finishing_criterion

@finishing_criterion.setter
def finishing_criterion(self, value: str) -> None:
"""Sets finishing criterion"""
self._finishing_criterion = value
Loading