From 961f903558265eb4a9918e428d66207aaef9173c Mon Sep 17 00:00:00 2001 From: Chris Elenbaas <67630508+elenbaasc@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:39:14 +0100 Subject: [PATCH] [CQT-259] Integrate wait and barrier directives in cQASMv1 exporter (#407) * Restore SGMQ notation for barrier links. Add tests for barrier links. --------- Co-authored-by: Roberto Turrado Camblor --- .github/workflows/release.yaml | 2 +- .github/workflows/tests.yaml | 4 +- CHANGELOG.md | 16 ++++++ .../passes/exporter/cqasmv1_exporter.py | 55 +++++++++++++++---- test/exporter/test_cqasmv1_exporter.py | 50 +++++++++++++++++ 5 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 48c1fde6..62ffdefa 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: with: python-version: "3.11" - name: Install poetry - uses: abatilo/actions-poetry@v4 + uses: abatilo/actions-poetry@v3 with: poetry-version: "1.8.3" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ec54ca23..81f159b8 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -17,7 +17,7 @@ jobs: with: python-version: "3.11" - name: Install poetry - uses: abatilo/actions-poetry@v4 + uses: abatilo/actions-poetry@v3 with: poetry-version: "1.3.2" - name: Install tox @@ -47,7 +47,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install poetry - uses: abatilo/actions-poetry@v4 + uses: abatilo/actions-poetry@v3 with: poetry-version: "1.3.2" - name: Install tox diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..ffdfdcd8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +### Types of changes: +* **Added** for new features. +* **Changed** for changes in existing functionality. +* **Fixed** for any bug fixes. +* **Removed** for now removed features. + + +## [ 0.2.0 ] - [ xxxx-yy-zz ] + +### Added +- Restore SGMQ notation for barrier groups in cQASMv1 Exporter. diff --git a/opensquirrel/passes/exporter/cqasmv1_exporter.py b/opensquirrel/passes/exporter/cqasmv1_exporter.py index 8b8472fb..bb25d7c8 100644 --- a/opensquirrel/passes/exporter/cqasmv1_exporter.py +++ b/opensquirrel/passes/exporter/cqasmv1_exporter.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re from typing import TYPE_CHECKING, SupportsFloat, SupportsInt from opensquirrel.exceptions import UnsupportedGateError @@ -10,6 +11,10 @@ from opensquirrel.register_manager import RegisterManager +class CqasmV1ExporterParseError(Exception): + pass + + class _CQASMv1Creator(IRVisitor): # Precision used when writing out a float number FLOAT_PRECISION = 8 @@ -17,9 +22,7 @@ class _CQASMv1Creator(IRVisitor): def __init__(self, register_manager: RegisterManager) -> None: self.register_manager = register_manager qubit_register_size = self.register_manager.get_qubit_register_size() - self.cqasmv1_string = "version 1.0\n\n{}\n\n".format( - f"qubits {qubit_register_size}" if qubit_register_size > 0 else "" - ) + self.output = "version 1.0\n\n{}\n\n".format(f"qubits {qubit_register_size}" if qubit_register_size > 0 else "") def visit_qubit(self, qubit: Qubit) -> str: qubit_register_name = self.register_manager.get_qubit_register_name() @@ -35,24 +38,24 @@ def visit_float(self, f: SupportsFloat) -> str: def visit_measure(self, measure: Measure) -> None: qubit_argument = measure.arguments[0].accept(self) # type: ignore[index] - self.cqasmv1_string += f"{measure.name}_z {qubit_argument}\n" + self.output += f"{measure.name}_z {qubit_argument}\n" def visit_init(self, init: Init) -> None: qubit_argument = init.arguments[0].accept(self) # type: ignore[index] - self.cqasmv1_string += f"prep_z {qubit_argument}\n" + self.output += f"prep_z {qubit_argument}\n" def visit_reset(self, reset: Reset) -> None: qubit_argument = reset.arguments[0].accept(self) # type: ignore[index] - self.cqasmv1_string += f"prep_z {qubit_argument}\n" + self.output += f"prep_z {qubit_argument}\n" def visit_barrier(self, barrier: Barrier) -> None: qubit_argument = barrier.arguments[0].accept(self) # type: ignore[index] - self.cqasmv1_string += f"barrier {qubit_argument}\n" + self.output += f"barrier {qubit_argument}\n" def visit_wait(self, wait: Wait) -> None: qubit_argument = wait.arguments[0].accept(self) # type: ignore[index] parameter = wait.arguments[1].accept(self) # type: ignore[index] - self.cqasmv1_string += f"wait {qubit_argument}, {parameter}\n" + self.output += f"wait {qubit_argument}, {parameter}\n" def visit_gate(self, gate: Gate) -> None: gate_name = gate.name.lower() @@ -62,9 +65,37 @@ def visit_gate(self, gate: Gate) -> None: if any(not isinstance(arg, Qubit) for arg in gate.arguments): # type: ignore[union-attr] params = [arg.accept(self) for arg in gate.arguments if not isinstance(arg, Qubit)] # type: ignore[union-attr] qubit_args = (arg.accept(self) for arg in gate.arguments if isinstance(arg, Qubit)) # type: ignore[union-attr] - self.cqasmv1_string += "{} {}{}\n".format( - gate_name, ", ".join(qubit_args), ", " + ", ".join(params) if params else "" - ) + self.output += "{} {}{}\n".format(gate_name, ", ".join(qubit_args), ", " + ", ".join(params) if params else "") + + +def post_process(output: str) -> str: + return _merge_barrier_groups(output) + + +def _merge_barrier_groups(output: str) -> str: + ret: str = "" + barrier_group_indices: list[int] = [] + for line in output.split("\n"): + if not line.startswith("barrier"): + if barrier_group_indices: + ret += _dump_barrier_group(barrier_group_indices) + barrier_group_indices = [] + ret += f"{line}\n" + else: + barrier_group_indices.append(_get_barrier_index(line)) + return ret + + +def _dump_barrier_group(indices: list[int]) -> str: + return "barrier q[{}]\n".format(", ".join(map(str, indices)) if len(indices) != 0 else "") + + +def _get_barrier_index(line: str) -> int: + barrier_index_match = re.search("\d+", line) + if not barrier_index_match: + msg = "expecting a barrier index but found none" + raise CqasmV1ExporterParseError(msg) + return int(barrier_index_match.group(0)) def export(circuit: Circuit) -> str: @@ -73,4 +104,4 @@ def export(circuit: Circuit) -> str: circuit.ir.accept(cqasmv1_creator) # Remove all trailing lines and leave only one - return cqasmv1_creator.cqasmv1_string.rstrip() + "\n" + return post_process(cqasmv1_creator.output).rstrip() + "\n" diff --git a/test/exporter/test_cqasmv1_exporter.py b/test/exporter/test_cqasmv1_exporter.py index 3010c3c3..a1fca593 100644 --- a/test/exporter/test_cqasmv1_exporter.py +++ b/test/exporter/test_cqasmv1_exporter.py @@ -236,3 +236,53 @@ def test_anonymous_gates(gate: Gate) -> None: with pytest.raises(UnsupportedGateError, match="not supported"): # noqa: PT012 qc = builder.to_circuit() qc.export(fmt=ExportFormat.CQASM_V1) + + +@pytest.mark.parametrize( + ("program", "expected_output"), + [ + ( + "version 3.0; qubit[1] q; barrier q[0];", + "version 1.0\n\nqubits 1\n\nbarrier q[0]\n", + ), + ( + "version 3.0; qubit[3] q; barrier q[0:2]", + "version 1.0\n\nqubits 3\n\nbarrier q[0, 1, 2]\n", + ), + ( + "version 3.0; qubit[1] q; barrier q[0, 0]", + "version 1.0\n\nqubits 1\n\nbarrier q[0, 0]\n", + ), + ( + "version 3.0; qubit[6] q; barrier q[0:2, 5, 3, 4, 1]", + "version 1.0\n\nqubits 6\n\nbarrier q[0, 1, 2, 5, 3, 4, 1]\n", + ), + ( + "version 3.0; qubit[5] q; barrier q[0]; H q[1]; barrier q[1:2]; X q[2]; barrier q[3:4, 1]; Y q[3];" + "barrier q[0]; barrier q[2]", + "version 1.0\n\nqubits 5\n\nbarrier q[0]\nh q[1]\nbarrier q[1, 2]\nx q[2]\nbarrier q[3, 4, 1]\ny q[3]\n" + "barrier q[0, 2]\n", + ), + ( + "version 3.0; qubit[3] q; barrier q[0]; barrier q[1]; X q[2]; barrier q[1]; X q[2]", + "version 1.0\n\nqubits 3\n\nbarrier q[0, 1]\nx q[2]\nbarrier q[1]\nx q[2]\n", + ), + ( + "version 3.0; qubit[20] q; barrier q[14]; barrier q[9:12]; X q[2]; barrier q[19]", + "version 1.0\n\nqubits 20\n\nbarrier q[14, 9, 10, 11, 12]\nx q[2]\nbarrier q[19]\n", + ), + ], + ids=[ + "no_group", + "single_group", + "repeated_index", + "preserve_order", + "with_instructions", + "group_consecutive_barriers", + "double_digit_register_size", + ], +) +def test_barrier_groups(program: str, expected_output: str) -> None: + qc = Circuit.from_string(program) + output = qc.export(fmt=ExportFormat.CQASM_V1) + assert output == expected_output