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 |
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 |
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 |
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:
parse input arguments
create run_vqe function that accepts estimator instance, creates VQE and runs calculation
decide which estimator to use and run vqe
if runtime service was passed then create a session and run
run_vqe
functionif runtime service was not passed then use stantard qiskit estimator
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(
channel='ibm_quantum',
instance='ibm-q/open/main',
token='<Your Token>',
verify=False
)
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 qiskit serverless client, setup provider and run our pattern
[13]:
from qiskit_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 qiskit_serverless import QiskitFunction
if USE_RUNTIME_SERVICE:
function = QiskitFunction(title="vqe", entrypoint="vqe.py", working_dir="./source_files/vqe/")
else:
function = QiskitFunction(title="vqe", entrypoint="vqe.py", working_dir="./source_files/vqe/", dependencies=["qiskit_aer"])
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}