Amazon Braket Intermediate Free 5/11 in series 55 minutes

Comparing Quantum Hardware on Amazon Braket: IonQ vs Rigetti vs OQC vs QuEra

Run the same benchmark circuit across multiple Braket hardware providers, compare noise levels, gate sets, connectivity, pricing, and latency.

What you'll learn

  • Amazon Braket
  • hardware comparison
  • IonQ Aria
  • Rigetti Ankaa
  • OQC Lucy
  • QuEra Aquila
  • benchmarking

Prerequisites

  • Python proficiency
  • Beginner quantum computing concepts (superposition, entanglement)
  • Linear algebra basics

Amazon Braket’s Hardware Ecosystem

Amazon Braket provides access to quantum hardware from multiple providers through a unified API. Rather than learning a different SDK for each vendor, you write circuits once and route them to different backends by changing a device ARN. This makes Braket an ideal platform for hardware comparison studies.

As of 2026, the major gate-based hardware providers available through Braket include:

ProviderDeviceTechnologyQubits
IonQAriaTrapped ion25
RigettiAnkaa-2Superconducting84
OQCLucySuperconducting8
QuEraAquilaNeutral atom (analog)256

QuEra’s Aquila is an analog device and uses a different programming model (Hamiltonian evolution rather than gate sequences), so most of this tutorial focuses on gate-based comparisons across IonQ, Rigetti, and OQC.

Setup

pip install amazon-braket-sdk boto3
from braket.aws import AwsDevice, AwsQuantumTask
from braket.circuits import Circuit, Gate, Qubit
from braket.devices import LocalSimulator
import json
import time

Querying Device Properties

Before running anything, inspect each device’s capabilities:

device_arns = {
    "IonQ Aria":    "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1",
    "Rigetti Ankaa-2": "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2",
    "OQC Lucy":     "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy",
}

for name, arn in device_arns.items():
    try:
        device = AwsDevice(arn)
        props = device.properties
        print(f"\n=== {name} ===")
        print(f"  Status:           {device.status}")
        print(f"  Paradigm:         {props.paradigm}")
        if hasattr(props, 'provider'):
            print(f"  Provider:         {props.provider}")
    except Exception as e:
        print(f"{name}: {e}")

Native Gate Sets

Different hardware technologies support different native gates. Understanding native gate sets matters because gates not in the native set are compiled into multiple native gates, increasing circuit depth and error.

def print_native_gates(device_arn: str, label: str):
    device = AwsDevice(device_arn)
    try:
        gates = device.properties.paradigm.nativeGateSet
        print(f"{label}: {gates}")
    except AttributeError:
        print(f"{label}: gate set info not available in this API version")

# IonQ: native set is {GPI, GPI2, MS} (Molmer-Sorensen)
# Rigetti: native set is {RZ, RX(pi/2), CZ} or {RZ, RX, XY}
# OQC: native set is {Rz, SX, ECR}
for name, arn in device_arns.items():
    print_native_gates(arn, name)

IonQ Aria uses Molmer-Sorensen (MS) gates for entanglement, which are all-to-all connected. Any two qubits can be entangled without routing.

Rigetti Ankaa-2 uses a fixed lattice topology. Two-qubit gates are only directly available between connected pairs, so arbitrary connectivity requires SWAP insertion.

OQC Lucy uses an 8-qubit ring topology with the Echoed Cross-Resonance (ECR) gate as the native two-qubit gate.

Inspecting Connectivity and Calibration Data

def print_connectivity(device_arn: str, label: str):
    device = AwsDevice(device_arn)
    try:
        connectivity = device.properties.paradigm.connectivity
        print(f"\n{label} connectivity:")
        print(f"  Fully connected: {connectivity.fullyConnected}")
        if not connectivity.fullyConnected:
            graph = connectivity.connectivityGraph
            print(f"  Edges (sample): {list(graph.items())[:5]}")
    except Exception as e:
        print(f"{label}: {e}")

for name, arn in device_arns.items():
    print_connectivity(arn, name)

Calibration data (T1, T2, gate fidelities) can be retrieved where available:

def print_calibration(device_arn: str, label: str):
    device = AwsDevice(device_arn)
    try:
        cal = device.properties.provider.specs
        # Print a subset of calibration data
        for qubit_id, qubit_data in list(cal.items())[:2]:
            print(f"  Qubit {qubit_id}: {qubit_data}")
    except Exception:
        print(f"  {label}: calibration data format varies by provider")

The Benchmark Circuit: GHZ State

We use a 3-qubit GHZ state as the benchmark. It is simple enough to run on all gate-based devices but sensitive enough to two-qubit gate errors that it discriminates between hardware quality levels.

def ghz_circuit(n_qubits: int = 3) -> Circuit:
    """Build an n-qubit GHZ state circuit."""
    circ = Circuit()
    circ.h(0)
    for i in range(n_qubits - 1):
        circ.cnot(i, i + 1)
    circ.probability()  # measure all qubits, return probabilities
    return circ

ghz = ghz_circuit(3)
print(ghz)

For an ideal 3-qubit GHZ state, the only outcomes are 000 and 111, each with probability 0.5. Any other outcome is a hardware error.

Ideal Fidelity Metric

We define GHZ fidelity as the total probability in the correct outcomes:

def ghz_fidelity(result_dict: dict, n_qubits: int) -> float:
    """Compute GHZ fidelity from measurement counts."""
    total = sum(result_dict.values())
    correct_states = {"0" * n_qubits, "1" * n_qubits}
    correct_counts = sum(result_dict.get(s, 0) for s in correct_states)
    return correct_counts / total

# Simulate ideal result
local_sim = LocalSimulator()
ideal_task = local_sim.run(ghz_circuit(3), shots=1000)
ideal_result = ideal_task.result()
ideal_counts = ideal_result.measurement_counts
print(f"Ideal GHZ fidelity: {ghz_fidelity(ideal_counts, 3):.4f}")

Running on Hardware (Asynchronous)

Braket tasks are asynchronous. Submit tasks and retrieve results later:

def submit_ghz(device_arn: str, shots: int = 200):
    """Submit a GHZ benchmark task and return the task object."""
    device = AwsDevice(device_arn)
    circ = ghz_circuit(3)
    # Use the first 3 qubits; OQC Lucy has 8, Rigetti has more
    task = device.run(circ, shots=shots)
    print(f"Submitted to {device_arn.split('/')[-1]}: task ARN = {task.id}")
    return task

# Submit to all devices (costs real money - use with caution)
# tasks = {name: submit_ghz(arn) for name, arn in device_arns.items()}

# Retrieve results (poll until complete)
# results = {}
# for name, task in tasks.items():
#     results[name] = task.result()
#     print(f"{name} fidelity: {ghz_fidelity(results[name].measurement_counts, 3):.4f}")

Simulating Different Device Noise Levels

Since running on real hardware costs money and requires queue time, we can use Braket’s noise simulators to compare device characteristics locally:

from braket.circuits import Circuit
from braket.devices import LocalSimulator
from braket.circuits.noise import Depolarizing, BitFlip

def simulate_with_noise(two_q_error: float, readout_error: float,
                        shots: int = 2000) -> float:
    """Simulate GHZ circuit with specified noise and return fidelity."""
    circ = Circuit()
    circ.h(0)
    circ.cnot(0, 1)
    circ.cnot(1, 2)
    
    # Add two-qubit depolarizing noise after each CNOT
    circ.depolarizing(0, two_q_error)
    circ.depolarizing(1, two_q_error)
    
    # Add readout bit-flip noise
    for q in range(3):
        circ.bit_flip(q, readout_error)
    
    circ.probability()
    
    sim = LocalSimulator("braket_dm")  # density matrix simulator
    task = sim.run(circ, shots=shots)
    counts = task.result().measurement_counts
    return ghz_fidelity(counts, 3)

# Compare noise profiles representative of each hardware type
noise_profiles = {
    "IonQ Aria (ion trap)":      (0.003, 0.003),   # very low gate error, moderate readout
    "Rigetti Ankaa-2 (sc)":      (0.010, 0.005),   # higher gate error, low readout
    "OQC Lucy (sc)":             (0.020, 0.008),   # higher gate error
    "Noisy reference":           (0.050, 0.020),
}

print("GHZ Fidelity by Device Profile:")
for name, (gate_err, readout_err) in noise_profiles.items():
    fidelity = simulate_with_noise(gate_err, readout_err)
    print(f"  {name:<35}: {fidelity:.4f}")

Pricing and Latency Comparison

Pricing on Braket follows a task-plus-shot model:

DevicePer-task feePer-shot feeTypical queue
IonQ Aria$0.30$0.015 to 60 min
Rigetti Ankaa-2$0.35$0.00095 to 30 min
OQC Lucy$0.30$0.0003510 to 60 min
QuEra Aquila$0.01$0.015 to 30 min

Key cost observations:

  • Rigetti and OQC are cheapest per shot, making them cost-effective for large shot counts.
  • IonQ has the highest per-shot cost but often requires fewer shots due to lower readout error.
  • QuEra Aquila has a different pricing model because it runs analog Hamiltonian evolution, not gate circuits.

Choosing the Right Hardware

WorkloadBest ChoiceReason
High-fidelity small circuitsIonQ AriaAll-to-all connectivity, low gate error
Large qubit count, superconductingRigetti Ankaa-284 qubits, fast repetition rate
Combinatorial optimization (QAOA)Rigetti Ankaa-2Low per-shot cost, many shots affordable
Neutral atom arrays, analogQuEra AquilaProgrammable Rydberg Hamiltonian, 256 sites
Rapid prototypingOQC LucyLow per-shot cost, 8-qubit ring

Key Takeaways

  • Braket’s unified API lets you benchmark the same circuit across all hardware with minimal code changes.
  • Gate sets and connectivity differ fundamentally between trapped ion (all-to-all, MS gates) and superconducting (fixed topology, CZ or ECR gates) devices.
  • GHZ fidelity is a simple, interpretable benchmark that scales to larger qubit counts.
  • Pricing is task-based plus per-shot; for shot-intensive workloads, Rigetti and OQC offer the best economics.
  • QuEra Aquila’s analog model is suited for combinatorial problems posed as Rydberg Hamiltonians rather than gate sequences.

Was this tutorial helpful?