Note
Cette page a été générée à partir de tutorials/algorithms/02_vqe_advanced.ipynb.
Options Avancées de VQE¶
Dans le premier tutoriel d’algorithmes, vous avez appris comment mettre en place un algorithme basique VQE. Maintenant, vous allez voir comment fournir des paramètres de configuration plus avancés pour explorer toute la gamme de capacités des algorithmes variationnels de Qiskit : VQE, QAOA et VQD entre autres. En particulier, ce tutoriel couvrira comment configurer un callback
pour surveiller la convergence et l’utilisation des initial point
s personnalisés et des gradient
s.
Procédure de rappel (callback)¶
Les méthodes de rappel peuvent être utilisées pour surveiller la progression de l’optimisation au fur et à mesure que l’algorithme s’exécute et converge au minimum. Le callback est appelé pour chaque évaluation fonctionnelle par l’optimiseur et fournit la valeur actuelle de l’optimiseur, le nombre d’évaluations, les paramètres actuels de l’optimiseur, etc. Notez que, selon l’optimiseur spécifique, cela peut ne pas être chaque itération (étape) de l’optimisateur, donc par exemple si l’optimiseur appelle la fonction coût pour calculer un gradient de différence finie, cela sera visible via le callback.
Cette section montre comment tirer parti des callbacks dans VQE
pour tracer le chemin de convergence vers l’énergie de l’état fondamental avec un ensemble sélectionné d’optimiseurs.
Tout d’abord, nous créons un opérateur quantique pour VQE. Ici nous utiliserons le même opérateur que celui qui a été utilisé dans l’introduction aux algorithmes, et initialement calculé par Qiskit Nature pour une molécule H2.
[1]:
from qiskit.quantum_info import SparsePauliOp
H2_op = SparsePauliOp.from_list(
[
("II", -1.052373245772859),
("IZ", 0.39793742484318045),
("ZI", -0.39793742484318045),
("ZZ", -0.01128010425623538),
("XX", 0.18093119978423156),
]
)
L’étape suivante consiste à instancier l” `` Estimateur`` de choix pour l’évaluation des valeurs attendues dans VQE
. Pour simplifier, vous pouvez sélectionner qiskit.primitives.Estimator
livré avec l’installation Qiskit Terra par défaut.
[2]:
from qiskit.primitives import Estimator
estimator = Estimator()
Vous êtes maintenant prêt à comparer un ensemble d’optimisateurs à travers le callback VQE
. L’énergie minimale du Hamiltonien H2 peut être trouvée assez facilement, donc le nombre maximum d’itérations (maxiter
) ne doit pas être très grand. Vous pouvez à nouveau utiliser TwoLocal
comme fonction d’essai sélectionnée (i.e. ansatz).
[3]:
import numpy as np
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP
from qiskit.circuit.library import TwoLocal
from qiskit.utils import algorithm_globals
# we will iterate over these different optimizers
optimizers = [COBYLA(maxiter=80), L_BFGS_B(maxiter=60), SLSQP(maxiter=60)]
converge_counts = np.empty([len(optimizers)], dtype=object)
converge_vals = np.empty([len(optimizers)], dtype=object)
for i, optimizer in enumerate(optimizers):
print("\rOptimizer: {} ".format(type(optimizer).__name__), end="")
algorithm_globals.random_seed = 50
ansatz = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")
counts = []
values = []
def store_intermediate_result(eval_count, parameters, mean, std):
counts.append(eval_count)
values.append(mean)
vqe = VQE(estimator, ansatz, optimizer, callback=store_intermediate_result)
result = vqe.compute_minimum_eigenvalue(operator=H2_op)
converge_counts[i] = np.asarray(counts)
converge_vals[i] = np.asarray(values)
print("\rOptimization complete ");
Optimization complete
Maintenant, à partir des données de callback que nous avons stockées, nous pouvons tracer la valeur d’énergie à chaque appel de fonction objectif que chaque optimiseur fait. Un optimiseur utilisant une méthode de différence finie pour le calcul du gradient a ce tracé avec des plateaux caractéristique où, pour un certain nombre d’évaluations consécutives, il calcule la valeur d’énergie pour des points très proches (les points très proches ayant des valeurs d’énergie très similaires, la différence ne peut être vue sur l’échelle du graphique).
[4]:
import pylab
pylab.rcParams["figure.figsize"] = (12, 8)
for i, optimizer in enumerate(optimizers):
pylab.plot(converge_counts[i], converge_vals[i], label=type(optimizer).__name__)
pylab.xlabel("Eval count")
pylab.ylabel("Energy")
pylab.title("Energy convergence for various optimizers")
pylab.legend(loc="upper right");

Enfin, puisque le problème ci-dessus est toujours facilement tractable classiquement, vous pouvez utiliser NumPyMinimumEigensolver
pour calculer une valeur de référence pour la solution.
[5]:
from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver
from qiskit.opflow import PauliSumOp
numpy_solver = NumPyMinimumEigensolver()
result = numpy_solver.compute_minimum_eigenvalue(operator=PauliSumOp(H2_op))
ref_value = result.eigenvalue.real
print(f"Reference value: {ref_value:.5f}")
Reference value: -1.85728
Vous pouvez maintenant tracer la différence entre la solution VQE
et cette valeur de référence exacte lorsque l’algorithme converge vers l’énergie minimale.
[6]:
pylab.rcParams["figure.figsize"] = (12, 8)
for i, optimizer in enumerate(optimizers):
pylab.plot(
converge_counts[i],
abs(ref_value - converge_vals[i]),
label=type(optimizer).__name__,
)
pylab.xlabel("Eval count")
pylab.ylabel("Energy difference from solution reference value")
pylab.title("Energy convergence for various optimizers")
pylab.yscale("log")
pylab.legend(loc="upper right");

Gradients¶
Dans les algorithmes variationnels de Qiskit, si l’optimiseur fourni utilise une technique basée sur un gradient, la méthode de gradient par défaut sera des différences finies. Cependant, ces classes incluent une option pour passer des gradients personnalisés via le paramètre gradient
, qui peut être n’importe laquelle des méthodes fournies dans le gradient de Qiskit, qui supporte entièrement l’utilisation des primitives. Cette section montre comment utiliser des gradients personnalisés dans le flux de travail VQE.
La première étape consiste à initialiser la primitive et la primitive de gradient correspondante :
[7]:
from qiskit.algorithms.gradients import FiniteDiffEstimatorGradient
estimator = Estimator()
gradient = FiniteDiffEstimatorGradient(estimator, epsilon=0.01)
A présent, vous pouvez inspecter une exécution SLSQP à l’aide de FiniteDiffEstimatorGradient
comme ci-dessus :
[8]:
algorithm_globals.random_seed = 50
ansatz = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")
optimizer = SLSQP(maxiter=100)
counts = []
values = []
def store_intermediate_result(eval_count, parameters, mean, std):
counts.append(eval_count)
values.append(mean)
vqe = VQE(
estimator, ansatz, optimizer, callback=store_intermediate_result, gradient=gradient
)
result = vqe.compute_minimum_eigenvalue(operator=H2_op)
print(f"Value using Gradient: {result.eigenvalue.real:.5f}")
Value using Gradient: -1.85728
[9]:
pylab.rcParams["figure.figsize"] = (12, 8)
pylab.plot(counts, values, label=type(optimizer).__name__)
pylab.xlabel("Eval count")
pylab.ylabel("Energy")
pylab.title("Energy convergence using Gradient")
pylab.legend(loc="upper right");

Point de départ¶
Par défaut, l’optimisation commence à un point aléatoire dans les limites définies par l’ansatz. L’option initial_point
permet de remplacer ce point par une liste personnalisée de valeurs qui correspondent au nombre de paramètres ansatz.
Vous pourriez vous demander… *Pourquoi définir un point initial personnalisé ? Eh bien, cette option peut vous être utile si vous avez une estimation pour un point de départ raisonnable pour le problème, ou peut-être savoir des informations d’une expérience antérieure.
Pour démontrer cette fonctionnalité, regardons les résultats de notre précédent VQE run:
[10]:
print(result)
cost_function_evals = result.cost_function_evals
{ 'aux_operators_evaluated': None,
'cost_function_evals': 9,
'eigenvalue': -1.8572750175655812,
'optimal_circuit': <qiskit.circuit.library.n_local.two_local.TwoLocal object at 0x13ef7dd20>,
'optimal_parameters': { ParameterVectorElement(θ[0]): 4.296519450348719,
ParameterVectorElement(θ[3]): 6.092947832767056,
ParameterVectorElement(θ[1]): 4.426962358395531,
ParameterVectorElement(θ[7]): 0.36021017470898664,
ParameterVectorElement(θ[4]): -2.598326651673288,
ParameterVectorElement(θ[5]): 1.5683250498282322,
ParameterVectorElement(θ[2]): 0.5470777607659094,
ParameterVectorElement(θ[6]): -4.717616147449751},
'optimal_point': array([ 4.29651945, 4.42696236, 0.54707776, 6.09294783, -2.59832665,
1.56832505, -4.71761615, 0.36021017]),
'optimal_value': -1.8572750175655812,
'optimizer_evals': None,
'optimizer_result': <qiskit.algorithms.optimizers.optimizer.OptimizerResult object at 0x13010b6a0>,
'optimizer_time': 0.3502693176269531}
Maintenant, vous pouvez prendre le optimal_point
du résultat ci-dessus et l’utiliser comme initial_point
pour un calcul ultérieur.
** Note:** initial_point
est maintenant un argument de type mot clé de la classe VQE
(c’est-à-dire qu’il doit être défini à la suite de la syntaxe mot clé = valeur
).
[11]:
initial_pt = result.optimal_point
estimator1 = Estimator()
gradient1 = FiniteDiffEstimatorGradient(estimator, epsilon=0.01)
ansatz1 = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz")
optimizer1 = SLSQP(maxiter=1000)
vqe1 = VQE(
estimator1, ansatz1, optimizer1, gradient=gradient1, initial_point=initial_pt
)
result1 = vqe1.compute_minimum_eigenvalue(operator=H2_op)
print(result1)
cost_function_evals1 = result1.cost_function_evals
print()
{ 'aux_operators_evaluated': None,
'cost_function_evals': 1,
'eigenvalue': -1.8572750175655812,
'optimal_circuit': <qiskit.circuit.library.n_local.two_local.TwoLocal object at 0x1411b9780>,
'optimal_parameters': { ParameterVectorElement(θ[0]): 4.296519450348719,
ParameterVectorElement(θ[1]): 4.426962358395531,
ParameterVectorElement(θ[4]): -2.598326651673288,
ParameterVectorElement(θ[5]): 1.5683250498282322,
ParameterVectorElement(θ[3]): 6.092947832767056,
ParameterVectorElement(θ[2]): 0.5470777607659094,
ParameterVectorElement(θ[6]): -4.717616147449751,
ParameterVectorElement(θ[7]): 0.36021017470898664},
'optimal_point': array([ 4.29651945, 4.42696236, 0.54707776, 6.09294783, -2.59832665,
1.56832505, -4.71761615, 0.36021017]),
'optimal_value': -1.8572750175655812,
'optimizer_evals': None,
'optimizer_result': <qiskit.algorithms.optimizers.optimizer.OptimizerResult object at 0x1411e3f10>,
'optimizer_time': 0.05097508430480957}
[12]:
print(
f"cost_function_evals is {cost_function_evals1} with initial point versus {cost_function_evals} without it."
)
cost_function_evals is 1 with initial point versus 9 without it.
En regardant les cost_function_evals
vous pouvez remarquer comment le point initial a aidé l’algorithme à converger plus rapidement (en une seule itération, comme nous avons déjà fourni la solution optimale).
Cela peut être particulièrement utile dans les cas où nous avons deux problèmes étroitement liés, et la solution à un problème peut être utilisée pour deviner les autres. Un bon exemple pourrait être le tracé de profils de dissociation en chimie, où nous changeons les distances interatomiques d’une molécule et calculons sa valeur propre minimale pour chaque distance. Lorsque les changements de distance sont faibles, nous nous attendons à ce que la solution soit encore proche de la solution précédente. Ainsi, une technique populaire consiste à utiliser simplement le point optimal d’une solution comme point de départ pour l’étape suivante. Il existe également des techniques plus complexes, où nous pouvons appliquer une extrapolation pour calculer une position initiale basée sur une ou des solutions antérieures plutôt que d’utiliser directement la solution précédente.
[13]:
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
Version Information
Qiskit Software | Version |
---|---|
qiskit-terra | 0.22.2 |
qiskit-aer | 0.11.1 |
qiskit-ignis | 0.7.1 |
qiskit-ibmq-provider | 0.19.2 |
qiskit | 0.39.2 |
System information | |
Python version | 3.10.2 |
Python compiler | Clang 13.0.0 (clang-1300.0.29.30) |
Python build | v3.10.2:a58ebcc701, Jan 13 2022 14:50:16 |
OS | Darwin |
CPUs | 8 |
Memory (Gb) | 64.0 |
Fri Nov 18 01:08:34 2022 CET |
This code is a part of Qiskit
© Copyright IBM 2017, 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.