Nota
Esta página fue generada a partir de tutorials/algorithms/02_vqe_advanced_options.ipynb.
Opciones Avanzadas de VQE¶
En el primer tutorial de algoritmos, aprendiste cómo configurar un algoritmo VQE básico. Ahora, verás cómo proporcionar parámetros de configuración más avanzados para explorar la gama completa de capacidades de los algoritmos variacionales de Qiskit: VQE, QAOA y VQD entre otros. En particular, este tutorial cubrirá cómo configurar una callback
para monitorear la convergencia y el uso de``initial point``s y gradient
s personalizados.
Devolución de Llamada (Callback)¶
Los métodos de devolución de llamada (callback) se pueden usar para monitorear el progreso de la optimización a medida que el algoritmo se ejecuta y converge al mínimo. El optimizador invoca la devolución de llamada para cada evaluación funcional y proporciona el valor actual del optimizador, el recuento de evaluaciones, los parámetros actuales del optimizador, etc. Ten en cuenta que, según el optimizador específico, es posible que no sea cada iteración (paso) del optimizador, por ejemplo, si el optimizador está llamando a la función de costo para calcular un gradiente basado en diferencias finitas, esto será visible a través de la devolución de llamada.
Esta sección demuestra cómo aprovechar las devoluciones de llamada en VQE
para graficar la ruta de convergencia a la energía del estado fundamental con un conjunto seleccionado de optimizadores.
Primero, necesitas un operador de qubit para VQE. Para este ejemplo, puedes usar el mismo operador que se usó en la introducción de los algoritmos, que Qiskit Nature calculó originalmente para una molécula de 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),
]
)
El siguiente paso es instanciar el Estimator
elegido para la evaluación de los valores esperados dentro de VQE
. Para simplificar, puedes seleccionar qiskit.primitives.Estimator
entregado con la instalación predeterminada de Qiskit Terra.
[2]:
from qiskit.primitives import Estimator
estimator = Estimator()
Ahora estás listo para comparar un conjunto de optimizadores a través de la devolución de llamada de VQE
. La energía mínima del Hamiltoniano H2 se puede encontrar con bastante facilidad, por lo que el número máximo de iteraciones (maxiter
) no tiene por qué ser muy grande. Puedes volver a utilizar TwoLocal
como la función de onda de prueba seleccionada (es decir, el 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
Ahora, a partir de los datos de devolución de llamada que almacenaste, puedes graficar el valor de la energía en cada llamada de la función objetivo que hace cada optimizador. Un optimizador que utiliza un método de diferencias finitas para calcular el gradiente tiene esa gráfica escalonada característica en el que, para una serie de evaluaciones, calcula el valor de los puntos cercanos para establecer un gradiente (los puntos cercanos tienen valores muy similares cuya diferencia no se puede ver en la escala de la gráfica aquí).
[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");

Finalmente, dado que el problema anterior todavía es fácilmente tratable de forma clásica, puedes usar NumPyMinimumEigensolver
para calcular un valor de referencia para la solución.
[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
Ahora puedes graficar la diferencia entre la solución de VQE
y este valor de referencia exacto a medida que el algoritmo converge hacia la energía mínima.
[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");

Gradientes¶
En los algoritmos variacionales de Qiskit, si el optimizador proporcionado utiliza una técnica basada en gradientes, el método de gradiente predeterminado será el de diferencias finitas. Sin embargo, estas clases incluyen una opción para pasar gradientes personalizados a través del parámetro gradient
, que puede ser cualquiera de los métodos proporcionados dentro del framework de gradiente de Qiskit, que soporta completamente el uso de primitivas. Esta sección muestra cómo usar gradientes personalizados en el flujo de trabajo de VQE.
El primer paso es inicializar tanto la correspondiente primitiva como el gradiente primitivo:
[7]:
from qiskit.algorithms.gradients import FiniteDiffEstimatorGradient
estimator = Estimator()
gradient = FiniteDiffEstimatorGradient(estimator, epsilon=0.01)
Ahora, puedes inspeccionar una ejecución de SLSQP usando el FiniteDiffEstimatorGradient
de arriba:
[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");

Punto inicial¶
Por defecto, la optimización comienza en un punto aleatorio dentro de los límites definidos por el ansatz. La opción initial_point
permite anular este punto con una lista personalizada de valores que coincidan con el número de parámetros del ansatz.
Quizás te preguntes… ¿Por qué establecer un punto inicial personalizado? Bueno, esta opción puede ser útil si tienes una suposición de un punto de partida razonable para el problema, o tal vez conoces información de un experimento anterior.
Para demostrar esta característica, veamos los resultados de nuestra ejecución anterior de VQE:
[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}
Ahora, puedes tomar el optimal_point
del resultado anterior y usarlo como el initial_point
para un cálculo de seguimiento.
Nota: initial_point
ahora es un argumento de palabra clave únicamente de la clase VQE
(es decir, debe establecerse siguiendo la sintaxis keyword=value
).
[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.
Al observar el cost_function_evals
, puedes notar cómo el punto inicial ayudó al algoritmo a converger más rápido (en solo 1 iteración, ya que proporcionamos la solución óptima).
Esto puede ser particularmente útil en casos en los que tenemos dos problemas estrechamente relacionados, y la solución de un problema puede usarse para adivinar la del otro. Un buen ejemplo podría ser graficar perfiles de disociación en química, donde cambiamos las distancias interatómicas de una molécula y calculamos su valor propio mínimo para cada distancia. Cuando los cambios de distancia son pequeños, esperamos que la solución aún esté cerca de la anterior. Por lo tanto, una técnica popular es simplemente usar el punto óptimo de una solución como punto de partida para el siguiente paso. También existen técnicas más complejas, en las que podemos aplicar la extrapolación para calcular una posición inicial basada en la(s) solución(es) anterior(es) en lugar de usar directamente la solución anterior.
[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.