diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 862f0d1..b5f694a 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -31,33 +31,44 @@ jobs: os: ['ubuntu-22.04', 'macos-12', 'windows-2022'] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/* - name: Set up Python 3.9 + if: github.event_name == 'push' || github.event_name == 'schedule' uses: actions/setup-python@v4 with: python-version: '3.9' - - name: Build and test including remote checks (3.9) mypy - if: (matrix.os == 'macos-12') && (github.event_name == 'pull_request' || github.event_name == 'release' || contains(github.ref, 'refs/heads/wheel') || github.event_name == 'schedule') + - name: Build and test (3.9) + if: github.event_name == 'push' || github.event_name == 'schedule' + shell: bash + run: | + ./.github/workflows/build-test nomypy + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Build and test including remote checks (3.10) mypy + shell: bash + if: (matrix.os == 'macos-12') && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'release' || github.event_name == 'schedule' ) run: | ./.github/workflows/build-test mypy env: PYTKET_RUN_REMOTE_TESTS: 1 - - name: Build and test including remote checks (3.9) nomypy + - name: Build and test including remote checks (3.10) nomypy if: (matrix.os != 'macos-12') && (github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'release' || github.event_name == 'schedule') shell: bash run: | ./.github/workflows/build-test nomypy env: PYTKET_RUN_REMOTE_TESTS: 1 - - name: Set up Python 3.10 + - name: Set up Python 3.11 if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'schedule' uses: actions/setup-python@v4 with: - python-version: '3.10' - - name: Build and test (3.10) + python-version: '3.11' + - name: Build and test (3.11) if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'schedule' shell: bash run: | @@ -104,13 +115,13 @@ jobs: needs: publish_to_pypi runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: '0' - - name: Set up Python 3.9 + - name: Set up Python 3.10 uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.10' - name: Download all wheels uses: actions/download-artifact@v3 with: @@ -129,7 +140,7 @@ jobs: mkdir extensions ./build-docs -d ${GITHUB_WORKSPACE}/.github/workflows/docs/extensions/api - name: Upload docs as artefact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v2 with: path: .github/workflows/docs/extensions @@ -147,4 +158,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 15eff11..153ff10 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -12,7 +12,7 @@ jobs: name: build docs runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v4 with: diff --git a/.github/workflows/docs/Quantinuum_logo.png b/.github/workflows/docs/Quantinuum_logo_black.png similarity index 100% rename from .github/workflows/docs/Quantinuum_logo.png rename to .github/workflows/docs/Quantinuum_logo_black.png diff --git a/.github/workflows/docs/Quantinuum_logo_white.png b/.github/workflows/docs/Quantinuum_logo_white.png new file mode 100644 index 0000000..e896db9 Binary files /dev/null and b/.github/workflows/docs/Quantinuum_logo_white.png differ diff --git a/.github/workflows/docs/build-docs b/.github/workflows/docs/build-docs index 7865143..b256ac7 100755 --- a/.github/workflows/docs/build-docs +++ b/.github/workflows/docs/build-docs @@ -16,6 +16,7 @@ MODULE = "iqm" PYTKET_IQM_PYPI_LINK = "https://pypi.org/project/pytket-iqm/" PYTKET_IQM_GITHUB = "https://github.com/CQCL/pytket-iqm" + def get_module_version(): m = import_module(f"pytket.extensions.{MODULE}") return m._metadata.__extension_version__.split(".") @@ -39,9 +40,11 @@ def build_module_docs(): mod_docs = MODULES_DIR / "docs" mod_build = mod_docs / "build" conf_copy = mod_docs / "conf.py" - logo_copy = mod_docs / "Quantinuum_logo.png" + logo_copy_black = mod_docs / "Quantinuum_logo_black.png" + logo_copy_white = mod_docs / "Quantinuum_logo_white.png" shutil.copy(DOCS_DIR / "conf.py", conf_copy) - shutil.copy(DOCS_DIR / "Quantinuum_logo.png", logo_copy) + shutil.copy(DOCS_DIR / "Quantinuum_logo_black.png", logo_copy_black) + shutil.copy(DOCS_DIR / "Quantinuum_logo_white.png", logo_copy_white) remove_dir(mod_build) index_rst = mod_docs / "index.rst" with open(mod_docs / "intro.txt", "r") as f: @@ -51,14 +54,11 @@ def build_module_docs(): ) content.append(f"\tpytket <{PYTKET_DOCS_LINK}>\n") content.append(f"\tpytket extensions <{PYTKET_EX_DOCS_LINK}>\n") - content.append( - "\n.. toctree::\n\t:caption: Links:\n\t:maxdepth: 1\n\n" - ) + content.append("\n.. toctree::\n\t:caption: Links:\n\t:maxdepth: 1\n\n") content.append(f"\tbug tracker <{PYTKET_IQM_GITHUB}/issues>\n") content.append(f"\tGitHub <{PYTKET_IQM_GITHUB}>\n") content.append(f"\tPyPi <{PYTKET_IQM_PYPI_LINK}>\n") - with open(index_rst, "w") as f: f.writelines(content) subprocess.run( @@ -69,7 +69,7 @@ def build_module_docs(): "-D", f"project=pytket-{MODULE}", "-D", - f"copyright={datetime.date.today().year} Cambridge Quantum Computing", + f"copyright={datetime.date.today().year} Quantinuum", "-D", f"version={'.'.join(v[:2])}", "-D", @@ -83,7 +83,8 @@ def build_module_docs(): fix_links(htmlfile) fix_links(mod_build / "searchindex.js") conf_copy.unlink() - logo_copy.unlink() + logo_copy_black.unlink() + logo_copy_white.unlink() index_rst.unlink() @@ -103,4 +104,4 @@ if __name__ == "__main__": MODULES_DIR / "docs" / "build", dest, dirs_exist_ok=True, - ) + ) diff --git a/.github/workflows/docs/conf.py b/.github/workflows/docs/conf.py index c44f60b..99c1e64 100644 --- a/.github/workflows/docs/conf.py +++ b/.github/workflows/docs/conf.py @@ -3,7 +3,8 @@ # Configuration file for the Sphinx documentation builder. # See https://www.sphinx-doc.org/en/master/usage/configuration.html -author = "Cambridge Quantum Computing Ltd" +copyright = "2023 Quantinuum" +author = "Quantinuum" extensions = [ "sphinx.ext.autodoc", @@ -13,22 +14,22 @@ "sphinx_copybutton", ] -pygments_style = "borland" - html_theme = "sphinx_book_theme" html_theme_options = { "repository_url": "https://github.com/CQCL/pytket-iqm", "use_repository_button": True, "use_issues_button": True, + "logo": { + "image_light": "Quantinuum_logo_black.png", + "image_dark": "Quantinuum_logo_white.png", + }, } html_static_path = ["_static"] html_css_files = ["custom.css"] -html_logo = "Quantinuum_logo.png" - # -- Extension configuration ------------------------------------------------- pytketdoc_base = "https://cqcl.github.io/tket/pytket/api/" @@ -114,7 +115,6 @@ def correct_signature( signature: str, return_annotation: str, ) -> (str, str): - new_signature = signature new_return_annotation = return_annotation for k, v in app.config.custom_internal_mapping.items(): diff --git a/.github/workflows/docs/intro.txt b/.github/workflows/docs/intro.txt deleted file mode 100644 index d269c7a..0000000 --- a/.github/workflows/docs/intro.txt +++ /dev/null @@ -1,15 +0,0 @@ -pytket-extensions -================= - -.. image:: CQCLogo.png - :width: 120px - :align: right - - -These extensions enable `CQC`_ `pytket`_ to be used in conjunction with other -platforms. Each extension adds either new methods to the ``pytket`` package to -convert between circuit representations, or new backends to which ``pytket`` -circuits can be submitted. - -.. _pytket: https://cqcl.github.io/tket/pytket/api/ -.. _CQC: https://cambridgequantum.com diff --git a/.github/workflows/docs/requirements.txt b/.github/workflows/docs/requirements.txt index 31e9126..c8d2b6c 100644 --- a/.github/workflows/docs/requirements.txt +++ b/.github/workflows/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx ~= 4.3.2 -sphinx_book_theme +sphinx >= 4.3.2, <6.2.0 +sphinx_book_theme >= 1.0.1, <2.0 sphinx-copybutton diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dfb5901..bed8273 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python 3.x uses: actions/setup-python@v4 with: diff --git a/README.md b/README.md index cb9fd6e..bc24485 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ circuit.H(0) circuit.CX(0, 1) circuit.CX(0, 2) circuit.measure_all() -circuit = backend.get_compiled_circuit(circuit) +compiled_circuit = backend.get_compiled_circuit(circuit) -result = backend.run_circuit(c, n_shots=100) +result = backend.run_circuit(compiled_circuit, n_shots=100) print(result.get_shots()) ``` diff --git a/_metadata.py b/_metadata.py index be52f94..323ac76 100644 --- a/_metadata.py +++ b/_metadata.py @@ -1,2 +1,2 @@ -__extension_version__ = "0.6.0" +__extension_version__ = "0.7.0" __extension_name__ = "pytket-iqm" diff --git a/docs/_static/custom.css b/docs/_static/custom.css index 732d595..9c8a6ed 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -16,10 +16,6 @@ background-color: #d9d9d9; } -.caption-text { - color: #000000; -} - .btn-link:visited, .btn-link, a:visited, @@ -30,13 +26,8 @@ a:visited, .wy-menu-vertical ul, .span.pre, .sig-param, -.std.std-ref, -a { - color: #544d4d; -} - -:root { - --pst-color-inline-code: 199, 37, 78 !important; +html[data-theme=light] { + --pst-color-inline-code: rgb(199, 37, 78) !important; } .sig-name { diff --git a/docs/changelog.rst b/docs/changelog.rst index 987a58f..c34bddf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changelog ~~~~~~~~~ +0.7.0 (October 2023) +-------------------- + +* Update pytket version requirement to 1.20. +* Update iqm-client version requirement to 14.0. +* Fix job status checks. +* Add support for token-based authentication. + 0.6.0 (March 2023) ------------------ diff --git a/docs/intro.txt b/docs/intro.txt index 2efc6ff..06e1d42 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -1,23 +1,10 @@ pytket-iqm ========== -.. raw:: html - - -
- -
- - -.. image:: Quantinuum_logo.png - :width: 0px - :height: 0px - :align: right - ``pytket-iqm`` is an extension to ``pytket`` that allows ``pytket`` circuits to be executed on IQM's quantum devices and simulators. -``pytket-iqm`` is available for Python 3.9 and 3.10, on Linux, MacOS and +``pytket-iqm`` is available for Python 3.9, 3.10 and 3.11, on Linux, MacOS and Windows. To install, run: :: diff --git a/mypy.ini b/mypy.ini index 6da039b..10efcea 100644 --- a/mypy.ini +++ b/mypy.ini @@ -14,7 +14,7 @@ namespace_packages = True check_untyped_defs = True warn_redundant_casts = True -warn_unused_ignores = False +warn_unused_ignores = True warn_no_return = False warn_return_any = True warn_unreachable = True diff --git a/pytket/extensions/iqm/__init__.py b/pytket/extensions/iqm/__init__.py index 0e42819..8742c19 100644 --- a/pytket/extensions/iqm/__init__.py +++ b/pytket/extensions/iqm/__init__.py @@ -16,6 +16,6 @@ """ # _metadata.py is copied to the folder after installation. -from ._metadata import __extension_version__, __extension_name__ # type: ignore +from ._metadata import __extension_version__, __extension_name__ from .backends import IQMBackend from .backends.config import IQMConfig, set_iqm_config diff --git a/pytket/extensions/iqm/backends/config.py b/pytket/extensions/iqm/backends/config.py index fb717b9..7c14c5b 100644 --- a/pytket/extensions/iqm/backends/config.py +++ b/pytket/extensions/iqm/backends/config.py @@ -48,9 +48,9 @@ def set_iqm_config( """Set default value for IQM API token.""" config = IQMConfig.from_default_config_file() if auth_server_url is not None: - config.auth_server_url = auth_server_url + config.auth_server_url = auth_server_url # type: ignore if username is not None: - config.username = username + config.username = username # type: ignore if password is not None: - config.password = password + config.password = password # type: ignore config.update_default_config_file() diff --git a/pytket/extensions/iqm/backends/iqm.py b/pytket/extensions/iqm/backends/iqm.py index 2c818a6..43585a6 100644 --- a/pytket/extensions/iqm/backends/iqm.py +++ b/pytket/extensions/iqm/backends/iqm.py @@ -13,14 +13,11 @@ # limitations under the License. import json +import os from typing import cast, Dict, List, Optional, Sequence, Tuple, Union from uuid import UUID -from iqm_client.iqm_client import Circuit as IQMCircuit -from iqm_client.iqm_client import ( - Instruction, - IQMClient, - Metadata, -) +from iqm.iqm_client.iqm_client import Circuit as IQMCircuit +from iqm.iqm_client.iqm_client import Instruction, IQMClient, Metadata, Status import numpy as np from pytket.backends import Backend, CircuitStatus, ResultHandle, StatusEnum from pytket.backends.backend import KwargTypes @@ -28,9 +25,9 @@ from pytket.backends.backendinfo import BackendInfo from pytket.backends.backendresult import BackendResult from pytket.backends.resulthandle import _ResultIdTuple -from pytket.circuit import Circuit, Node, OpType # type: ignore +from pytket.circuit import Circuit, Node, OpType from pytket.extensions.iqm._metadata import __extension_version__ -from pytket.passes import ( # type: ignore +from pytket.passes import ( BasePass, SequencePass, SynthesiseTket, @@ -43,7 +40,7 @@ DelayMeasures, SimplifyInitial, ) -from pytket.predicates import ( # type: ignore +from pytket.predicates import ( ConnectivityPredicate, GateSetPredicate, NoClassicalControlPredicate, @@ -53,7 +50,7 @@ NoSymbolsPredicate, Predicate, ) -from pytket.architecture import Architecture # type: ignore +from pytket.architecture import Architecture from pytket.utils import prepare_circuit from pytket.utils.outcomearray import OutcomeArray from .config import IQMConfig @@ -95,9 +92,15 @@ def __init__( """ Construct a new IQM backend. - Requires a valid username and API key. These can either be provided as - parameters or set in config using - :py:meth:`pytket.extensions.iqm.set_iqm_config`. + Requires _either_ a valid auth server URL, username and password, _or_ a tokens + file. + + Auth server URL, username and password can either be provided as parameters or + set in config using :py:meth:`pytket.extensions.iqm.set_iqm_config`. + + Path to the tokens file is read from the environmment variable + ``IQM_TOKENS_FILE``. If set, this overrides any other credentials provided as + arguments. :param url: base URL for requests :param arch: Optional list of couplings between the qubits defined, if @@ -111,22 +114,24 @@ def __init__( config = IQMConfig.from_default_config_file() if auth_server_url is None: - auth_server_url = config.auth_server_url + auth_server_url = config.auth_server_url # type: ignore + tokens_file = os.getenv("IQM_TOKENS_FILE") if username is None: - username = config.username - if username is None: - raise IqmAuthenticationError() - if password is None: - password = config.password + username = config.username # type: ignore if password is None: + password = config.password # type: ignore + if (username is None or password is None) and tokens_file is None: raise IqmAuthenticationError() - self._client = IQMClient( - self._url, - auth_server_url=auth_server_url, - username=username, - password=password, - ) + if tokens_file is None: + self._client = IQMClient( + self._url, + auth_server_url=auth_server_url, + username=username, + password=password, + ) + else: + self._client = IQMClient(self._url, tokens_file=tokens_file) _iqmqa = self._client.get_quantum_architecture() self._operations = [_IQM_PYTKET_OP_MAP[op] for op in _iqmqa.operations] self._qubits = [_as_node(qb) for qb in _iqmqa.qubits] @@ -220,7 +225,7 @@ def process_circuits( else: c0, ppcirc_rep = c, None instrs = _translate_iqm(c0) - qm = {str(qb): _as_name(qb) for qb in c.qubits} + qm = {str(qb): _as_name(cast(Node, qb)) for qb in c.qubits} iqmc = IQMCircuit( name=c.name if c.name else f"circuit_{i}", instructions=instrs, @@ -247,9 +252,9 @@ def circuit_status(self, handle: ResultHandle) -> CircuitStatus: run_id = UUID(bytes=cast(bytes, handle[0])) run_result = self._client.get_run(run_id) status = run_result.status - if status == "pending": + if status == Status.PENDING_EXECUTION: return CircuitStatus(StatusEnum.SUBMITTED) - elif status == "ready": + elif status == Status.READY: measurements = cast(dict, run_result.measurements)[0] shots = OutcomeArray.from_readouts( np.array( @@ -266,7 +271,7 @@ def circuit_status(self, handle: ResultHandle) -> CircuitStatus: ) return CircuitStatus(StatusEnum.COMPLETED) else: - assert status == "failed" + assert status == Status.FAILED return CircuitStatus(StatusEnum.ERROR, cast(str, run_result.message)) def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResult: diff --git a/setup.py b/setup.py index 4c636aa..4ce20d9 100644 --- a/setup.py +++ b/setup.py @@ -42,11 +42,12 @@ license="Apache 2", packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, - install_requires=["pytket ~= 1.13", "iqm-client ~= 11.8"], + install_requires=["pytket ~= 1.20", "iqm-client ~= 14.0"], classifiers=[ "Environment :: Console", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", diff --git a/tests/backend_test.py b/tests/backend_test.py index ce37ee0..4cc4499 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -17,8 +17,8 @@ import pytest from requests import get from conftest import get_demo_url # type: ignore -from iqm_client.iqm_client import ClientAuthenticationError, Metadata, RunRequest -from pytket.circuit import Circuit # type: ignore +from iqm.iqm_client.iqm_client import ClientAuthenticationError, Metadata, RunRequest +from pytket.circuit import Circuit from pytket.backends import StatusEnum from pytket.extensions.iqm import IQMBackend diff --git a/tests/convert_test.py b/tests/convert_test.py index 806ef1d..c9aa947 100644 --- a/tests/convert_test.py +++ b/tests/convert_test.py @@ -14,7 +14,7 @@ import os import numpy as np -from pytket.circuit import Circuit, OpType # type: ignore +from pytket.circuit import Circuit, OpType from pytket.extensions.iqm.backends.iqm import _iqm_rebase skip_remote_tests: bool = os.getenv("PYTKET_RUN_REMOTE_TESTS") is None