Sampling on Quantinuum devices

Here we introduce QuantinuumSamplingBackend, which submits sampling jobs to Quantinuum systems (H-Series), their emulators and Nexus-hosted simulators through Quantinuum Nexus.

Prerequisite

This section requires topics described in previous sections (Samplers, Sampling estimation and Sampling Backends), so you need to read them before this section.

QURI Parts modules used in this tutorial: quri-parts-circuit, quri-parts-core, quri-parts-tket and quri-parts-quantinuum. You can install them as follows:

!pip install "quri-parts[quantinuum]"

You also need a Quantinuum Nexus account. Access to Quantinuum hardware and emulators requires a plan with HQC (Hardware Quantum Credits); syntax checkers (device names ending with SC) and Nexus-hosted simulators such as Aer only need Nexus quota.

Logging in to Nexus

Authentication is handled by the qnexus package. Logging in once stores tokens under ~/.qnx/auth, so you do not need to log in again until the tokens expire.

import qnexus as qnx

# Opens a browser. On a headless machine (e.g. an HPC login node) use
# qnx.auth.login_no_interaction(email, password) instead.
qnx.login()

Which devices can I use?

The device names an account can use depend on its plan. qnexus lists them. sample() checks the device name against this list before submitting, so a wrong name or a device outside your plan fails immediately with the list in the error message.

from qnexus.models import IssuerEnum

qnx.devices.get_all(issuers=[IssuerEnum.QUANTINUUM]).df()

The QuantinuumSamplingBackend

A QuantinuumSamplingBackend is created from a device name. Device names ending with E are emulators and names ending with SC are syntax checkers, e.g. H2-1, H2-1E, H2-1SC. Quantinuum Helios is not supported yet (its Nexus endpoints accept only QIR or HUGR programs), so Helios names are rejected with a ValueError.

from quri_parts.quantinuum.backend import QuantinuumSamplingBackend

# Emulator of the H2-1 device
backend = QuantinuumSamplingBackend("H2-1E")

Any qnexus backend config can be passed instead of a device name. For example, a Nexus-hosted Qiskit Aer simulator does not consume HQC and is convenient for testing:

backend = QuantinuumSamplingBackend(backend_config=qnx.AerConfig())

With the sampling backend we just created, we can run the exact codes as in the Sampling Backend and Sampler section of the Sampling backend tutorial.

from quri_parts.circuit import QuantumCircuit
from quri_parts.core.sampling import create_sampler_from_sampling_backend

circuit = QuantumCircuit(2)
circuit.add_H_gate(0)
circuit.add_CNOT_gate(0, 1)

sampler = create_sampler_from_sampling_backend(backend)
print(sampler(circuit, 1000))

How a job is processed

backend.sample() does the following on Nexus:

  1. Converts the circuit to a pytket circuit, measures all qubits and uploads it to a Nexus project (project="quri-parts" by default; the project is created if it does not exist).

  2. Compiles the circuit for the target backend with Quantinuum’s TKET compiler (compile_on_nexus=True, optimisation_level=2 by default). This step waits for the compile job to finish.

  3. Submits an execute job and returns a QuantinuumSamplingJob immediately.

job.result() waits for the execute job and returns the counts. Uploaded circuits and jobs can be inspected on the Nexus portal.

job = backend.sample(circuit, 1000)
print(job.id, job.status())

result = job.result()
print(result.counts)

Retrieving a job later

The Nexus job id can be stored and used to retrieve the result in another process, for example after an HPC batch job ends:

from quri_parts.quantinuum.backend import QuantinuumSamplingJob

job = QuantinuumSamplingJob.from_id(job.id)
print(job.result().counts)

Options

  • project: Nexus project name (or ProjectRef) that holds the circuits and jobs.

  • circuit_transpiler: a QURI Parts transpiler applied before conversion.

  • compile_on_nexus / optimisation_level: control the Nexus compile step. With compile_on_nexus=False the circuit is executed as uploaded and must already use the backend’s gate set.

  • max_shots: split a request into several job items of at most max_shots shots inside one Nexus job. Defaults to 10,000 (the per-item limit of Quantinuum-hosted devices) for QuantinuumConfig, and to no split for other configs.

  • compile_timeout / result_timeout: time limits in seconds.

  • execute_kwargs: extra arguments of qnexus.start_execute_job, e.g. {"max_cost": 10.0} to cap the HQC cost. Note that qnexus applies max_cost to each job item, so a request split into n items by max_shots may cost up to n times that value.

backend = QuantinuumSamplingBackend(
    "H2-1",
    project="my-experiment",
    max_shots=5000,
    execute_kwargs={"max_cost": 10.0},
)