Example: QAOA as QiskitPattern

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

Let’s first get information on what is QAOA runtime program and what inputs and outputs it has. We will not be implementing full set of input/outputs, but we will cover most important ones. Later on we can recover full functionality if needed.

Description: Qiskit Runtime QAOA program.

Inputs:

name

type

description

initial_point

[array,string]

Initial parameters of the ansatz. Can be an array or the string 'random' to choose random initial parameters. The type must be numpy.ndarray or str.

operator

object

The cost Hamiltonian, consisting of Pauli I and Z operators, whose smallest eigenvalue we’re trying to find. The type must be a PauliSumOp.

method

str

The classical optimizer used to update the parameters in each iteration. Per default, COBYLA

ansatz

QuantumCircuit

Ansatz for optimization

Return values

name

type

description

optimal_point

array

The optimal parameter values found during the optimization.

optimal_value

number

The smallest value found during the optimization. Equal to the eigenvalue attribute.

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 qaoa.py file.

What our pattern should do:

  1. parse input arguments

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

  3. decide which sampler to use and run vqe

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

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

  4. save results from qaoa

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

# qaoa.py

import ...

def run_qaoa(
    ansatz: QuantumCircuit,
    estimator: BaseEstimator,
    operator: PauliSumOp,
    initial_point: np.array,
    method: str
):
    return minimize(cost_func, initial_point, args=(ansatz, operator, estimator), method=method)


arguments = get_arguments()
service = arguments.get("service")
operator = arguments.get("operator")
initial_point = arguments.get("initial_point")
ansatz = arguments.get("ansatz", 1)
...
if service is not None:
    # if we have service we need to open a session and create sampler
    service = arguments.get("service")
    backend = arguments.get("backend", "ibmq_qasm_simulator")
    with Session(service=service, backend=backend) as session:
        estimator = Estimator(session=session, options=options)
else:
    # if we do not have a service let's use standart local sampler
    estimator = QiskitEstimator()

result = run_qaoa(ansatz, estimator, operator, initial_point, "COBYLA")

save_result({
    "optimal_point": result.x.tolist(),
    "optimal_value": result.fun
})

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

[1]:
import numpy as np

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

from qiskit_ibm_runtime import QiskitRuntimeService


operator = SparsePauliOp.from_list(
    [("IIIZZ", 1), ("IIZIZ", 1), ("IZIIZ", 1), ("ZIIIZ", 1)]
)
ansatz = QAOAAnsatz(operator, reps=2)
ansatz = ansatz.decompose(reps=3)
ansatz.draw(fold=-1)
[1]:
     ┌─────────────┐                                                                                                            ┌──────────────┐                                                                                            ┌──────────────┐
q_0: ┤ U3(π/2,0,π) ├─■──────────────────────────────■──────────────────────────────■──────────────────────────────■─────────────┤ Rx(2.0*β[0]) ├─■──────────────■──────────────────────────────■──────────────────────────────■─────────────┤ Rx(2.0*β[1]) ├
     ├─────────────┤ │ZZ(2.0*γ[0]) ┌──────────────┐ │                              │                              │             └──────────────┘ │ZZ(2.0*γ[1])  │             ┌──────────────┐ │                              │             └──────────────┘
q_1: ┤ U3(π/2,0,π) ├─■─────────────┤ Rx(2.0*β[0]) ├─┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────■──────────────┼─────────────┤ Rx(2.0*β[1]) ├─┼──────────────────────────────┼─────────────────────────────
     ├─────────────┤               └──────────────┘ │ZZ(2.0*γ[0]) ┌──────────────┐ │                              │                                             │ZZ(2.0*γ[1]) ├──────────────┤ │                              │
q_2: ┤ U3(π/2,0,π) ├────────────────────────────────■─────────────┤ Rx(2.0*β[0]) ├─┼──────────────────────────────┼─────────────────────────────────────────────■─────────────┤ Rx(2.0*β[1]) ├─┼──────────────────────────────┼─────────────────────────────
     ├─────────────┤                                              └──────────────┘ │ZZ(2.0*γ[0]) ┌──────────────┐ │                                                           └──────────────┘ │ZZ(2.0*γ[1]) ┌──────────────┐ │
q_3: ┤ U3(π/2,0,π) ├───────────────────────────────────────────────────────────────■─────────────┤ Rx(2.0*β[0]) ├─┼────────────────────────────────────────────────────────────────────────────■─────────────┤ Rx(2.0*β[1]) ├─┼─────────────────────────────
     ├─────────────┤                                                                             └──────────────┘ │ZZ(2.0*γ[0]) ┌──────────────┐                                                             └──────────────┘ │ZZ(2.0*γ[1]) ┌──────────────┐
q_4: ┤ U3(π/2,0,π) ├──────────────────────────────────────────────────────────────────────────────────────────────■─────────────┤ Rx(2.0*β[0]) ├──────────────────────────────────────────────────────────────────────────────■─────────────┤ Rx(2.0*β[1]) ├
     └─────────────┘                                                                                                            └──────────────┘                                                                                            └──────────────┘
[2]:
USE_RUNTIME_SERVICE = False

service = None
if USE_RUNTIME_SERVICE:
    service = QiskitRuntimeService(
        channel='ibm_quantum',
        instance='ibm-q/open/main',
        token='<insert your token>'
    )
backend = None

input_arguments = {
    "initial_point": None,
    "ansatz": ansatz,
    "operator": operator,
    "service": service,
    "backend": backend,
}
input_arguments
[2]:
{'initial_point': None,
 'ansatz': <qiskit.circuit.quantumcircuit.QuantumCircuit at 0x7fd0b0e45310>,
 'operator': SparsePauliOp(['IIIZZ', 'IIZIZ', 'IZIIZ', 'ZIIIZ'],
               coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j]),
 'service': None,
 'backend': 'ibmq_qasm_simulator'}
[3]:
from qiskit_serverless import ServerlessClient
import os
[4]:
serverless = ServerlessClient(
    token=os.environ.get("GATEWAY_TOKEN", "awesome_token"),
    host=os.environ.get("GATEWAY_HOST", "http://localhost:8000"),
)
serverless
[4]:
<ServerlessProvider: gateway-provider>
[5]:
from qiskit_serverless import QiskitFunction

function = QiskitFunction(
    title="qaoa", entrypoint="qaoa.py", working_dir="./source_files/qaoa/", dependencies=["qiskit_aer"]
)

serverless.upload(function)
[5]:
'qaoa'
[6]:
job = serverless.run("qaoa", arguments=input_arguments)
job
[6]:
<Job | a55283e5-cfc9-4c6d-8bbb-fc7216d41a47>
[7]:
job.status()
[7]:
'QUEUED'
[8]:
job.result()
[8]:
{'optimal_point': [3.6344694882728,
  1.9051953195174722,
  2.5515024534598467,
  2.701136388288486],
 'optimal_value': -3.2939248998743915}