From 90f1e258766a77c8889ef3cf6668f6ec7ea881b1 Mon Sep 17 00:00:00 2001 From: Kartik Singhal Date: Mon, 15 Jul 2024 10:06:45 -0500 Subject: [PATCH 1/5] fix: Abstract WORDSIZE to specify maxwidth on qasm conversion Closes: #203 --- pytket/phir/api.py | 8 +++++--- pytket/phir/cli.py | 5 +++-- pytket/phir/phirgen.py | 4 +++- tests/test_phirgen.py | 3 ++- tests/test_utils.py | 5 ++++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pytket/phir/api.py b/pytket/phir/api.py index f718dc2..ec7e97f 100644 --- a/pytket/phir/api.py +++ b/pytket/phir/api.py @@ -18,7 +18,7 @@ from phir.model import PHIRModel from pytket.qasm.qasm import circuit_from_qasm_str, circuit_from_qasm_wasm -from .phirgen import genphir +from .phirgen import WORDSIZE, genphir from .phirgen_parallel import genphir_parallel from .place_and_route import place_and_route from .qtm_machine import QTM_MACHINES_MAP, QtmMachine @@ -100,10 +100,12 @@ def qasm_to_phir( wasm_file.flush() wasm_file.close() - circuit = circuit_from_qasm_wasm(qasm_file.name, wasm_file.name) + circuit = circuit_from_qasm_wasm( + qasm_file.name, wasm_file.name, maxwidth=WORDSIZE + ) finally: Path.unlink(Path(qasm_file.name)) Path.unlink(Path(wasm_file.name)) else: - circuit = circuit_from_qasm_str(qasm) + circuit = circuit_from_qasm_str(qasm, maxwidth=WORDSIZE) return pytket_to_phir(circuit, qtm_machine) diff --git a/pytket/phir/cli.py b/pytket/phir/cli.py index 029a2b2..3670fe2 100644 --- a/pytket/phir/cli.py +++ b/pytket/phir/cli.py @@ -16,6 +16,7 @@ from pecos.engines.hybrid_engine import HybridEngine from pecos.foreign_objects.wasmtime import WasmtimeObj +from pytket.phir.phirgen import WORDSIZE from pytket.qasm.qasm import ( circuit_from_qasm, circuit_from_qasm_wasm, @@ -60,10 +61,10 @@ def main() -> None: circuit = None if args.wasm_file: print(f"Including WASM from file {args.wasm_file}") - circuit = circuit_from_qasm_wasm(file, args.wasm_file) + circuit = circuit_from_qasm_wasm(file, args.wasm_file, maxwidth=WORDSIZE) wasm_pecos_obj = WasmtimeObj(args.wasm_file) else: - circuit = circuit_from_qasm(file) + circuit = circuit_from_qasm(file, maxwidth=WORDSIZE) match args.machine: case "H1": diff --git a/pytket/phir/phirgen.py b/pytket/phir/phirgen.py index fd2f513..9eb28a8 100644 --- a/pytket/phir/phirgen.py +++ b/pytket/phir/phirgen.py @@ -45,7 +45,9 @@ "version": "0.1.0", "metadata": {"source": f'pytket-phir v{version("pytket-phir").split("+")[0]}'}, } -UINTMAX = 2 ** (64 if pytket.__dict__.get("bit_width_64", False) else 32) - 1 + +WORDSIZE = 64 if pytket.__dict__.get("bit_width_64", False) else 32 +UINTMAX = 2**WORDSIZE - 1 Var: TypeAlias = str Bit: TypeAlias = list[Var | int] # e.g. [c, 0] for c[0] diff --git a/tests/test_phirgen.py b/tests/test_phirgen.py index 7531b48..bbaed38 100644 --- a/tests/test_phirgen.py +++ b/tests/test_phirgen.py @@ -13,6 +13,7 @@ from pytket.circuit import Bit, Circuit from pytket.circuit.logic_exp import BitWiseOp, create_bit_logic_exp from pytket.phir.api import pytket_to_phir +from pytket.phir.phirgen import WORDSIZE from pytket.qasm.qasm import circuit_from_qasm_str from pytket.unit_id import BitRegister @@ -30,7 +31,7 @@ def test_multiple_sleep() -> None: sleep(1) q[0]; sleep(2) q[1]; """ - circ = circuit_from_qasm_str(qasm) + circ = circuit_from_qasm_str(qasm, maxwidth=WORDSIZE) phir = json.loads(pytket_to_phir(circ)) assert phir["ops"][2] == {"mop": "Idle", "args": [["q", 0]], "duration": [1.0, "s"]} assert phir["ops"][4] == {"mop": "Idle", "args": [["q", 1]], "duration": [2.0, "s"]} diff --git a/tests/test_utils.py b/tests/test_utils.py index 06e6efd..dc351e6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -13,6 +13,7 @@ from wasmtime import wat2wasm +from pytket.phir.phirgen import WORDSIZE from pytket.phir.phirgen_parallel import genphir_parallel from pytket.phir.place_and_route import place_and_route from pytket.phir.qtm_machine import QTM_MACHINES_MAP, QtmMachine @@ -69,7 +70,9 @@ def get_qasm_as_circuit(qasm_file: QasmFile) -> "Circuit": Corresponding tket circuit """ this_dir = Path(Path(__file__).resolve()).parent - return circuit_from_qasm(f"{this_dir}/data/qasm/{qasm_file.name}.qasm") + return circuit_from_qasm( + f"{this_dir}/data/qasm/{qasm_file.name}.qasm", maxwidth=WORDSIZE + ) def get_phir_json(qasmfile: QasmFile, *, rebase: bool) -> "JsonDict": From a979fbcfa891a62210a359220f07428a0558d9dd Mon Sep 17 00:00:00 2001 From: Kartik Singhal Date: Mon, 15 Jul 2024 15:36:49 -0500 Subject: [PATCH 2/5] fix: generate cvar with data_type dependent on WORDSIZE --- pytket/phir/phirgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/phir/phirgen.py b/pytket/phir/phirgen.py index 9eb28a8..dc5e6ec 100644 --- a/pytket/phir/phirgen.py +++ b/pytket/phir/phirgen.py @@ -546,7 +546,7 @@ def get_decls(qbits: set["Qubit"], cbits: set[tkBit]) -> list[dict[str, str | in decls += [ { "data": "cvar_define", - "data_type": "u32", + "data_type": f"u{WORDSIZE}", "variable": cvar, "size": dim, } From bc4420164b2ebd81f1443969f8f55035e55186d3 Mon Sep 17 00:00:00 2001 From: Kartik Singhal Date: Mon, 15 Jul 2024 15:38:18 -0500 Subject: [PATCH 3/5] chore: update deps --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index f4476f7..9e6b40d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,9 +6,9 @@ pre-commit==3.7.1 pydata_sphinx_theme==0.15.4 pytest==8.2.2 pytest-order==1.2.1 -pytket==1.29.2 -ruff==0.5.1 +pytket==1.30.0 +ruff==0.5.2 setuptools_scm==8.1.0 -sphinx==7.3.7 +sphinx==7.4.4 wasmtime==22.0.0 wheel==0.43.0 From 2855da27de7593b930e0972ec8c9ce8ac5a88c8d Mon Sep 17 00:00:00 2001 From: Kartik Singhal Date: Mon, 15 Jul 2024 17:08:31 -0500 Subject: [PATCH 4/5] fix: fail early on WASM calls with more than 32-bit registers --- pytket/phir/phirgen.py | 4 ++++ tests/test_wasm.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/pytket/phir/phirgen.py b/pytket/phir/phirgen.py index dc5e6ec..8187a16 100644 --- a/pytket/phir/phirgen.py +++ b/pytket/phir/phirgen.py @@ -46,6 +46,7 @@ "metadata": {"source": f'pytket-phir v{version("pytket-phir").split("+")[0]}'}, } +WASM_WORDSIZE = 32 WORDSIZE = 64 if pytket.__dict__.get("bit_width_64", False) else 32 UINTMAX = 2**WORDSIZE - 1 @@ -479,6 +480,9 @@ def extract_wasm_args_and_returns( only_args = command.args[:-slice_index] # Eliminate conditional bits from the front of the args input_args = only_args[len(only_args) - op.n_inputs :] + if any(arg.index[0] >= WASM_WORDSIZE for arg in input_args): + msg = "WASM support is limited to at most 32-bit registers" + raise ValueError(msg) return ( dedupe_bits_to_registers(input_args), dedupe_bits_to_registers(command.bits), diff --git a/tests/test_wasm.py b/tests/test_wasm.py index 87cf0a8..8646ef9 100644 --- a/tests/test_wasm.py +++ b/tests/test_wasm.py @@ -64,6 +64,23 @@ def test_qasm_to_phir_with_wasm() -> None: } +def test_qasm_wasm_unsupported_reg_len() -> None: + """Test that qasm containing calls to WASM with more than 32-bits fails.""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + + creg cr[33]; + + cr = add(cr, cr); + """ + + wasm_bytes = get_wat_as_wasm_bytes(WatFile.add) + + with pytest.raises(ValueError, match="limited to at most 32-bit registers"): + qasm_to_phir(qasm, QtmMachine.H1, wasm_bytes=wasm_bytes) + + @pytest.mark.order("first") def test_pytket_with_wasm() -> None: """Test whether pytket works with WASM.""" @@ -142,6 +159,26 @@ def test_pytket_with_wasm() -> None: } +def test_pytket_wasm_unsupported_reg_len() -> None: + """Test that pytket circuit calling WASM with more than 32-bits fails.""" + wasm_bytes = get_wat_as_wasm_bytes(WatFile.testfile) + try: + wasm_file = NamedTemporaryFile(suffix=".wasm", delete=False) + wasm_file.write(wasm_bytes) + wasm_file.flush() + wasm_file.close() + + w = WasmFileHandler(wasm_file.name) + + c = Circuit(0, 33) + c0 = c.add_c_register("c0", 33) + + with pytest.raises(ValueError, match="only registers of at most 32 bits"): + c.add_wasm_to_reg("no_return", w, [c0], []) + finally: + Path.unlink(Path(wasm_file.name)) + + def test_conditional_wasm() -> None: """From https://github.com/CQCL/pytket-phir/issues/156 .""" wasm_bytes = get_wat_as_wasm_bytes(WatFile.testfile) From 414de00faeec8f31f6c031b7407f33be064a798c Mon Sep 17 00:00:00 2001 From: Kartik Singhal Date: Mon, 15 Jul 2024 18:06:32 -0500 Subject: [PATCH 5/5] fix: require pecos supporting 64-bits, emit i32/i64 cvars for compatibility --- pyproject.toml | 2 +- pytket/phir/api.py | 2 +- pytket/phir/cli.py | 2 +- pytket/phir/phirgen.py | 14 ++++++++++---- tests/test_utils.py | 2 +- tests/test_wasm.py | 6 +++++- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7a08cbb..0280127 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ [project.optional-dependencies] docs = ["sphinx", "pydata_sphinx_theme"] -phirc = ["projectq", "quantum-pecos>=0.5.0.dev10"] +phirc = ["projectq", "quantum-pecos>=0.6.0.dev2"] tests = ["pytest"] [project.scripts] diff --git a/pytket/phir/api.py b/pytket/phir/api.py index ec7e97f..2ed888b 100644 --- a/pytket/phir/api.py +++ b/pytket/phir/api.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2023 Quantinuum LLC All rights reserved. +# Copyright (c) 2023-2024 Quantinuum LLC All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. # diff --git a/pytket/phir/cli.py b/pytket/phir/cli.py index 3670fe2..9da9dc1 100644 --- a/pytket/phir/cli.py +++ b/pytket/phir/cli.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2023 Quantinuum LLC All rights reserved. +# Copyright (c) 2023-2024 Quantinuum LLC All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. # diff --git a/pytket/phir/phirgen.py b/pytket/phir/phirgen.py index 8187a16..5cfb4e0 100644 --- a/pytket/phir/phirgen.py +++ b/pytket/phir/phirgen.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2023 Quantinuum LLC All rights reserved. +# Copyright (c) 2023-2024 Quantinuum LLC All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. # @@ -10,11 +10,17 @@ import json import logging +import sys from copy import deepcopy from importlib.metadata import version from typing import TYPE_CHECKING, Any, TypeAlias -from typing_extensions import assert_never +from pytket.qasm.qasm import QASMUnsupportedError + +if sys.version_info >= (3, 11): + from typing import assert_never +else: + from typing_extensions import assert_never import pytket import pytket.circuit as tk @@ -482,7 +488,7 @@ def extract_wasm_args_and_returns( input_args = only_args[len(only_args) - op.n_inputs :] if any(arg.index[0] >= WASM_WORDSIZE for arg in input_args): msg = "WASM support is limited to at most 32-bit registers" - raise ValueError(msg) + raise QASMUnsupportedError(msg) return ( dedupe_bits_to_registers(input_args), dedupe_bits_to_registers(command.bits), @@ -550,7 +556,7 @@ def get_decls(qbits: set["Qubit"], cbits: set[tkBit]) -> list[dict[str, str | in decls += [ { "data": "cvar_define", - "data_type": f"u{WORDSIZE}", + "data_type": f"i{WORDSIZE}", "variable": cvar, "size": dim, } diff --git a/tests/test_utils.py b/tests/test_utils.py index dc351e6..0658f86 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,6 @@ ############################################################################## # -# Copyright (c) 2023 Quantinuum LLC All rights reserved. +# Copyright (c) 2023-2024 Quantinuum LLC All rights reserved. # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. # diff --git a/tests/test_wasm.py b/tests/test_wasm.py index 8646ef9..69be6e7 100644 --- a/tests/test_wasm.py +++ b/tests/test_wasm.py @@ -20,6 +20,7 @@ from pytket.circuit import Circuit, Qubit from pytket.phir.api import pytket_to_phir, qasm_to_phir from pytket.phir.qtm_machine import QtmMachine +from pytket.qasm.qasm import QASMUnsupportedError from pytket.wasm.wasm import WasmFileHandler from .test_utils import WatFile, get_wat_as_wasm_bytes @@ -77,7 +78,10 @@ def test_qasm_wasm_unsupported_reg_len() -> None: wasm_bytes = get_wat_as_wasm_bytes(WatFile.add) - with pytest.raises(ValueError, match="limited to at most 32-bit registers"): + with pytest.raises( + QASMUnsupportedError, + match="limited to at most 32-bit|try setting the `maxwidth` parameter", + ): qasm_to_phir(qasm, QtmMachine.H1, wasm_bytes=wasm_bytes)