tket Intermediate Free 1/8 in series 15 min read

Connecting tket to Real Quantum Hardware

How to connect tket circuits to IBM Quantum, Quantinuum H-Series, and AWS Braket backends using pytket extension packages.

What you'll learn

  • tket
  • pytket
  • IBM Quantum
  • Quantinuum
  • AWS Braket
  • backends
  • hardware

Prerequisites

  • Basic tket circuit construction
  • Python 3.10+

Why Use tket for Backend Integration

tket is a quantum compiler and circuit runtime built by Quantinuum (formerly Cambridge Quantum Computing). Its central value proposition is portability: you write a circuit once using abstract gates like H, CNOT, and T, and tket handles the backend-specific compilation for each hardware target you want to run on. That compilation includes gate rebasing (converting your abstract gates into the hardware’s native gate set), qubit routing (inserting SWAP gates so two-qubit operations respect the device’s connectivity), and optimization (merging redundant gates and reducing circuit depth).

This is fundamentally different from using a vendor-locked framework. Qiskit is primarily designed for IBM hardware. Cirq is primarily designed for Google hardware. Both can target other providers through plugins, but the compilation pipelines and abstractions are built around their respective ecosystems. tket takes a different approach: it treats every backend as a compilation target, with first-class support for IBM, Quantinuum, IonQ, Rigetti, and others through dedicated extension packages.

If you want to compare results across multiple hardware providers, benchmark the same algorithm on superconducting and trapped-ion systems, or choose the best available device for your circuit at runtime, tket is the pragmatic choice. You keep a single circuit definition and swap backends with a one-line change.

Installing Extension Packages

Each hardware provider has a corresponding pytket extension. Install only the ones you need:

# IBM Quantum (via Qiskit Runtime)
pip install pytket-qiskit

# Quantinuum H-Series (via Quantinuum API)
pip install pytket-quantinuum

# AWS Braket (IonQ, Rigetti, OQC devices)
pip install pytket-braket

The core pytket package is a dependency of all extensions and will be installed automatically.

Building a Test Circuit

We will use a simple 3-qubit GHZ state preparation as the circuit to submit across backends:

from pytket import Circuit

# Prepare |GHZ> = (|000> + |111>) / sqrt(2)
ghz = Circuit(3)
ghz.H(0)          # Hadamard on qubit 0
ghz.CX(0, 1)      # CNOT: entangle qubit 0 and 1
ghz.CX(1, 2)      # CNOT: entangle qubit 1 and 2
ghz.measure_all()  # Measure all qubits into classical bits

The GHZ circuit is a good test case because it uses both single-qubit and two-qubit gates, requires entanglement across multiple qubits, and produces a result distribution (ideally 50% 000 and 50% 111) that makes hardware noise easy to spot.

tket’s Compilation Model

When you call backend.get_compiled_circuit(), tket applies a sequence of compiler passes tailored to the target backend. Understanding these passes helps you interpret what happens to your circuit and gives you the tools to intervene when needed.

Rebasing

Your abstract circuit uses gates like H, CNOT, and T. Real hardware does not implement these gates directly. Each device has a native gate set, a small collection of physical operations it can execute. Rebasing converts every gate in your circuit into an equivalent sequence drawn from the target’s native set.

For example, a Hadamard gate targeting IBM Eagle hardware gets decomposed into a sequence of RZ and SX rotations. A CNOT becomes an ECR gate with surrounding single-qubit corrections. The circuit’s logical behavior is preserved exactly; only the gate-level representation changes.

Routing

Most superconducting devices have limited qubit connectivity: a physical qubit can interact directly only with its nearest neighbors on the chip. If your circuit applies a CNOT between qubit 0 and qubit 5, but those qubits are not adjacent on the hardware, the compiler must insert SWAP gates to move the quantum state into position. This adds depth and introduces additional noise.

Routing is one of the most impactful compilation steps. A good router minimizes the number of SWAPs inserted. tket uses heuristic algorithms to find efficient routings, but the overhead depends heavily on both the circuit structure and the device topology.

Optimization

After rebasing and routing, the circuit often contains redundant operations: pairs of gates that cancel each other, single-qubit rotations that can be merged, or sequences that simplify to shorter equivalents. tket’s optimization passes clean up this overhead.

You can inspect the effect of compilation by comparing gate counts before and after:

from pytket import Circuit, OpType

ghz = Circuit(3)
ghz.H(0)
ghz.CX(0, 1)
ghz.CX(1, 2)
ghz.measure_all()

print("Before compilation:")
print(f"  Total gates: {ghz.n_gates}")
print(f"  CX count: {ghz.n_gates_of_type(OpType.CX)}")

# Compile for a specific backend
compiled = backend.get_compiled_circuit(ghz)

print("After compilation:")
print(f"  Total gates: {compiled.n_gates}")
print(f"  Two-qubit gates: {compiled.n_gates_of_type(OpType.ECR)}")

The two-qubit gate count is the most important metric for near-term hardware, since two-qubit gates have error rates roughly 10x higher than single-qubit gates.

Targeting IBM Quantum

The pytket-qiskit extension provides IBMQBackend for accessing IBM Quantum systems through Qiskit Runtime.

Listing Available Backends

Before choosing a device, you can query the available IBM backends and inspect their properties:

from pytket.extensions.qiskit import IBMQBackend

# List all available backends for your account
available = IBMQBackend.available_devices()
for dev in available:
    print(f"{dev.device_name}: {dev.n_nodes} qubits")

Choose a backend based on qubit count, queue depth, and calibration data. Devices with shorter queues return results faster, while devices with better calibration data produce cleaner results.

Native Gate Set

IBM Eagle and Heron processors use the native gate set ECR, RZ, SX, and X. The ECR (echoed cross-resonance) gate serves as the two-qubit entangling operation. IBM chose ECR over CNOT because it maps more directly to the physical cross-resonance interaction between superconducting transmon qubits. CNOT is implemented as an ECR gate with single-qubit pre- and post-rotations, so using ECR natively avoids that overhead.

Compiling and Submitting

from pytket.extensions.qiskit import IBMQBackend

# Connect to a specific IBM backend
backend = IBMQBackend("ibm_sherbrooke")

# Compile the circuit for this backend's native gate set and topology
compiled = backend.get_compiled_circuit(ghz)

# Verify the circuit is valid before submitting
assert backend.valid_circuit(compiled), "Circuit failed backend validation"

# Submit and retrieve results
handle = backend.process_circuit(compiled, n_shots=1024)
result = backend.get_result(handle)
print(result.get_counts())

The get_compiled_circuit method applies a sequence of passes internally: it rebases gates to the backend’s native set (ECR, RZ, SX, X for Eagle/Heron processors), routes qubits to match the device coupling map, and runs optimization passes to reduce overhead from inserted SWAP gates.

Retrieving Results

When you call backend.process_circuit(), the job is submitted to IBM’s queue and you receive a handle. Calling backend.get_result(handle) blocks until the job completes and then returns the result. For long-running jobs, you can save the handle and retrieve results in a separate session:

import json

# Save the handle for later retrieval
handle_json = backend.circuit_status(handle)
print(f"Job status: {handle_json}")

# In a later session, retrieve with the same handle
result = backend.get_result(handle)
counts = result.get_counts()

You can inspect what the backend expects before compiling:

# View the backend's native gate set and coupling constraints
print(backend.backend_info.gate_set)
print(backend.backend_info.architecture.coupling)

Targeting Quantinuum H-Series

Quantinuum’s trapped-ion systems use a different native gate set (ZZPhase, Rz, PhasedX) and have all-to-all connectivity, meaning no SWAP insertion is needed.

Hardware vs. Emulator

Quantinuum provides two device targets for each system:

  • H1-1: the real trapped-ion hardware, which consumes H-Quantum Credits (HQC) for every job
  • H1-1E: a high-fidelity emulator that models the noise characteristics of H1-1 without consuming hardware credits

Always test on the emulator first. The emulator reproduces the device’s noise model faithfully enough to catch most circuit-level issues before you spend credits on the real machine.

Native Gates and Connectivity

Quantinuum’s native gate set consists of ZZPhase (a parameterized two-qubit entangling gate), Rz (Z-axis rotation), and PhasedX (a generalized single-qubit rotation combining X and Z phases). This gate set is different from IBM’s, reflecting the different physics of trapped-ion systems.

The all-to-all connectivity is a major practical advantage. On a superconducting chip, a CNOT between distant qubits requires multiple SWAP insertions, adding depth and noise. On Quantinuum hardware, any qubit can interact directly with any other qubit. The compiled circuit contains no routing overhead, which means the circuit that runs on hardware is shallower and accumulates less noise. This is particularly beneficial for algorithms with many long-range two-qubit gates.

Estimating Cost

Quantinuum charges in H-Quantum Credits (HQC). The cost of a job depends on the number of two-qubit gates in the compiled circuit and the number of shots. Always check the cost estimate before submitting to real hardware:

from pytket.extensions.quantinuum import QuantinuumBackend

# Connect to the H1-1 emulator for testing
backend = QuantinuumBackend(device_name="H1-1E")
backend.login()  # Authenticate via Quantinuum portal

compiled = backend.get_compiled_circuit(ghz)

# Check the cost estimate before committing
cost = backend.cost(compiled, n_shots=100)
print(f"Estimated HQC cost: {cost}")

handle = backend.process_circuit(compiled, n_shots=100)
result = backend.get_result(handle)
print(result.get_counts())

Because trapped-ion devices have all-to-all connectivity, the compiled circuit will not contain any routing overhead. The compilation primarily handles gate rebasing.

Targeting AWS Braket

The pytket-braket extension lets you reach multiple hardware providers through a single cloud platform.

Available Hardware

Amazon Braket provides access to several quantum hardware providers:

  • IonQ Aria: trapped-ion system with all-to-all connectivity and native MS (Molmer-Sorensen) gates, available in the us-east-1 region
  • IQM Garnet: superconducting system with a square-lattice topology, available in the eu-north-1 region
  • Rigetti Ankaa: superconducting system with tunable couplers, available in the us-west-1 region

Each device is only available in a specific AWS region. You must configure your AWS session for the correct region before connecting.

AWS Credentials

Before using the Braket backend, configure your AWS credentials. The extension reads credentials from the standard AWS credential chain:

# Option 1: configure via the AWS CLI
aws configure

# Option 2: set environment variables
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="us-east-1"

Your IAM user or role needs the braket:* permissions, and you need an S3 bucket for Braket to store results.

Submitting Jobs

from pytket.extensions.braket import BraketBackend

# Target IonQ Aria via Braket
backend = BraketBackend(device="ionq", device_type="Aria 1")

compiled = backend.get_compiled_circuit(ghz)
handle = backend.process_circuit(compiled, n_shots=1024)
result = backend.get_result(handle)
print(result.get_counts())

Cost Considerations

AWS Braket uses a two-part billing model: a per-task fee (charged each time you submit a circuit) and a per-shot fee (charged for each measurement repetition). The per-task fee varies by device. IonQ charges a higher per-task fee but a lower per-shot fee; superconducting devices tend to have lower per-task fees but higher per-shot rates for large shot counts. Check the AWS Braket pricing page for current rates before running experiments.

Manual Compilation with Rebase Passes

If you want finer control over compilation, you can apply rebase passes explicitly instead of relying on get_compiled_circuit:

from pytket import OpType
from pytket.passes import (
    RebaseCustom,
    FullPeepholeOptimise,
    SequencePass,
)

# Define a custom native gate set (e.g., CZ + Rz + SX)
rebase = RebaseCustom(
    gateset={OpType.CZ, OpType.Rz, OpType.SX, OpType.Measure}
)

# Build a compilation pipeline
pipeline = SequencePass([
    FullPeepholeOptimise(),  # Optimize first
    rebase,                  # Then rebase to native gates
])

pipeline.apply(ghz)
print(ghz.get_commands())

This approach is useful when you need to insert custom optimization passes between routing and rebasing, or when targeting a backend that does not yet have a dedicated pytket extension.

Understanding FullPeepholeOptimise

FullPeepholeOptimise is tket’s most aggressive optimization pass. It works by examining local windows (peepholes) of 2-3 gates and replacing them with equivalent shorter sequences. The pass combines several techniques:

  • Phase gadget synthesis: identifies patterns of CNOT-Rz-CNOT that implement phase gadgets and resynthesizes them using fewer gates
  • Clifford simplification: detects subcircuits composed entirely of Clifford gates (H, S, CNOT) and replaces them with canonical forms
  • Two-qubit gate commutation: reorders commuting gates to create new cancellation opportunities

You can measure the impact of optimization by comparing gate counts:

from pytket import Circuit, OpType
from pytket.passes import FullPeepholeOptimise

# Build a circuit with some redundancy
circ = Circuit(3)
circ.H(0)
circ.CX(0, 1)
circ.CX(0, 1)  # This cancels with the previous CX
circ.H(0)
circ.Rz(0.5, 0)
circ.Rz(0.3, 0)  # These two Rz gates merge into Rz(0.8)
circ.CX(1, 2)
circ.measure_all()

print(f"Before optimization: {circ.n_gates} gates, "
      f"{circ.n_gates_of_type(OpType.CX)} CX gates")

FullPeepholeOptimise().apply(circ)

print(f"After optimization: {circ.n_gates} gates, "
      f"{circ.n_gates_of_type(OpType.CX)} CX gates")

For circuits with significant redundancy (common after routing or after combining subcircuits), FullPeepholeOptimise can reduce two-qubit gate counts by 20-40%.

Choosing Between Backends

Each hardware platform has distinct strengths. Here is a practical guide for choosing where to run your circuits.

IBM Quantum is the most accessible starting point. A free tier is available, the community is large, and extensive documentation and tutorials exist. IBM’s superconducting processors (Eagle, Heron) offer 100+ qubits, but limited connectivity means circuits with many long-range two-qubit gates accumulate significant SWAP overhead. Best for: learning, research prototyping, and algorithms that map well to nearest-neighbor connectivity.

Quantinuum H-Series offers the highest gate fidelities currently available and all-to-all qubit connectivity. The lack of routing overhead makes it ideal for algorithms with dense two-qubit gate patterns, such as variational algorithms, error correction experiments, and circuits with high entanglement depth. The tradeoff is cost: HQC pricing makes large-shot experiments expensive. Best for: high-fidelity results, algorithms needing many two-qubit gates, and error correction research.

IonQ (via AWS Braket) provides trapped-ion hardware with all-to-all connectivity and native Molmer-Sorensen gates. Fidelities are competitive with Quantinuum, and the Braket integration makes it straightforward to access alongside other providers. Best for: comparing trapped-ion results across vendors and leveraging native MS gate decompositions.

Rigetti (via AWS Braket) offers superconducting processors with tunable couplers and support for pulse-level control. If your research involves custom pulse sequences or you want to experiment with pulse-level optimization, Rigetti’s platform provides more low-level access than IBM. Best for: pulse-level experiments and superconducting hardware research.

When in doubt, start with a simulator or emulator, verify your circuit logic, then submit to the cheapest available hardware for a small number of shots to validate the pipeline before scaling up.

Common Mistakes

Submitting Uncompiled Circuits

Every backend requires circuits expressed in its native gate set. If you submit a circuit containing abstract gates (like H or CNOT) directly to hardware that expects ECR and RZ, the submission fails. Always call backend.get_compiled_circuit() or run an equivalent manual compilation pipeline before submitting.

Skipping Backend Validation

tket provides backend.valid_circuit(compiled) to verify that a compiled circuit satisfies all of a backend’s constraints (gate set, connectivity, qubit count, classical register requirements). Call this check before every submission:

compiled = backend.get_compiled_circuit(ghz)
if not backend.valid_circuit(compiled):
    raise RuntimeError("Circuit does not satisfy backend constraints")
handle = backend.process_circuit(compiled, n_shots=1024)

Skipping this check means you only discover problems after waiting in a queue, which wastes both time and potentially credits.

Forgetting That Handles Expire

Job handles returned by backend.process_circuit() have a limited lifetime on most platforms. If your Python session disconnects or your notebook kernel restarts, you lose the handle and cannot retrieve your results. For long-running jobs, serialize the handle immediately:

handle = backend.process_circuit(compiled, n_shots=1024)

# Save the handle to disk right away
with open("job_handle.json", "w") as f:
    f.write(str(handle))

This is especially important for Quantinuum jobs, which can queue for hours during periods of high demand.

Using the Wrong AWS Region

Each Braket hardware device is available only in a specific AWS region. IonQ Aria runs in us-east-1, Rigetti Ankaa runs in us-west-1, and IQM Garnet runs in eu-north-1. If your AWS session is configured for a different region, the device appears unavailable or the submission silently fails. Always verify your region matches the target device before submitting.

Practical Tips

  • Always use get_compiled_circuit as a starting point. It applies the correct sequence of passes for each backend.
  • Use emulators and simulators (like H1-1E or the AerBackend from pytket-qiskit) to test your pipeline before spending hardware credits.
  • Check backend.backend_info to understand the device’s qubit count, gate set, and connectivity before submitting jobs.
  • Job handles can be stored and used to retrieve results later if your script disconnects during execution.
  • Compare two-qubit gate counts across backends to understand the compilation overhead each platform introduces for your specific circuit.
  • When benchmarking across providers, use the same number of shots and the same circuit to ensure a fair comparison. Account for differences in reported qubit ordering conventions.

Was this tutorial helpful?