Batch Jobs

Note

Batching is only supported on the Helios-1. For batching on System Model H2, see here.

The batch feature enables users to benefit from “ad-hoc” reservations. Circuits submitted together in a batch will run at one time. Once a batch hits the front of the queue, jobs in a batch will run uninterrupted until they are completed. Hence, chunks from other organizations’ jobs will not contaminate chunks in a batch.

Batching Requirements:

  • Only supported by Nexus Execute Jobs (nexus:qnexus.jobs.ExecuteJob()).

  • Only compatible with Helios-1. Batching is not supported on syntax checkers or Helios emulators.

  • Batching is supported across multiple Nexus execute jobs. Each Nexus execute job has a 300 circuit hard limit.

Batch termination conditions:

  • A user specifies the current job submission is the last job in the batch

  • 1 minute of inactivity

  • The total job cost in the batch exceeds 2,000 HQCs.

    • If the total HQCs for jobs in a batch hit this limit, those jobs will run as regular jobs in the queue instead of as a batch.

The main method synthesizes an 8-qubit log-depth GHZ circuit, and compiles the guppy source into a Heirarchical Unified Graph Representation (HUGR) binary. HUGR is a compact intermediate representation for Guppy programs.

from guppylang import guppy
from guppylang.std.quantum import cx, h, measure_array, qubit
from guppylang.std.builtins import array, result, comptime, barrier

n_qubits = 8

@guppy
def main() -> None:
    """
    Create a GHZ circuit with log depth.
    """

    qubit_array: array[qubit, comptime(n_qubits)] = array(qubit() for _ in range(comptime(n_qubits)))
    h(qubit_array[0])
    barrier(qubit_array)
    qubits_used: int = 1
    i: int = 0
    while qubits_used < comptime(n_qubits):
        i += 1
        for k in range(qubits_used):
            cx(qubit_array[k], qubit_array[qubits_used])
            qubits_used += 1
        barrier(qubit_array)
    mresults = measure_array(qubit_array)
    result("measurementResults", mresults[0])

hugr = main.compile()

The max_cost keyword argument in the QuantinuumConfig constructor is used to stop HQC consumption if a threshold is exceeded during batch execution. The device_name corresponds to the emulation target Various Helios backends can be specified via device_name. The keyword argument attempt_batching is used to enable batching for all nexus:qnexus.jobs.ExecuteJob() submitted to the emulator.

import qnexus as qnx

config = qnx.models.QuantinuumConfig(device_name="Helios-1", max_cost=500, simulator="stabilizer")

The Nexus project is initialized and a name suffix is defined.

import qnexus as qnx
import datetime

project = qnx.projects.get_or_create("release/helios", f"HUGR to support Quantinuum Helios Release.")
qnx.context.set_active_project(project)

name_suffix = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M_%S")

The HUGR binary is uploaded to Nexus with a unique name (datetime) using the qnx.hugr module. The HUGR upload requires a user friendly name, which does not need to be unique across the Nexus project. A HUGR reference is returned after uploading to the Nexus database. This reference is required for execute jobs.

from importlib.metadata import version

pkg_version = version('hugr')  # replace 'numpy' with your package name

hugr_ref = qnx.hugr.upload(
    hugr_package=hugr,
    name=f"ghz-{datetime.strftime('%Y-%m-%d_%H-%M-%S')}",
    description=f"Log-depth GHZ circuit using {n_qubits} qubits. HUGR version {pkg_version}.",
)

The compiled circuit reference is used to submit an execute job with four instances of the Bell State, each circuit with a different number of shots. The execute job uses batching since it is specified QuantinuumConfig instance passed to the function.

n_shots = 100

ref_execute_job = qnx.start_execute_job(
    circuits=[hugr_ref for _ in range(4)],
    n_shots=[i*n_shots for i in range(1,5)],
    backend_config=config,
    name=f"execute-job-{name_suffix}"
)
qnx.jobs.wait_for(ref_execute_job)
results = [
    ref_result.download_result() 
    for ref_result in qnx.jobs.results(ref_execute_job)
]

The probability distribution of measurements is printed below.

for i, r in enumerate(results):
    print(f"result {i} with {n_shots * (i+1)} shots\n{r.get_distribution()}\n")