Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ['3.11']
python-version: ['3.12']
action: ['lint', 'type', 'format']
steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
NEXUS_QA_USER_PASSWORD: ${{ secrets.NEXUS_QA_USER_PASSWORD }}
NEXUS_DOMAIN: "qa.myqos.com"
NEXUS_STORE_TOKENS: "false"
NEXUS_QA_QSYS_DEVICE: ${{ secrets.NEXUS_QA_QSYS_DEVICE }}

steps:
- name: Comment action run link on PR
Expand Down
5 changes: 3 additions & 2 deletions integration/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# qnexus integration test suite

These tests are intended to be runnable against any Nexus environment by any user (with quotas enabled for basic Nexus usage).
The tests will login the user and create all projects/resources required, deleting them once the test suite has run (TODO).
The tests will login the user and create all projects/resources required, deleting them once the test suite has run.

In general we target the Nexus staging environment at https://staging.myqos.com using the
In general we target the Nexus QA environment at https://qa.myqos.com using the
QA test user with email: 'qa_nexus_staging_pytket-nexus@mailsac.com'.

Requires the following environment variables to be set:

- NEXUS_USER_EMAIL
- NEXUS_USER_PASSWORD
- NEXUS_HOST (to avoid targetting prod by default)
- NEXUS_QA_QSYS_DEVICE (for executing HUGR programs on a next-gen qsys device)
23 changes: 20 additions & 3 deletions integration/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pytket import Circuit
from pytket.backends.backendinfo import BackendInfo
from pytket.backends.backendresult import BackendResult
from quantinuum_schemas.models.backend_config import BaseBackendConfig
from quantinuum_schemas.models.hypertket_config import HyperTketConfig

import qnexus as qnx
Expand All @@ -33,7 +34,10 @@ def test_job_get_all(
assert isinstance(my_job_db_matches.count(), int)
assert isinstance(my_job_db_matches.summarize(), pd.DataFrame)

assert isinstance(next(my_job_db_matches), JobRef)
for job_ref in my_job_db_matches.list():
assert isinstance(job_ref, JobRef)
assert job_ref.backend_config is not None
assert isinstance(job_ref.backend_config, BaseBackendConfig)


def test_job_get_by_id(
Expand All @@ -45,12 +49,14 @@ def test_job_get_by_id(

my_compile_job = qnx.jobs.get(name_like=qa_compile_job_name)
assert isinstance(my_compile_job, CompileJobRef)
assert isinstance(my_compile_job.backend_config, BaseBackendConfig)

my_compile_job_2 = qnx.jobs.get(id=my_compile_job.id)
assert my_compile_job == my_compile_job_2

my_execute_job = qnx.jobs.get(name_like=qa_execute_job_name)
assert isinstance(my_execute_job, ExecuteJobRef)
assert isinstance(my_execute_job.backend_config, BaseBackendConfig)

my_execute_job_2 = qnx.jobs.get(id=my_execute_job.id)
assert my_execute_job == my_execute_job_2
Expand Down Expand Up @@ -99,11 +105,13 @@ def test_submit_compile(

my_proj = qnx.projects.get(name_like=qa_project_name)

config = qnx.AerConfig()

compile_job_ref = qnx.start_compile_job(
circuits=[_authenticated_nexus_circuit_ref],
name=f"qnexus_integration_test_compile_job_{datetime.now()}",
project=my_proj,
backend_config=qnx.AerConfig(),
backend_config=config,
)

assert isinstance(compile_job_ref, CompileJobRef)
Expand All @@ -125,6 +133,9 @@ def test_submit_compile(
assert isinstance(first_pass_data.get_output(), CircuitRef)
assert isinstance(first_pass_data.pass_name, str)

cj_ref = qnx.jobs.get(id=compile_job_ref.id)
assert cj_ref.backend_config == config


def test_compile(
_authenticated_nexus_circuit_ref: CircuitRef,
Expand Down Expand Up @@ -194,14 +205,16 @@ def test_submit_execute(
"""Test that we can run an execute job in Nexus, wait for the job to complete and
obtain the results from the execution."""

config = qnx.AerConfig()

my_proj = qnx.projects.get(name_like=qa_project_name)
my_circ = qnx.circuits.get(name_like=qa_circuit_name, project=my_proj)

execute_job_ref = qnx.start_execute_job(
circuits=[my_circ],
name=f"qnexus_integration_test_execute_job_{datetime.now()}",
project=my_proj,
backend_config=qnx.AerConfig(),
backend_config=config,
n_shots=[10],
)

Expand All @@ -219,6 +232,9 @@ def test_submit_execute(

assert isinstance(execute_results[0].download_backend_info(), BackendInfo)

pj_ref = qnx.jobs.get(id=execute_job_ref.id)
assert pj_ref.backend_config == config


def test_execute(
_authenticated_nexus: None,
Expand All @@ -239,6 +255,7 @@ def test_execute(
)

assert len(backend_results) == 1
assert isinstance(backend_results[0], BackendResult)
assert isinstance(backend_results[0].get_counts(), Counter)


Expand Down
92 changes: 92 additions & 0 deletions integration/test_qsys_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""Tests related to running jobs against QSys devices."""
import os
from datetime import datetime
from typing import cast

from guppylang import guppy
from guppylang.std.builtins import result
from guppylang.std.quantum import cx, h, measure, qubit, x, z
from pytket.backends.backendinfo import BackendInfo
from quantinuum_schemas.models.result import QSysResult

import qnexus as qnx

QSYS_QA_DEVICE_NAME = os.environ["NEXUS_QA_QSYS_DEVICE"]
N_SHOTS = 10


def prepare_teleportation():
"""Prepares the teleportation circuit."""

@guppy
def bell() -> tuple[qubit, qubit]:
# pylint: disable=too-many-function-args
"""Constructs a bell state."""
q1, q2 = qubit(), qubit()
h(q1)
cx(q1, q2)
return q1, q2

@guppy
def main() -> None:
# pylint: disable=too-many-function-args
src = qubit()
x(src)
alice, bob = bell()

cx(src, alice)
h(src)
if measure(alice):
x(bob)
if measure(src):
z(bob)

result("teleported", measure(bob)) # type: ignore

return main.compile()


def test_guppy_execution(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the comments (and the reality) about the QSYS QA device not always being online, should we consider marking this test with @Skip for now?

Also - if the env var "NEXUS_QA_QSYS_DEVICE" is not present, this python file will raise an exception on import. Maybe we could skip_if the env var is absent. Then normal runs of the tests will continue to work as before. Just a suggestion though

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could certainly, wasn't sure what the best approach was here but wanted to make sure there was at least some sort of test. Think its ok to @Skip for now and then look towards a proper test device in the future (e.g. selene)

_authenticated_nexus: None,
qa_project_name: str,
) -> None:
"""Test the execution of a guppy program
on a next-generation QSys device."""

# Compile the guppy program
compiled_module = prepare_teleportation()
hugr_package = compiled_module.to_executable_package().package

project_ref = qnx.projects.get_or_create(name=qa_project_name)

hugr_ref = qnx.hugr.upload(
hugr_package=hugr_package,
name="QA testing teleportation program",
project=project_ref,
)

job_ref = qnx.start_execute_job(
circuits=[hugr_ref],
n_shots=[N_SHOTS],
backend_config=qnx.QuantinuumConfig(device_name=QSYS_QA_DEVICE_NAME),
project=project_ref,
name=f"QA Test QSys job from {datetime.now()}",
)

# QSYS QA device might not always be online, so we might expect failures for now
qnx.jobs.wait_for(job_ref)

results = qnx.jobs.results(job_ref)

assert len(results) == 1
result_ref = results[0]

assert isinstance(result_ref.download_backend_info(), BackendInfo)
assert isinstance(result_ref.get_input(), hugr_package)

assert result_ref.get_input().id == hugr_ref.id

qsys_result = cast(QSysResult, result_ref.download_result())
assert len(qsys_result) == N_SHOTS
assert qsys_result[0][0][0] == "teleported"
assert qsys_result[0][0][1] == 0
Loading