H-Series Workflow with Nexus

Quantinuum Nexus is a cloud-based platform that enables users to seamlessly run, review, and collaborate on quantum computing projects. The platform integrates support for various quantum targets using the TKET quantum programming tools to optimize circuit performance and translation between different targets. One such target is H-Series, powered by Honeywell. Each quantum target in nexus is called a BackendConfig and can be configured to access hardware, emulator or simulator. Quantinuum Nexus offers different types of jobs that represent a component of your workflow that is running on Nexus-hosted emulators or H-series.

  • CompileJobs represent the TKET compilation of circuits for a particular target device.

  • ExecuteJobs represent the execution of circuits on a quantum computer or simulator.

  • Nexus stores Job inputs and outputs such as BackendResult, BackendInfo and Circuit in addition to job metadata. These are searchable by a user friendly name. qnexus uses references to access these resources. For access across multiple python sessions, these references should be saved to and loaded from local disk. More information is available here.

To access Nexus from an external environment, a python client must be installed with pip install qnexus.

Authentication

External Environment

Quantinuum Nexus API usage requires you to have valid authentication tokens. You can obtain these by logging in with qnexus. Authentication is performed silently in the Nexus Jupyterhub environment. However, in an external environment, qnexus requires explicit authentication. There are two ways:

  1. Authentication via Web Browser

  2. Authentication via a Python prompt

The credentials one uses to authenticate access to qnexus are the same as the credentials to login to the Quantinuum Nexus UMUI.

Authentication via a Web Browser

The code snippet below opens a windows in a web browser and requires the user to login to the nexus portal. A code is also displayed in both the python session and the web browser. The user is asked to verify the code is the same.

import qnexus
qnexus.login()

Calling qnexus.login will first lead to a prompt being displayed as output. The prompt will contain an authentication code alongside instructions.

RZZ gate

In the web browser, the following page opens displaying:

  1. An authentication code. This needs to be compared with the authentication code displayed.

  2. The option to link a new device to nexus.

RZZ gate

Subsequently, the end-user must login to the nexus portal to grant the external environment access to nexus.

RZZ gate

Once the user login is complete, a message will be displayed confirming successful authentication.

RZZ gate

Authentication via a Python prompt

import qnexus
qnexus.login_with_credentials()

Within Nexus Jupyterhub

Within the JupyterHub environment within Nexus, user authentication is not required for qnexus. The workflow defined for user using qnexus in external environments does not need to be followed.

Project Setup

Within Quantinuum Nexus, all jobs are contained within projects. At the start of each Python session, a qnexus user must authenticate using the workflow above and define an active project. The method qnexus.projects.get_or_create can be used to retrieve an existing project from the database, called Nexus-Workflow-Demonstration. The method qnexus.context.set_active_project ensures this project is used within the context of this session by default.

import qnexus

project = qnexus.projects.get_or_create(name="Nexus-Workflow-Demonstration")
qnexus.context.set_active_project(project)

Within the scope of a nexus project, the job name is unique. Trying to create a new job using an existing job name from the database will lead to an error. A suffix is defined with the python datetime library.

import datetime

jobname_suffix = datetime.datetime.now().strftime("%Y_%m_%d-%H-%M-%S")

Backend Configuration

A BackendConfig must also be specified to start compilation jobs and execution jobs. A project can contain jobs using multiple BackendConfig specifications. To access H-Series devices and emulators, qnexus.QuantinuumConfig, needs to be instantiated. The code-cell defines QuantinuumConfig to target a nexus-hosted emulator, H1-Emulator.

config = qnexus.QuantinuumConfig(device_name="H1-Emulator")

Uploading and Downloading Circuits

For the demonstration of the qnexus workflow, an existing circuit on the nexus database will be used. The method qnexus.circuits.get retrieves the reference of the circuit called GHZ-Circuit. This is visualized using TKET. It is a requirement for a circuit to be uploaded into the nexus database before compilation and execution jobs can be used. This can be achieved with the method qnexus.circuits.upload.

from pytket.circuit import Circuit

circuit = Circuit(10)
circuit.H(0)
for i, j in zip(circuit.qubits[:-1], circuit.qubits[1:]):
    circuit.CX(i, j)
circuit.measure_all();
from pytket.circuit.display import render_circuit_jupyter

render_circuit_jupyter(circuit)
ref = qnexus.circuits.upload(circuit=circuit, name=f"GHZ-Circuit-{jobname_suffix}")
from pytket.circuit.display import render_circuit_jupyter

render_circuit_jupyter(ref.download_circuit())

Compilation Jobs

Quantinuum Nexus enables remote compilation of jobs for different backend configurations. A compilation job requires:

  • a database reference to the uncompiled Circuit,

  • the specified BackendConfig,

  • optimisation_level

  • a job name.

The output would be a reference to the compilation job. This reference can be used to query job status and to retrieve the job result when its ready. For compilation jobs, the job result would be a reference to the compiled circuit. The output reference can be used to download the compiled circuit into the local python session as a Circuit instance.

ref_compile_job = qnexus.start_compile_job(
    circuits=[ref],
    backend_config=config,
    optimisation_level=2,
    name=f"compilation-job-{jobname_suffix}"
)

The method qnexus.jobs.wait_for can be used to block any further operations whilst the job is running. The method also has a timeout. Upon timeout, an exception is raised. The subsequent method, qnexus.jobs.results won’t be called until the compilation job completes.

qnexus.jobs.results requires the compilation job reference as an input and outputs the reference for the compiled job result. The get_output method returns the reference for the compiled circuit and download_circuit returns an instance of the compiled circuit.

qnexus.jobs.wait_for(ref_compile_job)
ref_compiled_circuit = qnexus.jobs.results(ref_compile_job)[0].get_output()
compiled_circuit = ref_compiled_circuit.download_circuit()
from pytket.circuit.display import render_circuit_jupyter

render_circuit_jupyter(compiled_circuit)

Execution Jobs

Quantinuum nexus enables execution of quantum circuits across different quantum targets using the same interface. A prequisite step before an execution job can be started is a compilation job to ensure the circuit to be executed satisfies the gateset predicate. An execution job requires:

  • A database reference to the compiled circuit. The references must be passed as a list.

  • A list of integers for each circuit to be submitted for execution.

  • A BackendConfig to specify which quantum target to use for execution

  • A name to assign on the execution job for job management purposes. This name is unique within the scope of the active project.

ref_execute_job = qnexus.start_execute_job(
    circuits=[ref_compiled_circuit],
    n_shots=[100],
    backend_config=config,
    name=f"execution-job-{jobname_suffix}"
)

Similar to the compile job workflow, the methods qnexus.jobs.wait_for and qnexus.jobs.results are used together to retrieve a reference to the job result. As an alternative, qnexus.jobs.status is used to query the status of the execution job.

qnexus.jobs.status(ref_execute_job)
JobStatus(status=<StatusEnum.SUBMITTED: 'Circuit has been submitted.'>, message='Job has been submitted to Nexus.', error_detail=None, completed_time=None, queued_time=None, submitted_time=datetime.datetime(2024, 8, 9, 12, 43, 14, 267558, tzinfo=datetime.timezone.utc), running_time=None, cancelled_time=None, error_time=None, queue_position=None)

The user can call download_result directly on the result reference. For execution jobs, get_output does not need to be called. The method, download_result, will download the result data into a local instance of BackendResult.

qnexus.jobs.wait_for(ref_execute_job)
ref_result = qnexus.jobs.results(ref_execute_job)[0]
backend_result = ref_result.download_result()
backend_result.get_distribution()
{(0, 0, 0, 0, 0, 0, 0, 0, 0, 0): 0.47,
 (0, 0, 0, 0, 0, 0, 0, 0, 1, 0): 0.01,
 (0, 0, 0, 0, 0, 0, 0, 1, 1, 1): 0.01,
 (0, 0, 0, 0, 0, 1, 0, 0, 0, 0): 0.01,
 (1, 1, 1, 1, 1, 0, 1, 1, 1, 1): 0.01,
 (1, 1, 1, 1, 1, 1, 1, 1, 1, 0): 0.01,
 (1, 1, 1, 1, 1, 1, 1, 1, 1, 1): 0.48}

Cancel job

ref_execute_job1 = qnexus.start_execute_job(
    circuits=[ref_compiled_circuit],
    n_shots=[100],
    backend_config=qnexus.QuantinuumConfig(device_name="H1-1E"),
    name=f"execution-job-to-cancel-{jobname_suffix}"
)
qnexus.jobs.status(ref_execute_job1)
JobStatus(status=<StatusEnum.SUBMITTED: 'Circuit has been submitted.'>, message='Job has been submitted to Nexus.', error_detail=None, completed_time=None, queued_time=None, submitted_time=datetime.datetime(2024, 8, 9, 12, 43, 58, 943523, tzinfo=datetime.timezone.utc), running_time=None, cancelled_time=None, error_time=None, queue_position=None)
config = qnexus.QuantinuumConfig(device_name="H1-Emulator", attempt_batching=True)

Local Reference Storage

Nexus stores all circuits, results and jobs on the nexus database. Each type of resource is searchable by a user-friendly name. With the qnexus client, an end-user can request these resources across different sessions with references. To this end, these references must be saved and loaded from local disk using the qnexus.filesystem module.

from pathlib import Path

qnexus.filesystem.save(
    path=Path.cwd() / "my_job_folder" / ref_execute_job.annotations.name,
    ref=ref_execute_job,
    mkdir=True
)
ref_execute_job_2 = qnexus.filesystem.load(path=Path.cwd() / "my_job_folder" / ref_execute_job.annotations.name)