diff --git a/docs/changelog.rst b/docs/changelog.rst index d7221df1..1c5c3cd3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,12 +1,14 @@ Changelog ~~~~~~~~~ -0.16.0 (April 2023) -------------------- +0.16.0 (May 2023) +----------------- +* Updated pytket version requirement to 1.15. * cost function now takes the same kwargs as process_circuits * add check for the number of classical registers to the backend -* Updated pytket version requirement to 1.14. +* add ``get_partial_result`` method to ``QuantinuumBackend``. +* add ``Rxxyyzz`` gate support. 0.15.0 (April 2023) ------------------- diff --git a/docs/intro.txt b/docs/intro.txt index bface5de..2785d228 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -24,7 +24,6 @@ The pytket-quantinuum extension allows the user to access the following quantum * ``H1-1``, ``H1-2``: Quantum computers, submit specifically to ``H1-1`` or ``H1-2`` by using the device name * ``H1-1E``, ``H1-2E``: Device-specific emulators of ``H1-1`` and ``H1-2``. These emulators run remotely on servers and require credentials. * ``H1-1SC``, ``H1-2SC``: Device-specific syntax checkers. These check compilation of a quantum circuit against device-specific instructions, and return status "completed" if the syntax is correct (along with the H-Series Quantum Credits (HQCs)), or status "failed" if the syntax is incorrect (along with the error). -* ``H1``: Submit to Quantinuum ``H1`` machines: job is run on ``H1-1`` or ``H1-2`` based on the soonest available device. There are also optional initialisation parameters ``label`` (for naming circuits), ``group`` (identifier for a collection of jobs) and ``simulator`` (see below). diff --git a/pytket/extensions/quantinuum/backends/api_wrappers.py b/pytket/extensions/quantinuum/backends/api_wrappers.py index de0c1e93..ad6fef95 100644 --- a/pytket/extensions/quantinuum/backends/api_wrappers.py +++ b/pytket/extensions/quantinuum/backends/api_wrappers.py @@ -528,7 +528,6 @@ def __init__(self, machine_list: Optional[list] = None): "gateset": ["RZZ", "Riswap", "Rxxyyzz"], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "H1", "system_type": "hardware", "emulator": "H1-1E", "syntax_checker": "H1-1SC", @@ -541,7 +540,6 @@ def __init__(self, machine_list: Optional[list] = None): "gateset": ["RZZ", "Riswap", "Rxxyyzz"], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "H1", "system_type": "hardware", "emulator": "H1-2E", "syntax_checker": "H1-2SC", @@ -554,7 +552,6 @@ def __init__(self, machine_list: Optional[list] = None): "gateset": ["RZZ", "Riswap", "Rxxyyzz"], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "H2", "system_type": "hardware", "emulator": "H2-1E", "syntax_checker": "H2-1SC", diff --git a/pytket/extensions/quantinuum/backends/quantinuum.py b/pytket/extensions/quantinuum/backends/quantinuum.py index feed566b..7b0d3742 100644 --- a/pytket/extensions/quantinuum/backends/quantinuum.py +++ b/pytket/extensions/quantinuum/backends/quantinuum.py @@ -17,7 +17,7 @@ from dataclasses import dataclass import json from http import HTTPStatus -from typing import Dict, List, Set, Optional, Sequence, Union, Any, cast +from typing import Dict, List, Set, Optional, Sequence, Union, Any, cast, Tuple import warnings import numpy as np @@ -101,6 +101,8 @@ def _get_gateset(gates: List[str]) -> Set[OpType]: gs = _GATE_SET.copy() if "RZZ" in gates: gs.add(OpType.ZZPhase) + if "Rxxyyzz" in gates: + gs.add(OpType.TK2) return gs @@ -193,7 +195,7 @@ def __init__( ): """Construct a new Quantinuum backend. - :param device_name: Name of device, e.g. "H1" + :param device_name: Name of device, e.g. "H1-1" :type device_name: str :param label: Job labels used if Circuits have no name, defaults to "job" :type label: Optional[str], optional @@ -325,13 +327,7 @@ def device_state( headers={"Authorization": api_handler.login()}, ) api_handler._response_check(res, "get machine status") - jr = res.json() - try: - return str(jr["state"]) - except KeyError: - # for family backends the response dictionary is different - # {"": } - return str(jr) + return str(res.json()["state"]) @property def backend_info(self) -> Optional[BackendInfo]: @@ -779,6 +775,32 @@ def circuit_status( ) return circ_status + def get_partial_result( + self, handle: ResultHandle + ) -> Tuple[Optional[BackendResult], CircuitStatus]: + """ + Retrieve partial results for a given job, regardless of its current state. + + :param handle: handle to results + :type handle: ResultHandle + + :return: A tuple containing the results and circuit status. + If no results are available, the first element is None. + :rtype: Tuple[Optional[BackendResult], CircuitStatus] + """ + job_id = str(handle[0]) + jr = self.api_handler.retrieve_job_status(job_id) + if not jr: + raise QuantinuumAPIError(f"Unable to retrive job {job_id}") + res = jr.get("results") + circ_status = _parse_status(jr) + if res is None: + return None, circ_status + ppcirc_rep = json.loads(cast(str, handle[1])) + ppcirc = Circuit.from_dict(ppcirc_rep) if ppcirc_rep is not None else None + backres = _convert_result(res, ppcirc) + return backres, circ_status + def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResult: """ See :py:meth:`pytket.backends.Backend.get_result`. @@ -842,9 +864,6 @@ def cost( If the backend is not a syntax checker (backend name does not end with "SC"), it is automatically appended to check against the relevant syntax checker. - Sometimes it may not be possible to find the relevant syntax checker, - for example for device families. In which case you may need to set - the ``syntax_checker`` kwarg to the appropriate syntax checker name. See :py:meth:`QuantinuumBackend.process_circuits` for the supported kwargs. @@ -891,10 +910,7 @@ def cost( f"Cannot find syntax checker for device {self._device_name}. " "Try setting the `syntax_checker` key word argument" " to the appropriate syntax checker for" - " your device explicitly. " - "For device families, you may need to pick the" - " syntax checker for the specific device," - " e.g. 'H1-1SC' as opposed to 'H1SC'" + " your device explicitly." ) from e _ = backend.get_result(handle, use_websocket=use_websocket) diff --git a/setup.py b/setup.py index f48fb216..24d85597 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, install_requires=[ - "pytket ~= 1.14", + "pytket ~= 1.15", "requests >= 2.2", "types-requests", "websockets >= 7.0", diff --git a/tests/api1_test.py b/tests/api1_test.py index df9f442a..6c88d0c1 100644 --- a/tests/api1_test.py +++ b/tests/api1_test.py @@ -25,6 +25,7 @@ import requests from requests_mock.mocker import Mocker +from pytket.backends import ResultHandle, StatusEnum from pytket.extensions.quantinuum.backends.api_wrappers import QuantinuumAPI from pytket.extensions.quantinuum.backends import QuantinuumBackend from pytket.circuit import Circuit # type: ignore @@ -433,7 +434,6 @@ def test_available_devices( mock_quum_api_handler: QuantinuumAPI, mock_machine_info: Dict[str, Any], ) -> None: - requests_mock.register_uri( "GET", f"https://qapi.quantinuum.com/v1/machine/?config=true", @@ -456,7 +456,6 @@ def test_available_devices( assert backinfo.n_cl_reg == 120 assert backinfo.misc == { "n_shots": 10000, - "system_family": "mock_family", "system_type": "hardware", "emulator": "H9-27E", "syntax_checker": "H9-27SC", @@ -515,3 +514,38 @@ def test_submit_qasm_api( assert submitted_json["program"] == qasm assert submitted_json["count"] == 10 + + +def test_get_partial_result( + requests_mock: Mocker, + mock_quum_api_handler: QuantinuumAPI, +) -> None: + queued_job_id = "abc-123" + requests_mock.register_uri( + "GET", + f"https://qapi.quantinuum.com/v1/job/{queued_job_id}?websocket=true", + json={"job": "abc-123", "name": "job", "status": "queued"}, + headers={"Content-Type": "application/json"}, + ) + running_job_id = "abc-456" + requests_mock.register_uri( + "GET", + f"https://qapi.quantinuum.com/v1/job/{running_job_id}?websocket=true", + json={ + "job": "abc-123", + "name": "job", + "status": "running", + "results": {"c": ["10110", "10000", "10110", "01100", "10000"]}, + }, + headers={"Content-Type": "application/json"}, + ) + backend = QuantinuumBackend(device_name="H1-2SC", api_handler=mock_quum_api_handler) + h1 = ResultHandle(queued_job_id, "null") + res, status = backend.get_partial_result(h1) + assert res is None + assert status.status == StatusEnum.QUEUED + + h2 = ResultHandle(running_job_id, "null") + res, status = backend.get_partial_result(h2) + assert res is not None + assert status.status == StatusEnum.RUNNING diff --git a/tests/backend_test.py b/tests/backend_test.py index fae6cfc4..039ebf53 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -63,7 +63,17 @@ "PYTKET_RUN_REMOTE_TESTS not set (requires configuration of Quantinuum username)" ) -ALL_DEVICE_NAMES = ["H1-1SC", "H1-2SC", "H1", "H1-1", "H1-2", "H1-1E", "H1-2E"] +ALL_DEVICE_NAMES = [ + "H1-1SC", + "H1-2SC", + "H1-1", + "H1-2", + "H1-1E", + "H1-2E", + "H2-1", + "H2-1E", + "H2-1SC", +] @pytest.mark.parametrize("authenticated_quum_backend", [None], indirect=True) diff --git a/tests/conftest.py b/tests/conftest.py index 67d97a27..216e77e0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,7 +62,6 @@ def mock_machine_info() -> Dict[str, Any]: "gateset": [], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "mock_family", "system_type": "hardware", "emulator": "H9-27E", "syntax_checker": "H9-27SC", @@ -108,7 +107,6 @@ def sample_machine_infos() -> List[Dict[str, Any]]: "gateset": ["RZZ", "Riswap", "Rxxyyzz"], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "H1", "system_type": "hardware", "emulator": "H1-1E", "syntax_checker": "H1-1SC", @@ -121,7 +119,6 @@ def sample_machine_infos() -> List[Dict[str, Any]]: "gateset": ["RZZ", "Riswap", "Rxxyyzz"], "n_classical_registers": 120, "n_shots": 10000, - "system_family": "H1", "system_type": "hardware", "emulator": "H1-2E", "syntax_checker": "H1-2SC",