diff --git a/pyproject.toml b/pyproject.toml index 0280127..7647bb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,11 @@ name = "pytket-phir" description = "A circuit analyzer and translator from pytket to PHIR" readme = "README.md" requires-python = ">=3.10, <3.13" -license = {file = "LICENSE"} -authors = [{name = "Quantinuum"}] +license = { file = "LICENSE" } +authors = [{ name = "Quantinuum" }] +maintainers = [ + { name = "Kartik Singhal", email = "Kartik.Singhal@quantinuum.com" }, +] classifiers = [ "Environment :: Console", @@ -50,9 +53,7 @@ where = ["."] [tool.pytest.ini_options] addopts = "-s -vv" -pythonpath = [ - "." -] +pythonpath = ["."] log_cli = true log_cli_level = "INFO" log_level = "DEBUG" diff --git a/pytket/phir/phirgen.py b/pytket/phir/phirgen.py index 5cfb4e0..0db08b3 100644 --- a/pytket/phir/phirgen.py +++ b/pytket/phir/phirgen.py @@ -11,6 +11,7 @@ import json import logging import sys +from collections import deque from copy import deepcopy from importlib.metadata import version from typing import TYPE_CHECKING, Any, TypeAlias @@ -356,15 +357,29 @@ def convert_classicalevalop(op: tk.ClassicalEvalOp, cmd: tk.Command) -> JsonDict def multi_bit_condition(args: "list[UnitID]", value: int) -> JsonDict: """Construct bitwise condition.""" - return { - "cop": "&", - "args": [ - {"cop": "==", "args": [arg_to_bit(arg), bval]} - for (arg, bval) in zip( - args[::-1], map(int, f"{value:0{len(args)}b}"), strict=True - ) - ], - } + min_args = 2 + if len(args) < min_args: + msg = f"multi_bit_condition requires at least {min_args} arguments" + raise TypeError(msg) + + def nested_cop(cop: str, args: "deque[UnitID]", val_bits: deque[int]) -> JsonDict: + if len(args) == min_args: + return { + "cop": cop, + "args": [ + {"cop": "==", "args": [arg_to_bit(args.popleft()), val_bits.pop()]}, + {"cop": "==", "args": [arg_to_bit(args.popleft()), val_bits.pop()]}, + ], + } + return { + "cop": cop, + "args": [ + {"cop": "==", "args": [arg_to_bit(args.popleft()), val_bits.pop()]}, + nested_cop(cop, args, val_bits), + ], + } + + return nested_cop("&", deque(args), deque(map(int, f"{value:0{len(args)}b}"))) def convert_subcmd(op: tk.Op, cmd: tk.Command) -> JsonDict | None: # noqa: PLR0912 diff --git a/requirements.txt b/requirements.txt index 0649295..2e682ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ networkx<3 phir==0.3.3 pre-commit==3.8.0 pydata_sphinx_theme==0.15.4 -pytest==8.3.2 +pytest==8.3.3 pytket==1.32.0 ruff==0.6.4 setuptools_scm==8.1.0 diff --git a/tests/test_phirgen.py b/tests/test_phirgen.py index bbaed38..d4a28b5 100644 --- a/tests/test_phirgen.py +++ b/tests/test_phirgen.py @@ -94,8 +94,8 @@ def test_pytket_classical_only() -> None: "condition": { "cop": "&", "args": [ - {"cop": "==", "args": [["b", 2], 1]}, {"cop": "==", "args": [["b", 1], 0]}, + {"cop": "==", "args": [["b", 2], 1]}, ], }, "true_branch": [ @@ -177,8 +177,8 @@ def test_conditional_barrier() -> None: "condition": { "cop": "&", "args": [ - {"cop": "==", "args": [["m", 1], 0]}, {"cop": "==", "args": [["m", 0], 0]}, + {"cop": "==", "args": [["m", 1], 0]}, ], }, "true_branch": [{"meta": "barrier", "args": [["q", 0], ["q", 1]]}], @@ -463,3 +463,26 @@ def test_nullary_ops() -> None: "cop": "==", "args": [["tk_SCRATCH_BIT", 1], 1], # evals to False } + + +def test_condition_multiple_bits() -> None: + """From https://github.com/CQCL/pytket-phir/issues/215 .""" + n_bits = 3 + c = Circuit(1, n_bits) + c.Rz(0.5, 0, condition_bits=list(range(n_bits)), condition_value=6) + phir = json.loads(pytket_to_phir(c)) + + assert phir["ops"][2] == {"//": "IF ([c[0], c[1], c[2]] == 6) THEN Rz(0.5) q[0];"} + assert phir["ops"][3]["condition"] == { + "cop": "&", + "args": [ + {"cop": "==", "args": [["c", 0], 0]}, + { + "cop": "&", + "args": [ + {"cop": "==", "args": [["c", 1], 1]}, + {"cop": "==", "args": [["c", 2], 1]}, + ], + }, + ], + }