diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bcc44f8..84dc384 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: debug-statements - repo: https://github.com/crate-ci/typos - rev: v1.18.0 + rev: v1.18.1 hooks: - id: typos @@ -25,7 +25,7 @@ repos: - black==23.10.1 - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.2.1 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/pytket/phir/phirgen_parallel.py b/pytket/phir/phirgen_parallel.py index 2d3a08d..72d051f 100644 --- a/pytket/phir/phirgen_parallel.py +++ b/pytket/phir/phirgen_parallel.py @@ -30,23 +30,21 @@ logger = logging.getLogger(__name__) -def exec_order_preserved_helper( - ordered_dict: OrderedDict["UnitID", int], group_number: int, qubit_last_group: int +def exec_order_preserved( + group_exec_order: list[int], group_number: int, qubit_last_group: int ) -> bool: - """A helper to determine whether order is preserved when adding qubits to groups.""" - # determine whether the current group number is later in execution - # than the last group in which a qubit was used - group_eligible = group_number > qubit_last_group - if not group_eligible: + """Determine whether order is preserved when adding qubits to groups.""" + if (group_number not in group_exec_order) or ( + qubit_last_group not in group_exec_order + ): + return True + if group_number <= qubit_last_group: return False - for group in ordered_dict.values(): - if group == qubit_last_group: - order_preserved = False - break - if group == group_number: - order_preserved = True - break - return order_preserved + # if the group that the qubit is eligible for is later in the exec_order list than + # the last group in which it was used, it can be parallelized + return group_exec_order.index(group_number) > group_exec_order.index( + qubit_last_group + ) def process_sub_commands( @@ -61,28 +59,29 @@ def process_sub_commands( # and different gate types don't go in the same group # RZ gates go in (mod 3)=0 groups, R1XY gates go in (mod 3)=1 groups, # and all other gates will go in (mod 3)=2 groups - rz_group_number = -3 # will be set to 0 when first RZ gate is assigned (-3 + 3 = 0) - r1xy_group_number = ( - -2 # will be set to 1 when first R1XY gate is assigned (-2 + 3 = 1) - ) - other_group_number = ( - -1 # will be set to 2 when first other gate is assigned (-1 + 3 = 2) - ) - num_scs_per_qubit = {} - - for qubit in sub_commands: - num_scs_per_qubit[qubit] = len(sub_commands[qubit]) - # set every qubit's group id to be -4 - # prevents KeyError in check for group number - # will get set to a valid group number the first time the qubit is used - qubits2groups[qubit] = -4 + rz_group_number = -3 # set to 0 when first RZ gate is assigned (-3 + 3 = 0) + r1xy_group_number = -2 # set to 1 when first R1XY gate is assigned (-2 + 3 = 1) + other_group_number = -1 # set to 2 when first other gate is assigned (-1 + 3 = 2) + num_scs_per_qubit: dict["UnitID", int] = {} + group_exec_order: list[int] = [] + + for qubit, cmds in sub_commands.items(): + num_scs_per_qubit[qubit] = len(cmds) + max_len = max(num_scs_per_qubit.values()) for index in range(max_len): - for qubit in sub_commands: + for qubit, cmds in sub_commands.items(): + # make sure the qubits are inserted into + # qubits2groups in the order in which they appear + if qubit not in qubits2groups: + # set every qubit's group id to be a default value -4 + # prevents KeyError in check for group number + # will get set to a valid group number the first time the qubit is used + qubits2groups[qubit] = -4 # check to make sure you are not accessing beyond the end of the list if index < num_scs_per_qubit[qubit]: - sc = sub_commands[qubit][index] + sc = cmds[index] gate = sc.op.type match gate: case tk.OpType.Rz: @@ -96,18 +95,15 @@ def process_sub_commands( valid_pll_op = False # does a group exist for that gate type? group_available = group_number in groups - # is that group later in execution than the - # most recent group for an op on that qubit? - order_preserved = exec_order_preserved_helper( - qubits2groups, group_number, qubits2groups[qubit] - ) # is the group size still under the maximum allowed parallel ops? group_size = len(groups[group_number]) if group_number in groups else 0 group_not_too_large = group_size < max_parallel_sq_gates # is the op parallelizable (only RZ or R1XY)? if ( group_available - and order_preserved + and exec_order_preserved( + group_exec_order, group_number, qubits2groups[qubit] + ) and group_not_too_large and valid_pll_op ): @@ -126,6 +122,7 @@ def process_sub_commands( other_group_number += 3 group_number = other_group_number groups[group_number] = [sc] + group_exec_order.append(group_number) # prevent the group number from ever decrementing if group_number > qubits2groups[qubit]: qubits2groups[qubit] = group_number diff --git a/requirements.txt b/requirements.txt index 76c57a8..2f6d840 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ pre-commit==3.6.0 pydata_sphinx_theme==0.15.2 pytest==8.0.0 pytket==1.24.0 -ruff==0.2.0 +ruff==0.2.1 setuptools_scm==8.0.4 sphinx==7.2.6 wasmtime==17.0.0 diff --git a/tests/data/qasm/rz_exec_order_three_qubits.qasm b/tests/data/qasm/rz_exec_order_three_qubits.qasm new file mode 100644 index 0000000..7551736 --- /dev/null +++ b/tests/data/qasm/rz_exec_order_three_qubits.qasm @@ -0,0 +1,22 @@ +OPENQASM 2.0; +include "qelib1.inc"; +include "hqslib1_dev.inc"; + +qreg q[3]; +creg c[3]; + +ZZ q[1],q[2]; + +ry(0.5*pi) q[0]; +ry(0.5*pi) q[2]; + +barrier q[0],q[1],q[2]; + +ry(3.5*pi) q[0]; +ry(0.5*pi) q[2]; + + +barrier q[0],q[1],q[2]; +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; diff --git a/tests/test_parallelization.py b/tests/test_parallelization.py index 106652f..26c5b80 100644 --- a/tests/test_parallelization.py +++ b/tests/test_parallelization.py @@ -110,6 +110,29 @@ def test_single_qubit_circuit_with_parallel() -> None: ) +def test_three_qubit_rz_exec_order_preserved() -> None: + """Test that the order of gating is preserved in a 3 qubit circuit with RZ gates.""" + phir = get_phir_json(QasmFile.rz_exec_order_three_qubits, rebase=True) + # verify that the parallel RZ gates are executed before the second R1XY gate + assert phir["ops"][8] == { + "qop": "R1XY", + "angles": [[0.5, 0.5], "pi"], + "args": [["q", 0]], + } + assert phir["ops"][10] == { + "block": "qparallel", + "ops": [ + {"qop": "RZ", "angles": [[0.5], "pi"], "args": [["q", 1]]}, + {"qop": "RZ", "angles": [[1.5], "pi"], "args": [["q", 2]]}, + ], + } + assert phir["ops"][12] == { + "qop": "R1XY", + "angles": [[0.5, 0.5], "pi"], + "args": [["q", 2]], + } + + def test_two_qubit_exec_order_preserved() -> None: """Test that the order of gating in preserved in a 2 qubit circuit.""" phir = get_phir_json(QasmFile.exec_order_two_qubits, rebase=True) diff --git a/tests/test_utils.py b/tests/test_utils.py index 1eb6e7b..2ed0877 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -47,6 +47,7 @@ class QasmFile(Enum): classical_ordering = auto() single_qubit_parallel_test = auto() exec_order_two_qubits = auto() + rz_exec_order_three_qubits = auto() cond_barrier = auto() arbitrary_qreg_names = auto()