From 8488a58bcd37a81b75eabf962ca06ce352849baf Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Thu, 27 Aug 2026 12:16:44 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Add=20QSD=20and=20MGD=20synthes?= =?UTF-8?q?is=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonio Tudisco Assisted-by: GPT-5.6 via Codex Signed-off-by: flowerthrower --- CHANGELOG.md | 3 +++ UPGRADING.md | 3 ++- src/mqt/predictor/rl/actions/bqskit_actions.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad5762d16..342241fcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ releases may include breaking changes. ### Added +- ✨ Add BQSKit's `QSDPass` and `MGDPass` to the RL synthesis actions ([#795]) + ([**@flowerthrower**]) - ✨ Add Qiskit's `TrivialLayout`, `ElidePermutations`, `SabreSwap`, `BasicSwap`, `LookaheadSwap`, `RemoveIdentityEquivalent`, and `Optimize1qGatesSimpleCommutation` passes and the optional IBM-backed @@ -100,6 +102,7 @@ for previous changelogs._ [#773]: https://github.com/munich-quantum-toolkit/predictor/pull/771 [#769]: https://github.com/munich-quantum-toolkit/predictor/pull/769 +[#795]: https://github.com/munich-quantum-toolkit/predictor/pull/795 [#794]: https://github.com/munich-quantum-toolkit/predictor/pull/794 [#758]: https://github.com/munich-quantum-toolkit/predictor/pull/758 [#755]: https://github.com/munich-quantum-toolkit/predictor/pull/755 diff --git a/UPGRADING.md b/UPGRADING.md index 1965cc466..dd9282c8c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -67,7 +67,8 @@ The new actions include: - the `GeneralizedSabreRoutingPass` routing action; - the `BQSKitSABREMapping` mapping action; and - the `QSearchSynthesisPass`, `LEAPSynthesisPass`, `WalshDiagonalSynthesisPass`, - `FullQSDPass`, `BlockZXZPass`, and `FullBlockZXZPass` synthesis actions. + `QSDPass`, `MGDPass`, `FullQSDPass`, `BlockZXZPass`, and `FullBlockZXZPass` + synthesis actions. See the [framework setup](docs/setup.md#step-2-train-reinforcement-learning-models) for diff --git a/src/mqt/predictor/rl/actions/bqskit_actions.py b/src/mqt/predictor/rl/actions/bqskit_actions.py index 0d78632cc..087d0fc75 100644 --- a/src/mqt/predictor/rl/actions/bqskit_actions.py +++ b/src/mqt/predictor/rl/actions/bqskit_actions.py @@ -39,6 +39,8 @@ IfThenElsePass, LEAPSynthesisPass, ManyQuditGatesPredicate, + MGDPass, + QSDPass, QSearchSynthesisPass, RestoreMeasurements, SetModelPass, @@ -309,6 +311,21 @@ def bqskit_synthesis_actions() -> list[Action]: IfThenElsePass(DiagonalPredicate(1e-9), WalshDiagonalSynthesisPass()), ), ), + DeferredDeviceAction( + "QSDPass", + CompilationOrigin.BQSKIT, + PassType.SYNTHESIS, + transpile_pass=lambda device: _bqskit_partitioned_synthesis_factory( + device, + QSDPass(min_qudit_size=_BQSKIT_BLOCK_SIZE - 1), + ), + ), + DeferredDeviceAction( + "MGDPass", + CompilationOrigin.BQSKIT, + PassType.SYNTHESIS, + transpile_pass=lambda device: _bqskit_partitioned_synthesis_factory(device, MGDPass()), + ), DeferredDeviceAction( "FullQSDPass", CompilationOrigin.BQSKIT, From 51bf4430fd773de97318d6a2705c0962bd740e5b Mon Sep 17 00:00:00 2001 From: flowerthrower Date: Fri, 28 Aug 2026 10:41:04 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20Make=20BQSKit=20QSD=20action?= =?UTF-8?q?=20effective?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: flowerthrower Assisted-by: GPT 5.6 via Codex --- CHANGELOG.md | 2 +- UPGRADING.md | 4 +-- .../predictor/rl/actions/bqskit_actions.py | 33 +++++++++++-------- .../test_integration_further_SDKs.py | 21 +++++++++++- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 342241fcf..e4a70a251 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ releases may include breaking changes. ### Added -- ✨ Add BQSKit's `QSDPass` and `MGDPass` to the RL synthesis actions ([#795]) +- ✨ Add BQSKit's `QSDPass` to the RL synthesis actions ([#795]) ([**@flowerthrower**]) - ✨ Add Qiskit's `TrivialLayout`, `ElidePermutations`, `SabreSwap`, `BasicSwap`, `LookaheadSwap`, `RemoveIdentityEquivalent`, and diff --git a/UPGRADING.md b/UPGRADING.md index dd9282c8c..5a147ea78 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -67,8 +67,8 @@ The new actions include: - the `GeneralizedSabreRoutingPass` routing action; - the `BQSKitSABREMapping` mapping action; and - the `QSearchSynthesisPass`, `LEAPSynthesisPass`, `WalshDiagonalSynthesisPass`, - `QSDPass`, `MGDPass`, `FullQSDPass`, `BlockZXZPass`, and `FullBlockZXZPass` - synthesis actions. + `QSDPass`, `FullQSDPass`, `BlockZXZPass`, and `FullBlockZXZPass` synthesis + actions. See the [framework setup](docs/setup.md#step-2-train-reinforcement-learning-models) for diff --git a/src/mqt/predictor/rl/actions/bqskit_actions.py b/src/mqt/predictor/rl/actions/bqskit_actions.py index 087d0fc75..ea2bbc41c 100644 --- a/src/mqt/predictor/rl/actions/bqskit_actions.py +++ b/src/mqt/predictor/rl/actions/bqskit_actions.py @@ -15,7 +15,7 @@ from functools import cache from typing import TYPE_CHECKING, TypeAlias, cast -from bqskit import MachineModel +from bqskit import Circuit, MachineModel from bqskit.compiler import Compiler, Workflow from bqskit.compiler.compile import ( build_multi_qudit_retarget_workflow, @@ -39,13 +39,13 @@ IfThenElsePass, LEAPSynthesisPass, ManyQuditGatesPredicate, - MGDPass, QSDPass, QSearchSynthesisPass, RestoreMeasurements, SetModelPass, SetRandomSeedPass, StaticPlacementPass, + SynthesisPass, TrivialPlacementPass, UnfoldPass, WalshDiagonalSynthesisPass, @@ -61,11 +61,11 @@ if TYPE_CHECKING: from collections.abc import Callable - from bqskit import Circuit from bqskit.compiler.basepass import BasePass from bqskit.compiler.passdata import PassData from bqskit.compiler.workflow import WorkflowLike from bqskit.ir import Gate + from bqskit.qis import StateSystem, StateVector, UnitaryMatrix from qiskit import QuantumCircuit from qiskit.circuit import Qubit as QiskitQubit from qiskit.transpiler import Target @@ -83,6 +83,22 @@ _BQSKIT_NUM_WORKERS = 1 if os.getenv("GITHUB_ACTIONS") == "true" else -1 +class _QSDUnitarySynthesisPass(SynthesisPass): + """Apply one QSD level to each multi-qubit synthesis target.""" + + async def synthesize( + self, + target: UnitaryMatrix | StateVector | StateSystem, + data: PassData, + ) -> Circuit: + """Synthesize a partition target with one QSD level.""" + del data + unitary = cast("UnitaryMatrix", target) + if unitary.num_qudits == 1: + return Circuit.from_unitary(unitary) + return QSDPass.qsd(unitary) + + def _r_gate(theta: float, phi: float) -> Instruction: """Construct an RGate with the given parameters.""" return RGate(theta, phi) @@ -315,16 +331,7 @@ def bqskit_synthesis_actions() -> list[Action]: "QSDPass", CompilationOrigin.BQSKIT, PassType.SYNTHESIS, - transpile_pass=lambda device: _bqskit_partitioned_synthesis_factory( - device, - QSDPass(min_qudit_size=_BQSKIT_BLOCK_SIZE - 1), - ), - ), - DeferredDeviceAction( - "MGDPass", - CompilationOrigin.BQSKIT, - PassType.SYNTHESIS, - transpile_pass=lambda device: _bqskit_partitioned_synthesis_factory(device, MGDPass()), + transpile_pass=lambda device: _bqskit_partitioned_synthesis_factory(device, _QSDUnitarySynthesisPass()), ), DeferredDeviceAction( "FullQSDPass", diff --git a/tests/compilation/test_integration_further_SDKs.py b/tests/compilation/test_integration_further_SDKs.py index 017794e7b..e29fdc276 100644 --- a/tests/compilation/test_integration_further_SDKs.py +++ b/tests/compilation/test_integration_further_SDKs.py @@ -10,9 +10,12 @@ from __future__ import annotations +import asyncio from typing import TYPE_CHECKING import pytest +from bqskit.compiler.passdata import PassData +from bqskit.ir.gates import MPRYGate, MPRZGate, VariableUnitaryGate from mqt.bench.targets import get_device from qiskit import QuantumCircuit from qiskit.circuit import StandardEquivalenceLibrary @@ -26,7 +29,7 @@ TrivialLayout, ) -from mqt.predictor.rl.actions import CompilationOrigin, PassType +from mqt.predictor.rl.actions import CompilationOrigin, PassType, bqskit_actions from mqt.predictor.rl.predictorenv import PredictorEnv if TYPE_CHECKING: @@ -126,6 +129,22 @@ def env(target: Target) -> PredictorEnv: return PredictorEnv(device=target, reward_function="expected_fidelity") +@pytest.mark.filterwarnings("ignore:__array__ implementation doesn't accept a copy keyword:DeprecationWarning") +def test_qsd_unitary_synthesis_pass_applies_one_qsd_level(simple_circuit: QuantumCircuit) -> None: + """The QSD adapter performs one equivalent decomposition of a reachable partition target.""" + bqskit_circuit = bqskit_actions.qiskit_to_bqskit(simple_circuit) + unitary = bqskit_circuit.get_unitary() + synthesis_pass = bqskit_actions._QSDUnitarySynthesisPass() # ruff: ignore[private-member-access] + + decomposed = asyncio.run(synthesis_pass.synthesize(unitary, PassData(bqskit_circuit))) + + assert decomposed.get_unitary().get_distance_from(unitary) < 1e-7 + assert sum(count for gate, count in decomposed.gate_counts.items() if isinstance(gate, VariableUnitaryGate)) == 4 + assert all(gate.num_qudits == 2 for gate in decomposed.gate_set if isinstance(gate, VariableUnitaryGate)) + assert sum(count for gate, count in decomposed.gate_counts.items() if isinstance(gate, MPRZGate)) == 2 + assert sum(count for gate, count in decomposed.gate_counts.items() if isinstance(gate, MPRYGate)) == 1 + + def test_synthesis_actions_produce_native_gates( simple_circuit: QuantumCircuit, env: PredictorEnv,