How to upload a custom program that uses OpenQASM 3

Note

Click here to view this tutorial as an interactive Jupyter notebook in IBM Quantum Lab (requires sign-in).

Warning

This tutorial uses qiskit-ibmq-provider since it is still widely in use. However, it is scheduled to be deprecated by qiskit-ibm-runtime. You can find a similar tutorial on how to upload a custom program with qiskit-ibm-runtime here.

The existing qasm3-runner Qiskit Runtime program allows you to submit circuits and OpenQASM 3 strings to backends that supports OpenQASM 3. You can also write your own custom runtime program to do that.

Read this tutorial for a more in-depth description on how to construct and upload your own runtime program.

Construct a runtime program

If you want to use the OpenQASM 3 stack in your custom program, simply pass a OpenQASM 3 string instead of circuits to backend.run().

Note

This behavior of passing an OpenQASM 3 string to backend.run() currently only works inside the Qiskit Runtime.

In the following example, we use Qiskit’s QASM3 Exporter to convert a circuit to a OpenQASM 3 string. We also save the program to the program_data variable, allowing you to run this tutorial without additional files. When you construct your own program, however, the program code can be in a .py file.

program_data = '''

import sys
import json
from typing import Union, Optional, Dict

from qiskit import QuantumCircuit, transpile
from qiskit.qasm3 import Exporter
from qiskit.providers.ibmq.runtime import UserMessenger, ProgramBackend


def main(backend: ProgramBackend,
         user_messenger: UserMessenger,
         circuit: Union[QuantumCircuit, str],
         exporter_config: Optional[Dict] = None
        ):
    """This is the main entry point of a runtime program.

    The name of this method must not change. It also must have ``backend``
    and ``user_messenger`` as the first two positional arguments.

    Args:
        backend: Backend for the circuits to run on.
        user_messenger: Used to communicate with the program user.
        circuit: Circuit to run.
        exporter_config: OpenQASM 3 exporter configuration.
    """
    qasm3_metadata = None

    exporter_config = exporter_config or {}
    # Set disable_constants to True by default for the compiler.
    exporter_config["disable_constants"] = exporter_config.get("disable_constants", True)

    if isinstance(circuit, QuantumCircuit):
        # The compiler also requires circuit metadata.
        qasm3_metadata = get_circuit_metadata(circuit)

        # Convert circuit to OpenQASM 3 string.
        circuit = transpile(circuit, backend=backend)
        circuit = Exporter(**exporter_config).dumps(circuit)

    return backend.run(circuit, qasm3_metadata=qasm3_metadata).result()


def get_circuit_metadata(circuit: QuantumCircuit):
    """Get the circuit metadata."""

    # header data
    num_qubits = 0
    memory_slots = 0
    qubit_labels = []
    clbit_labels = []

    qreg_sizes = []
    creg_sizes = []
    for qreg in circuit.qregs:
        qreg_sizes.append([qreg.name, qreg.size])
        for j in range(qreg.size):
            qubit_labels.append([qreg.name, j])
        num_qubits += qreg.size
    for creg in circuit.cregs:
        creg_sizes.append([creg.name, creg.size])
        for j in range(creg.size):
            clbit_labels.append([creg.name, j])
        memory_slots += creg.size

    return {
        "qubit_labels": qubit_labels,
        "n_qubits": num_qubits,
        "qreg_sizes": qreg_sizes,
        "clbit_labels": clbit_labels,
        "memory_slots": memory_slots,
        "creg_sizes": creg_sizes,
        "name": circuit.name,
        "global_phase": float(circuit.global_phase),
        "metadata": circuit.metadata or {}
    }
'''

qasm3-runner does something similar but offers more input options.

You can also submit a list of OpenQASM 3 strings. The compiler right now can only process one at a time, so backend.run() will send in one string at time, wait for them all to finish, and then return to you the aggregated results.

Define program metadata

Program metadata is used to describe your program. This is not as important for custom programs since you are not sharing it with anyone, but it is still required by the upload function.

Below is a very simple example of the program metadata. One thing to note is the max_execution_time. It defines, in seconds, how long your program can run before it is forcibly terminated. There is also a system limit on runtime job execution.

program_metadata = {
    "name": "my-program",
    "max_execution_time": 7200,
    "description": "My awesome OpenQASM 3 program."
}

Program metadata can also live in a .json file.

Upload the program

You can use the upload_program() method to upload your program.

import os
from qiskit import IBMQ

IBMQ.load_account()
provider = IBMQ.get_provider(project='<project>')  # Substitute with your provider.

program_id = provider.runtime.upload_program(
    data=program_data,         # This can also be a file name.
    metadata=program_metadata  # This can also be a file name.
)
print(program_id)
my-program-zxDmqdRJ2Z

Select a backend

OpenQASM 3 strings can only be submitted to backends that support OpenQASM 3. The backend configuration will soon have a supported_features field that tells you whether it supports OpenQASM 3. You can then use the following code to find all backends that support OpenQASM 3:

provider.backends(filters=lambda b: 'qasm3' in b.configuration().supported_features)

Run the program

Use the run() method to run your custom program. Check out this tutorial for an in-depth review on running a Qiskit Runtime program.

from qiskit.test.reference_circuits import ReferenceCircuits

runtime_params = {
    "circuit": ReferenceCircuits.bell(),
}
options = {'backend_name': "<your backend>"}

job = provider.runtime.run(
    program_id=program_id,
    options=options,
    inputs=runtime_params,
)
print(job.job_id())
result = job.result()
print(result.get_counts())
camal6rvl46vjsikpm80
{'00': 1884, '01': 123, '10': 96, '11': 1897}

Update a program

Use the update_program() method to update an existing program.

provider.runtime.update_program(program_id=program_id, data=program_data, metadata=program_metadata)

Delete a program

You can use the delete_program() method to delete a program. Only the person who uploaded the program can delete it.

provider.runtime.delete_program(program_id)
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.2
qiskit-aer0.10.4
qiskit-ignis0.7.1
qiskit-ibmq-provider0.19.1
qiskit0.36.2
System information
Python version3.9.12
Python compilerClang 13.1.6 (clang-1316.0.21.2)
Python buildmain, Mar 26 2022 15:51:15
OSDarwin
CPUs6
Memory (Gb)16.0
Fri Jun 17 12:24:56 2022 EDT

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.