Example: VQE as QiskitPattern#

This tutorial will be demonstation of creating VQE as QiskitPattern as well as migration guide on how you can replicate IBM Quantum VQE custom runtime program.

Let’s first get information on what is VQE runtime program and what inputs and outputs it has.

Description of runtime program is: Variational Quantum Eigensolver (VQE) to find the minimal eigenvalue of a Hamiltonian.

Inputs:

name

type

description

ansatz

object

A parameterized quantum circuit preparing the ansatz wavefunction for the VQE. It is assumed that all qubits are initially in the 0 state.

initial_parameters

[array,string]

Initial parameters of the ansatz. Can be an array or the string 'random' to choose random initial parameters.

operator

object

The Hamiltonian whose smallest eigenvalue we’re trying to find. Should be PauliSumOp

method

str

The classical optimizer used in to update the parameters in each iteration.

Return values

name

type

description

cost_function_evals

integer

The number of cost function (energy) evaluations.

optimal_parameters

null

Not supported at the moment, therefore None.

optimal_point

array

The optimal parameter values found during the optimization. This is a numpy array.

optimal_value

number

The smallest value found during the optimization. Equal to the eigenvalue attribute. This is a float.

optimizer_evals

integer

The number of steps of the optimizer.

optimizer_history

object

A dictionary containing information about the function evaluations (not necessarily the actual parameter value!): the current evaluation count, the parameters, the energy and the standard deviation.

optimizer_time

number

The total time taken by the optimizer. This is a float.

We will also add optional QiskitRuntimeService as an argument to use that to access real devices.

With that information we can start drafting our pattern implementation in vqe.py file.

What our pattern should do:

  1. parse input arguments

  2. create run_vqe function that accepts estimator instance, creates VQE and runs calculation

  3. decide which estimator to use and run vqe

    • if runtime service was passed then create a session and run run_vqe function

    • if runtime service was not passed then use stantard qiskit estimator

  4. save results from vqe

Roughly our VQE pattern will look like this. Full code can be found in vqe.py file.

# vqe.py

import ...

def run_vqe(
    initial_parameters,
    ansatz,
    operator,
    estimator,
    method
):
    ...

arguments = get_arguments()

service = arguments.get("service")
ansatz = arguments.get("ansatz")
operator = arguments.get("operator")
initial_parameters = arguments.get("initial_parameters")
optimizer = ...

...

if service is not None:
    # if we have service we need to open a session and create estimator
    backend = arguments.get("backend", "ibmq_qasm_simulator")
    with Session(service=service, backend=backend) as session:
        estimator = Estimator(session=session, options=options) # qiskit_ibm_runtime.Estimator
        vqe_result = run_vqe( estimator=estimator, ...)
else:
    # if we do not have a service let's use standart local estimator
    estimator = QiskitEstimator() # qiskit.primitives.Estimator

vqe_result, callback_dict = run_vqe(
    initial_parameters=initial_parameters,
    ansatz=ansatz,
    operator=operator,
    estimator=estimator,
    method=method
)

save_result({
    "optimal_point": vqe_result.x.tolist(),
    "optimal_value": vqe_result.fun,
    "optimizer_evals": vqe_result.nfev,
    "optimizer_history": callback_dict.get("cost_history", []),
    "optimizer_time": callback_dict.get("_total_time", 0)
})

At this point we have our pattern implemented. Now we need to actually run it. But before let’s prepare input arguments from our VQE pattern.

[12]:
import numpy as np


from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import SparsePauliOp

from qiskit_ibm_runtime import QiskitRuntimeService, Estimator, Session, Options

USE_RUNTIME_SERVICE = False

service = None
if USE_RUNTIME_SERVICE:
    service = QiskitRuntimeService()
    backend = "ibmq_qasm_simulator"

operator = SparsePauliOp.from_list(
    [("YZ", 0.3980), ("ZI", -0.3980), ("ZZ", -0.0113), ("XX", 0.1810)]
)
ansatz = EfficientSU2(operator.num_qubits)

input_arguments = {
    "ansatz": ansatz,
    "operator": operator,
    "method": "COBYLA",
    "service": service,
}

input_arguments
[12]:
{'ansatz': <qiskit.circuit.library.n_local.efficient_su2.EfficientSU2 at 0x7f8ee8de92b0>,
 'operator': SparsePauliOp(['YZ', 'ZI', 'ZZ', 'XX'],
               coeffs=[ 0.398 +0.j, -0.398 +0.j, -0.0113+0.j,  0.181 +0.j]),
 'method': 'COBYLA',
 'service': None}

With arguments prepared we can create our quantum serverless client, setup provider and run our pattern

[13]:
from quantum_serverless import ServerlessClient
import os
[14]:
serverless = ServerlessClient(
    token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
    host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
serverless
[14]:
<ServerlessProvider: gateway-provider>
[15]:
from quantum_serverless import QiskitFunction

function = QiskitFunction(title="vqe", entrypoint="vqe.py", working_dir="./source_files/vqe/")

serverless.upload(function)
[15]:
'vqe'
[16]:
job = serverless.run("vqe", arguments=input_arguments)
job
[16]:
<Job | 94ae450e-bb6f-46c3-a0b0-1ad12b91115f>
[17]:
job.status()
[17]:
'QUEUED'
[18]:
job.result()
[18]:
{'result': {'0': 0.4223, '1': 0.3604, '2': 0.1073, '3': 0.11},
 'optimal_point': [2.51762813907937,
  1.532634671366952,
  6.968201754881848,
  1.8258529400009142,
  1.5453234923701027,
  3.905921764150066,
  1.6694898480396192,
  1.075020301957671,
  0.8048376424004327,
  2.823196594205023,
  2.9665234835014846,
  4.143832547893007,
  4.382722375425133,
  4.582108812661252,
  6.596830693043498,
  4.716678649450963],
 'optimal_value': -0.7029303910686284,
 'optimizer_time': 3.4171429999987595}