Note

# Monitoring VQE convergence¶

Variational algorithms in Qiskit, like VQE and QAOA, provide the option for a user to give a callback method that can be used to monitor optimization progress as the algorithm runs and converges to the minimum. The callback is invoked for each functional evaluation by the optimizer and provides the current optimizer value, evaluation count, current optimizer parameters etc. Note that, depending on the specific optimizer this may not be each iteration (step) of the optimizer, so for example if the optimizer is calling the cost function to compute a finite difference based gradient this will be visible via the callback.

This notebook demonstrates using Qiskit’s VQE algorithm to plot graphs of the convergence path to ground state energy with a selected set of optimizers.

[1]:

import numpy as np
import pylab

from qiskit import Aer
from qiskit.opflow import X, Z, I
from qiskit.utils import QuantumInstance, algorithm_globals
from qiskit.algorithms import VQE, NumPyMinimumEigensolver
from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B, SLSQP
from qiskit.circuit.library import TwoLocal


First we create a qubit operator for VQE. Here we will use the same operator as used in the algorithms introduction, which was originally computed by Qiskit Nature for an H2 molecule.

[2]:

H2_op = (-1.052373245772859 * I ^ I) + \
(0.39793742484318045 * I ^ Z) + \
(-0.39793742484318045 * Z ^ I) + \
(-0.01128010425623538 * Z ^ Z) + \
(0.18093119978423156 * X ^ X)


We will show the callback usage below over a set of optimizers for comparison. The minimum energy of the H2 Hamiltonian can be found quite easily so we are able to set maxiters to a small value

[3]:

optimizers = [COBYLA(maxiter=80), L_BFGS_B(maxiter=60), SLSQP(maxiter=60)]
converge_cnts = 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(ansatz, optimizer, callback=store_intermediate_result,
quantum_instance=QuantumInstance(backend=Aer.get_backend('statevector_simulator')))
result = vqe.compute_minimum_eigenvalue(operator=H2_op)
converge_cnts[i] = np.asarray(counts)
converge_vals[i] = np.asarray(values)
print('\rOptimization complete      ');

Optimization complete


Now from the callback data we stored we can plot the energy value at each objective function call each optimizer makes. An optimizer using a finite difference method for computing gradient has that characteristic step like plot where for a number of evaluations it is computing the value for close by points to establish a gradient (the close by points having very similar values whose difference cannot be seen on the scale of the graph here).

[4]:

pylab.rcParams['figure.figsize'] = (12, 8)
for i, optimizer in enumerate(optimizers):
pylab.plot(converge_cnts[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');


Finally since the above problem is still easily tractable classically we can use NumPyMinimumEigensolver to compute a reference value for the solution. We can now plot the difference from the resultant exact solution as the energy converges with VQE towards the minimum value which should be that exact classical solution.

[5]:

npme = NumPyMinimumEigensolver()
result = npme.compute_minimum_eigenvalue(operator=H2_op)
ref_value = result.eigenvalue.real
print(f'Reference value: {ref_value:.5f}')

Reference value: -1.85728

[6]:

pylab.rcParams['figure.figsize'] = (12, 8)
for i, optimizer in enumerate(optimizers):
pylab.plot(converge_cnts[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');


Qiskit now has a Gradient framework as part of the Opflow capability. With the gradient computed for the optimizer we now see just the optimization steps themselves.

[7]:

from qiskit.opflow.gradients import Gradient

algorithm_globals.random_seed = 50
ansatz = TwoLocal(rotation_blocks='ry', entanglement_blocks='cz')

optimizer = SLSQP(maxiter=60)

counts = []
values = []
def store_intermediate_result(eval_count, parameters, mean, std):
counts.append(eval_count)
values.append(mean)

vqe = VQE(ansatz, optimizer, callback=store_intermediate_result,
quantum_instance=QuantumInstance(backend=Aer.get_backend('statevector_simulator')))
result = vqe.compute_minimum_eigenvalue(operator=H2_op)

Value using Gradient: -1.07393

[8]:

pylab.rcParams['figure.figsize'] = (12, 8)
pylab.plot(counts, values, label=type(optimizer).__name__)
pylab.xlabel('Eval count')
pylab.ylabel('Energy')
pylab.legend(loc='upper right');


## Monitoring via the logging¶

Much of the code is instrumented with Python logging statements. The logging is configurable to adjust level of logging etc. Here we set the logging level to INFO

[9]:

import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('qiskit.algorithms.minimum_eigen_solvers.vqe').setLevel(logging.INFO)


and at INFO level logging VQE will include information on the evaluations, as below.

INFO:qiskit.algorithms.minimum_eigen_solvers.vqe:Energy evaluation returned [-1.07392554] - 116.41884 (ms), eval count: 1 INFO:qiskit.algorithms.minimum_eigen_solvers.vqe:Energy evaluation returned [-1.43698938] - 4.05884 (ms), eval count: 2 INFO:qiskit.algorithms.minimum_eigen_solvers.vqe:Energy evaluation returned [-1.74596698] - 7.40194 (ms), eval count: 3 INFO:qiskit.algorithms.minimum_eigen_solvers.vqe:Energy evaluation returned [-1.75399268] - 6.61016 (ms), eval count: 4

[10]:

import qiskit.tools.jupyter
%qiskit_version_table

/home/runner/work/qiskit/qiskit/.tox/docs/lib/python3.8/site-packages/qiskit/aqua/__init__.py:86: DeprecationWarning: The package qiskit.aqua is deprecated. It was moved/refactored to qiskit-terra For more information see <https://github.com/Qiskit/qiskit-aqua/blob/main/README.md#migration-guide>
warn_package('aqua', 'qiskit-terra')


### Version Information

Qiskit SoftwareVersion
qiskit-terra0.18.3
qiskit-aer0.9.1
qiskit-ignis0.6.0
qiskit-ibmq-provider0.18.1
qiskit-aqua0.9.5
qiskit0.32.1
qiskit-nature0.2.2
qiskit-finance0.2.1
qiskit-optimization0.2.3
qiskit-machine-learning0.2.1
System information
Python3.8.12 (default, Oct 18 2021, 14:07:50) [GCC 9.3.0]
OSLinux
CPUs2
Memory (Gb)6.788982391357422
Wed Nov 24 22:46:56 2021 UTC

### This code is a part of Qiskit

[ ]: