What is qBraid?

The qBraid-SDK is an open-source Python framework providing a complete, platform-agnostic quantum runtime solution. Distinguishing itself through a streamlined and highly-configurable approach to cross-platform integration, the qBraid-SDK does not adhere to a fixed circuit-building library or quantum program representation. Instead, it allows clients to dynamically register and submit quantum programs of any type compatible with the architecture of the target device. This flexibility extends to customizable pipelines for program validation, transpilation, and compilation.

The qbraid.runtime.IonQProvider provides support for IonQ’s trapped-ion systems. This means that you can write quantum circuits in Qiskit, Cirq, Amazon Braket, Pennylane, PyTKET, or any other library compatible with OpenQASM (version 2 or 3), and run them on IonQ’s simulators and trapped-ion quantum computers, all from within the qBraid Runtime framework.

Getting started

Before you begin, make sure you have an IonQ Quantum Cloud account and API key. For help, see our guide on creating and managing API keys.

Set up the qBraid-SDK

Install qBraid with the ionq extra from PyPI using pip:

pip install 'qbraid[ionq]'

Note: The qBraid-SDK requires Python 3.10 or greater. You can check your Python version by running python --version from the command line.

We encourage doing this inside an environment management system, such as virtualenv or conda. Alternatively, you can bypass this step by using a pre-configured qBraid Lab environment. See qBraid-SDK installation and setup for more.

Set up your environment

By default, qBraid will look in your local environment for a variable named IONQ_API_KEY, so if you’ve already followed our guide on setting up and managing your API keys, qBraid will automatically find it.

Alternatively, you can set a “temporary” environment variable from your command line:

export IONQ_API_KEY="your_api_key_here"

While we recommend setting an environment variable so that qBraid can find your API key, you can also pass in your API key explicitly within your Python code, when creating the IonQ Provider object that authenticates your connection to the IonQ Cloud Platform. This might be necessary if you’ve named your environment variable something other than IONQ_API_KEY, or if you are working from a Python environment where accessing environment variables is not straightforward. You can import your key explicitly or load it from a file, and pass it into the IonQProvider() object directly:

import os
from qbraid.runtime import IonQProvider

# Load your API key from an environment variable named MY_IONQ_API_KEY
my_api_key = os.getenv("MY_IONQ_API_KEY")
provider = IonQProvider(my_api_key)

In the examples below, we show IonQProvider() initialized with no arguments and assume that qBraid will automatically find your API key, but you can always use this approach instead.

List available devices

Use the IonQProvider to list all of the devices to which you have access:

from qbraid.runtime import IonQProvider

provider = IonQProvider()

devices = provider.get_devices()

Running this script should print the results below—something like this:

[<qbraid.runtime.ionq.device.IonQDevice('qpu.harmony')>,
<qbraid.runtime.ionq.device.IonQDevice('qpu.aria-1')>,
<qbraid.runtime.ionq.device.IonQDevice('qpu.aria-2')>,
<qbraid.runtime.ionq.device.IonQDevice('qpu.forte-1')>,
<qbraid.runtime.ionq.device.IonQDevice('simulator')>]

If this works correctly then your qBraid-SDK installation is correct, your IonQ API key is valid, and you have access to the IonQ devices!

Submit a circuit to the simulator

First, let’s try running a simple Bell state circuit on the ideal simulator. Here, we’ll use 1000 shots, and give the circuit a name that will show up in the IonQ Cloud Console.

Your input OpenQASM program should not include measurement statements. Measurement will be applied over all qubits at the end of the circuit, automatically. Mid-circuit measurements and partial measurements are not supported.

from qbraid.runtime import IonQProvider

provider = IonQProvider()
device = provider.get_device("simulator")

# Define a Bell state circuit in qasm
qasm = """
OPENQASM 3.0;
qubit[2] q;
h q[0];
cx q[0], q[1];
"""

job = device.run(qasm, name="Hello many worlds!", shots=1000)
result = job.result()

print(result.data.get_counts())

This returns:

{'00': 500, '11': 500}

As expected, the ideal simulator creates a quantum state with a 50-50 probability of being measured as “00” or “11”. To view the calculated probabilities for a circuit run on the simulator, use result.data.get_probabilities(). You can return histogram data in decimal format for either of these result.data methods using argument decimal=True.

Submit a circuit with noise

Get a list of all noise models supported by the IonQ simulator:

print(device.profile.noise_models)

Then, to run a noisy simulation, simply specify a noise model and an optional random seed:

job = device.run(qasm, shots=1000, noise={"model" : "aria-1", "seed": 42})

You can read more about simulation with noise models here.

Submit a circuit to a QPU

You can view your access to IonQ systems in the “Backends” tab of the IonQ Cloud Console. Before submitting to any QPU, we recommend testing your code on a simulator (including with noise model) and following the other steps in the QPU submission checklist to confirm your access and the QPU availability.

To run a circuit on a QPU, use the same setup as before, just with a different device ID:

from qbraid.runtime import IonQProvider

provider = IonQProvider()
device = provider.get_device("qpu.aria-1")

Verify that the device is ONLINE:

print(device.status())

View device characterization data including the connectivity, fidelity, timings, and more:

print(device.profile.characterization)

Next, construct your quantum program, ensuring it aligns with the device’s supported gateset. In this example, we’ll define GHZ state circuit using OpenQASM 3:

qasm = """
OPENQASM 3.0;
qubit[3] q;
h q[0];
cx q[0], q[1];
cx q[0], q[2];
"""

Estimate Cost

You can use the device.run() method with the preflight=True option to obtain a cost estimate for the anticipated job without actually executing the program on a QPU.

job = device.run(qasm, name="GHZ (Preflight)", shots=100, preflight=True)

job.wait_for_final_state()

metadata = job.metadata()
cost_usd = metadata["cost_usd"]

print(f"Cost Estimate (USD): {cost_usd}")

If you plan to run your job with debiasing, be sure to include the error_mitigation parameter in the preflight submission to ensure it is accounted for in the cost estimate:

job = device.run(..., preflight=True, error_mitigation={"debias": True})

Submit to QPU

When you are ready to submit the job on the QPU, simply set preflight=False, or don’t specify any preflight value. Optionally include job tags or other metadata for your convenience, specifying up to 10 key-value pairs.

job = device.run(qasm, name="GHZ", shots=100, metadata={"key": "str_value"})

When submitting jobs to a QPU, your job may need to wait in the queue. You can print and record the job’s unique ID, which can be used to retrieve the job (including its status and results) at a later time, see Retrieve a job.

print(job.id)

Check the status of your job:

print(job.status())

Once the job is COMPLETED, you can retrieve the results:

result = job.result()

counts = result.data.get_counts(decimal=True)

print(f"Counts: {counts}")

print(f"Details: {result.details}")

Submit a multicircuit job

The flexibility of qBraid-SDK allows you to write your circuit not just in OpenQASM, but in Qiskit, Cirq, Amazon Braket, PyTKET, or any other registered program type that can be transpiled to qBraid’s qasm2 or qasm3 format.

In this example, we’ll install the qiskit and cirq extras,

pip install 'qbraid[qiskit,cirq]'

and submit a multicircuit job containing one of each program type:

import cirq

from qbraid.runtime import IonQProvider
from qiskit import QuantumCircuit

provider = IonQProvider()
device = provider.get_device("simulator")

# Define a bell state in qiskit
qiskit_bell = QuantumCircuit(2)
qiskit_bell.h(0)
qiskit_bell.cx(0, 1)

# Define a bell state in cirq
cirq_bell = cirq.Circuit()
q0, q1 = cirq.LineQubit.range(2)
cirq_bell.append(cirq.H(q0))
cirq_bell.append(cirq.CNOT(q0, q1))

circuit_batch = [qiskit_bell, cirq_bell]

job = device.run(circuit_batch, shots=1000, noise={"model": "aria-1"})

result = job.result()
counts_batch = result.data.get_counts()

print("\n".join(f"Circuit {i}: {c}" for i, c in enumerate(counts_batch)))

This script submits two quantum circuits in a single job. When the job completes, it prints the counts for each circuit:

Circuit 0: {'00': 492, '01': 4, '10': 3, '11': 501}
Circuit 1: {'00': 482, '01': 6, '10': 6, '11': 506}

All of the necessary program type conversions are carried out automatically within the device.run() method. However, you can also perform this “transpile” step manually, if needed (e.g. to inspect the ultimate OpenQASM submission format). This can be done as follows:

from qbraid import transpile

qasm_batch = [transpile(circuit, "qasm2") for circuit in circuit_batch]

jobs = device.run(qasm_batch, shots=1000, ...)

qBraid offers native support for 10 major quantum programming libraries including 20+ inter-library conversions. For more details, including how to configure your own custom conversions, refer to the qBraid transpiler documentation.

Manage jobs

Retrieve a job

You can retrieve results for a previously run job using its job ID. You can save the job ID after submitting a job (as in the QPU example above) or copy it from the “ID” column in the “My Jobs” tab on the IonQ Cloud Console.

from qbraid.runtime import IonQJob, IonQProvider

job_id = "..."

provider = IonQProvider()

job = IonQJob(job_id, provider.session)

result = job.result()

print(result.data.get_counts())

Cancel a job

You can cancel a job while it’s waiting in the queue:

job.cancel()

Visualize Results

To plot the results of a job, first, install the qBraid visualization extra:

pip install 'qbraid[visualization]'

You can visualize the histogram counts data using plot_histogram, or display the probability distribution with plot_distribution.

from qbraid.visualization import plot_histogram

plot_histogram(counts)

The “counts” input may be either a single dictionary or a list of dictionaries for multicircuit jobs. In the latter case, histogram data for each circuit will be plotted side-by-side. The X-axis labels will display in decimal if you set decimal=True when retrieving measurement counts with get_counts(). If decimal=False or left unspecified, the default quantum state labels in hexadecimal will be used. See Plot Experimental Results for more.

Supported gates

For actual execution, gates will be compiled into optimal operations for our trapped ion hardware. For convenience, IonQ provide a more expressive gateset for programming. However, not all gates supported by the OpenQASM 3 standard library are accepted by IonQ backends. See full list of supported gates.

You can also view a list of the OpenQASM gates supported by a given device directly from an IonQDevice. For example:

device = provider.get_device("qpu.forte-1")

print(device.profile.basis_gates)

Which would return:

{'swap', 'tdg', 't', 'gpi2', 'gpi', 'rz', 'sx', 'z', 's', 'sdg', 'zz', 'cx', 'rx', 'y', 'h', 'ry', 'x', 'sxdg'}

Note: the returned gateset includes both the abstract QIS and IonQ-native gates supported by the device; however, circuits must be constructed using either exclusively QIS gates or exclusively native gates—you cannot mix the two within a single circuit. In the next section, we’ll explore using IonQ native gates with qBraid in greater detail.

Additional resources

Great work! You successfully ran your first quantum circuits - what next?

Explore more resources for using qBraid:

Learn about advanced features for using IonQ systems with qBraid:

Find examples for using IonQ systems with other quantum programming libraries: