Note

# Leveraging Qiskit Runtime¶

Iterative algorithms, such as the Variational Quantum Eigensolver (VQE), traditionally send one batch of circuits (one “job”) to be executed on the quantum device in each iteration. Sending a job involves certain overhead, mainly

• the time to process the requests and send the data (API overhead, usually about 10s)

• the job queue time, that is how long you have to wait before it’s your turn to run on the device (usually about 2min)

If we send hundreds of jobs iteratively, this overhead quickly dominates the execution time of our algorithm. Qiskit Runtime allows us to tackle these issues and significantly speed up (especially) iterative algorithms. With Qiskit Runtime, one job does not contain only a batch of circuits but the entire algorithm. That means we only experience the API overhead and queue wait once instead of in every iteration! You’ll be able to either upload algorithm parameters and delegate all the complexity to the cloud, where your program is executed, or upload your personal algorithm directly.

For the VQE, the integration of Qiskit Runtime in your existing code is a piece of cake. There is a (almost) drop-in replacement, called VQEProgram for the VQE class.

Let’s see how you can leverage the runtime on a simple chemistry example: Finding the ground state energy of the lithium hydrate (LiH) molecule at a given bond distance.

## Problem specification: LiH molecule¶

First, we specify the molecule whose ground state energy we seek. Here, we look at LiH with a bond distance of 2.5 Å.

[1]:

from qiskit_nature.drivers import UnitsType, Molecule
from qiskit_nature.drivers.second_quantization import ElectronicStructureDriverType, ElectronicStructureMoleculeDriver
from qiskit_nature.problems.second_quantization.electronic import ElectronicStructureProblem
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.mappers.second_quantization import ParityMapper
from qiskit_nature.properties.second_quantization.electronic import ParticleNumber
from qiskit_nature.transformers.second_quantization.electronic import ActiveSpaceTransformer

[2]:

bond_distance = 2.5  # in Angstrom

# define molecule
molecule = Molecule(geometry=[['Li', [0., 0., 0.]],
['H',  [0., 0., bond_distance]]],
charge=0,
multiplicity=1)

# specify driver
driver = ElectronicStructureMoleculeDriver(molecule, basis='sto3g', driver_type=ElectronicStructureDriverType.PYSCF)
properties = driver.run()

particle_number = properties.get_property(ParticleNumber)

# specify active space transformation
active_space_trafo = ActiveSpaceTransformer(num_electrons=particle_number.num_particles,
num_molecular_orbitals=3)

# define electronic structure problem
problem = ElectronicStructureProblem(driver, transformers=[active_space_trafo])

# construct qubit converter (parity mapping + 2-qubit reduction)
qubit_converter = QubitConverter(ParityMapper(), two_qubit_reduction=True)


## Classical reference solution¶

As a reference solution we can solve this system classically with the NumPyEigensolver.

[3]:

from qiskit.algorithms import NumPyMinimumEigensolver
from qiskit_nature.algorithms.ground_state_solvers import GroundStateEigensolver

np_solver = NumPyMinimumEigensolver()
np_groundstate_solver = GroundStateEigensolver(qubit_converter, np_solver)

np_result = np_groundstate_solver.solve(problem)

[4]:

import numpy as np
target_energy = np.real(np_result.eigenenergies + np_result.nuclear_repulsion_energy)[0]
print('Energy:', target_energy)

Energy: -7.773617988868097


## VQE¶

To run the VQE we need to select a parameterized quantum circuit acting as ansatz and a classical optimizer. Here we’ll choose a heuristic, hardware efficient ansatz and the SPSA optimizer.

[5]:

from qiskit.circuit.library import EfficientSU2

ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement='linear', insert_barriers=True)
ansatz.draw('mpl', style='iqx')

[5]:

[6]:

from qiskit.algorithms.optimizers import SPSA

optimizer = SPSA(maxiter=100)

np.random.seed(5)  # fix seed for reproducibility
initial_point = np.random.random(ansatz.num_parameters)


Before executing the VQE in the cloud using Qiskit Runtime, let’s execute a local VQE first.

[7]:

from qiskit.providers.basicaer import QasmSimulatorPy  # local simulator
from qiskit.algorithms import VQE

local_vqe = VQE(ansatz=ansatz,
optimizer=optimizer,
initial_point=initial_point,
quantum_instance=QasmSimulatorPy())

local_vqe_groundstate_solver = GroundStateEigensolver(qubit_converter, local_vqe)

local_vqe_result = local_vqe_groundstate_solver.solve(problem)

Measured Observable is not composed of only Paulis, converting to Pauli representation, which can be expensive.

[8]:

print('Energy:', np.real(local_vqe_result.eigenenergies + local_vqe_result.nuclear_repulsion_energy)[0])

Energy: -7.561492258661798


## Runtime VQE¶

Let’s exchange the eigensolver from a local VQE algorithm to a VQE executed using Qiskit Runtime – simply by exchanging the VQE class by the VQEProgram.

First, we’ll have to load a provider to access Qiskit Runtime. Note: You have to replace the next cell with your provider.

[9]:

from qiskit import IBMQ

provider = IBMQ.get_provider(project='qiskit-runtime')  # replace by your runtime provider

backend = provider.get_backend('ibmq_montreal')  # select a backend that supports the runtime


Now we can set up the VQEProgram. In this first release, the optimizer must be provided as a dictionary, in future releases you’ll be able to pass the same optimizer object as in the traditional VQE.

[10]:

from qiskit_nature.runtime import VQEProgram

# currently the VQEProgram supports only 'SPSA' and 'QN-SPSA'
optimizer = {
'name': 'QN-SPSA',  # leverage the Quantum Natural SPSA
# 'name': 'SPSA',  # set to ordinary SPSA
'maxiter': 100,
'resamplings': {1: 100},  # 100 samples of the QFI for the first step, then 1 sample per step
}

runtime_vqe = VQEProgram(ansatz=ansatz,
optimizer=optimizer,
initial_point=initial_point,
provider=provider,
backend=backend,
shots=1024,
measurement_error_mitigation=True)  # use a complete measurement fitter for error mitigation

[11]:

runtime_vqe_groundstate_solver = GroundStateEigensolver(qubit_converter, runtime_vqe)
runtime_vqe_result = runtime_vqe_groundstate_solver.solve(problem)

[12]:

print('Energy:', np.real(runtime_vqe_result.eigenenergies + runtime_vqe_result.nuclear_repulsion_energy)[0])

Energy: -7.575601255595115


If we are interested in the development of the energy, the VQEProgram allows access to the history of the optimizer, which contains the loss per iteration (along with the parameters and a timestamp). We can access this data via the raw_result attribute of the ground state solver.

[13]:

vqeprogram_result = runtime_vqe_result.raw_result
history = vqeprogram_result.optimizer_history
loss = history['loss']

[22]:

import matplotlib.pyplot as plt

plt.rcParams['font.size'] = 14

# plot loss and reference value
plt.figure(figsize=(12,6))
plt.plot(loss + runtime_vqe_result.nuclear_repulsion_energy, label='Runtime VQE')
plt.axhline(y=target_energy + 0.2, color='tab:red', ls=':', label='Target + 200mH')
plt.axhline(y=target_energy, color='tab:red', ls='--', label='Target')

plt.legend(loc='best')
plt.xlabel('Iteration')
plt.ylabel('Energy [H]')
plt.title('VQE energy');

[ ]: