From 3be6d8e4323cedeb96ecf6c372ca1bf60bb99758 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 29 Jul 2026 12:06:44 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20Derive=20native-gates=20menus?= =?UTF-8?q?=20from=20device=20operation=20names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add NativeGateset::fromOperationNames / toMenuString and Python helpers so FoMaC/QDMI devices can produce menus for fuse-two-qubit-unitary-runs. --- CHANGELOG.md | 2 + bindings/mlir/register_mlir.cpp | 44 +++++++ .../Transforms/Decomposition/NativeGateset.h | 22 ++++ .../Decomposition/NativeGateset.cpp | 117 ++++++++++++++++++ .../Decomposition/test_weyl_decomposition.cpp | 51 ++++++++ python/mqt/core/mlir.pyi | 16 +++ test/python/test_native_gates_from_device.py | 60 +++++++++ 7 files changed, 312 insertions(+) create mode 100644 test/python/test_native_gates_from_device.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b3dc1c4e08..7a2f58c36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ releases may include breaking changes. ### Added +- ✨ Derive a native-gates menu from device/backend operation names + (`NativeGateset::fromOperationNames`) ([**@simon1hofmann**]) - ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) ([**@burgholzer**]) - ✨ Add versioned, relocatable configuration and stable-ID registration for diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 34e807eefa..56632be01f 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -10,7 +10,10 @@ #include "ir/QuantumComputation.hpp" #include "mlir/Compiler/Programs.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" +#include +#include #include #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) @@ -237,6 +240,31 @@ compileProgram(const nb::object& program, const mlir::ProgramFormat output, enableStatistics)); } +[[nodiscard]] std::string +nativeGatesMenuOrThrow(const std::vector& names) { + llvm::SmallVector refs; + refs.reserve(names.size()); + for (const auto& name : names) { + refs.emplace_back(name); + } + const auto gateset = + mlir::qco::decomposition::NativeGateset::fromOperationNames(refs); + if (!gateset) { + throw nb::value_error( + "cannot derive a supported native-gates menu from the given " + "operation names"); + } + return gateset->toMenuString(); +} + +[[nodiscard]] std::string nativeGatesMenuFromDevice(const nb::object& device) { + std::vector names; + for (const auto& op : device.attr("operations")()) { + names.push_back(nb::cast(nb::handle(op).attr("name")())); + } + return nativeGatesMenuOrThrow(names); +} + } // namespace NB_MODULE(MQT_CORE_MODULE_NAME, m) { @@ -513,6 +541,22 @@ LLVM bitcode.)pb"); &BooleanMemberAdapter<&mlir::QIRProgram::writeBitcode>::call, "path"_a, "Write this program as LLVM bitcode."); + m.def("native_gates_from_operation_names", &nativeGatesMenuOrThrow, "names"_a, + "Derive a comma-separated native-gates menu from operation name " + "strings."); + + m.def("native_gates_from_device", &nativeGatesMenuFromDevice, "device"_a, + R"pb(Derive a comma-separated native-gates menu from a FoMaC device. + +Args: + device: A FoMaC device exposing ``operations()`` with ``name()``. + +Returns: + Comma-separated native gate menu string. + +Raises: + ValueError: When no supported menu can be derived.)pb"); + m.def("compile_program", &compileProgram, "program"_a, nb::kw_only(), "output"_a = mlir::ProgramFormat::QC, "inplace"_a = false, "qco_pipeline"_a = "mqt-qco-default", "enable_timing"_a = false, diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h index bb48a23383..38cfc6112c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h @@ -13,10 +13,12 @@ #include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" +#include #include #include #include +#include namespace mlir { class Operation; @@ -70,6 +72,26 @@ struct NativeGateset { [[nodiscard]] static std::optional parse(StringRef nativeGates); + /** + * @brief Builds a gateset from device/backend operation names. + * + * Normalizes known aliases, ignores unrecognized names, and resolves the + * Euler basis and entangler with the same priority as @ref parse. The + * resulting @p gates set contains only the selected strategy tokens. + * + * @return Resolved gateset, or `std::nullopt` when no supported menu exists. + */ + [[nodiscard]] static std::optional + fromOperationNames(llvm::ArrayRef names); + + /** + * @brief Comma-separated menu for the selected Euler factors and entangler. + * + * Token order is deterministic (Euler constituents, then entangler), e.g. + * `"x,sx,rz,cz"` or `"u,cx"`. + */ + [[nodiscard]] std::string toMenuString() const; + /** * @brief Basis decomposition of @p target under this gateset, if supported. */ diff --git a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp index 74a0dd9cd7..b9e416768a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp @@ -24,6 +24,7 @@ #include #include +#include #include namespace mlir::qco::decomposition { @@ -271,4 +272,120 @@ std::optional NativeGateset::parse(StringRef nativeGates) { }; } +static StringRef normalizeGateAlias(StringRef token) { + token = token.trim(); + if (token.equals_insensitive("prx")) { + return "r"; + } + if (token.equals_insensitive("u3")) { + return "u"; + } + if (token.equals_insensitive("cnot")) { + return "cx"; + } + return token; +} + +static void insertEulerConstituents(DenseSet& selected, + EulerBasis euler) { + switch (euler) { + case EulerBasis::U: + selected.insert(NativeGateKind::U); + break; + case EulerBasis::ZSXX: + selected.insert(NativeGateKind::X); + selected.insert(NativeGateKind::SX); + selected.insert(NativeGateKind::RZ); + break; + case EulerBasis::R: + selected.insert(NativeGateKind::R); + break; + case EulerBasis::XZX: + selected.insert(NativeGateKind::RX); + selected.insert(NativeGateKind::RZ); + break; + case EulerBasis::XYX: + selected.insert(NativeGateKind::RX); + selected.insert(NativeGateKind::RY); + break; + case EulerBasis::ZYZ: + selected.insert(NativeGateKind::RY); + selected.insert(NativeGateKind::RZ); + break; + } +} + +std::optional +NativeGateset::fromOperationNames(ArrayRef names) { + DenseSet recognized; + for (StringRef name : names) { + std::string lowered = name.trim().lower(); + if (lowered.empty()) { + continue; + } + const StringRef token = normalizeGateAlias(lowered); + const auto gate = parseGateToken(token); + if (gate) { + recognized.insert(*gate); + } + } + const auto euler = resolveEulerBasis(recognized); + const auto entangler = selectEntangler(recognized); + if (!euler || !entangler) { + return std::nullopt; + } + DenseSet selected; + insertEulerConstituents(selected, *euler); + selected.insert(*entangler); + return NativeGateset{ + .gates = std::move(selected), + .eulerBasis = euler, + .entangler = entangler, + }; +} + +std::string NativeGateset::toMenuString() const { + if (!eulerBasis || !entangler) { + return {}; + } + std::string out; + auto append = [&](StringRef tok) { + if (!out.empty()) { + out.push_back(','); + } + out.append(tok.str()); + }; + switch (*eulerBasis) { + case EulerBasis::U: + append("u"); + break; + case EulerBasis::ZSXX: + append("x"); + append("sx"); + append("rz"); + break; + case EulerBasis::R: + append("r"); + break; + case EulerBasis::XZX: + append("rx"); + append("rz"); + break; + case EulerBasis::XYX: + append("rx"); + append("ry"); + break; + case EulerBasis::ZYZ: + append("ry"); + append("rz"); + break; + } + if (*entangler == NativeGateKind::CZ) { + append("cz"); + } else { + append("cx"); + } + return out; +} + } // namespace mlir::qco::decomposition diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index e1c5b6f600..2307f9f234 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -750,6 +750,57 @@ TEST(NativeSpecTest, RejectsGatesetWithoutSingleQubitStrategy) { EXPECT_FALSE(NativeGateset::parse("rx,cx").has_value()); } +TEST(NativeGatesetFromNamesTest, DerivesIbmLikeMenu) { + const SmallVector names = {"x", "sx", "rz", "cx", "h", "measure"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::ZSXX); + EXPECT_EQ(gs->entangler, NativeGateKind::CX); + EXPECT_EQ(gs->toMenuString(), "x,sx,rz,cx"); +} + +TEST(NativeGatesetFromNamesTest, PrefersCzAndMapsPrxAlias) { + const SmallVector names = {"prx", "cz", "cx"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::R); + EXPECT_EQ(gs->entangler, NativeGateKind::CZ); + EXPECT_EQ(gs->toMenuString(), "r,cz"); +} + +TEST(NativeGatesetFromNamesTest, PrefersUAndCz) { + const SmallVector names = {"u", "u3", "cx", "cz"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::U); + EXPECT_EQ(gs->entangler, NativeGateKind::CZ); + EXPECT_EQ(gs->toMenuString(), "u,cz"); +} + +TEST(NativeGatesetFromNamesTest, RotationPairXzx) { + const SmallVector names = {"rx", "rz", "cx"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::XZX); + EXPECT_EQ(gs->toMenuString(), "rx,rz,cx"); +} + +TEST(NativeGatesetFromNamesTest, RejectsInsufficientMenus) { + EXPECT_FALSE(NativeGateset::fromOperationNames( + SmallVector{"h", "measure"})); + EXPECT_FALSE(NativeGateset::fromOperationNames(SmallVector{"cx"})); + EXPECT_FALSE( + NativeGateset::fromOperationNames(SmallVector{"cnot"})); +} + +TEST(NativeGatesetFromNamesTest, MapsCnotAlias) { + const SmallVector names = {"u", "cnot"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->entangler, NativeGateKind::CX); + EXPECT_EQ(gs->toMenuString(), "u,cx"); +} + TEST(NativeSpecTest, ResolvesEulerBasisFromGateset) { const auto uGateset = NativeGateset::parse("u,cx"); ASSERT_TRUE(uGateset); diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 79093f057e..20d4a793a7 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -241,6 +241,22 @@ class QIRProgram(Program): def write_bitcode(self, path: str | os.PathLike) -> None: """Write this program as LLVM bitcode.""" +def native_gates_from_operation_names(names: Sequence[str]) -> str: + """Derive a comma-separated native-gates menu from operation name strings.""" + +def native_gates_from_device(device: object) -> str: + """Derive a comma-separated native-gates menu from a FoMaC device. + + Args: + device: A FoMaC device exposing ``operations()`` with ``name()``. + + Returns: + Comma-separated native gate menu string. + + Raises: + ValueError: When no supported menu can be derived. + """ + @overload def compile_program( program: str diff --git a/test/python/test_native_gates_from_device.py b/test/python/test_native_gates_from_device.py new file mode 100644 index 0000000000..fffe7efdfa --- /dev/null +++ b/test/python/test_native_gates_from_device.py @@ -0,0 +1,60 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for deriving native gate menus from device operation names.""" + +from __future__ import annotations + +import pytest +from plugins.qiskit.test_mock_backend import MockQDMIDevice + +from mqt.core.mlir import ( + native_gates_from_device, + native_gates_from_operation_names, +) + + +@pytest.fixture +def mock_qdmi_device_factory() -> type[MockQDMIDevice]: + """Return the mock QDMI device class for parameterized device tests.""" + return MockQDMIDevice + + +def test_native_gates_from_operation_names_ibm_like() -> None: + """Map an IBM-like op list to an x/sx/rz/cx menu.""" + assert native_gates_from_operation_names(["x", "sx", "rz", "cx", "h", "measure"]) == "x,sx,rz,cx" + + +def test_native_gates_from_operation_names_iqm_prx() -> None: + """Alias prx to r and prefer cz.""" + assert native_gates_from_operation_names(["prx", "cz"]) == "r,cz" + + +def test_native_gates_from_operation_names_rejects_insufficient() -> None: + """Reject name lists that lack a supported Euler + entangler pair.""" + with pytest.raises(ValueError, match="native-gates"): + native_gates_from_operation_names(["h", "measure"]) + + +def test_native_gates_from_device_ibm_like(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Derive an IBM-like menu from a FoMaC-style device.""" + device = mock_qdmi_device_factory(operations=["x", "sx", "rz", "cx", "h", "measure"]) + assert native_gates_from_device(device) == "x,sx,rz,cx" + + +def test_native_gates_from_device_iqm_prx(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Derive an IQM-like prx/cz menu from a FoMaC-style device.""" + device = mock_qdmi_device_factory(operations=["prx", "cz"]) + assert native_gates_from_device(device) == "r,cz" + + +def test_native_gates_from_device_rejects_insufficient(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None: + """Raise when a device exposes no supported native menu.""" + device = mock_qdmi_device_factory(operations=["h", "measure"]) + with pytest.raises(ValueError, match="native-gates"): + native_gates_from_device(device) From 1aa4c975bb2ebac16e5a736a9c7c70d73a048a14 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 29 Jul 2026 12:57:18 +0200 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20Add=20progressive=20backend?= =?UTF-8?q?=20targeting=20for=20native=20menus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire decompose → optional place/route → fuse via QCOProgram::targetBackend, Python target_backend/target_device, and mqt-cc --coupling-map. --- CHANGELOG.md | 3 + bindings/mlir/register_mlir.cpp | 47 ++++++++ bindings/patterns.txt | 9 ++ mlir/include/mlir/Compiler/Programs.h | 7 ++ mlir/lib/Compiler/Programs.cpp | 16 +++ mlir/tools/mqt-cc/CMakeLists.txt | 42 +++++++ mlir/tools/mqt-cc/mqt-cc.cpp | 68 +++++++++++ ...k-coupling-map-requires-native-gates.cmake | 23 ++++ .../tests/check-native-gates-empty.cmake | 26 +++++ .../tests/check-native-gates-raw-qco.cmake | 27 +++++ mlir/tools/mqt-cc/tests/coupling-line.qasm | 11 ++ mlir/tools/mqt-cc/tests/native-gates.qasm | 13 +++ .../Compiler/test_compiler_pipeline.cpp | 109 ++++++++++++++++++ python/mqt/core/mlir.pyi | 11 ++ test/python/test_target_backend.py | 56 +++++++++ 15 files changed, 468 insertions(+) create mode 100644 mlir/tools/mqt-cc/tests/check-coupling-map-requires-native-gates.cmake create mode 100644 mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake create mode 100644 mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake create mode 100644 mlir/tools/mqt-cc/tests/coupling-line.qasm create mode 100644 mlir/tools/mqt-cc/tests/native-gates.qasm create mode 100644 test/python/test_target_backend.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a2f58c36d..bfda1c3df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ releases may include breaking changes. - ✨ Derive a native-gates menu from device/backend operation names (`NativeGateset::fromOperationNames`) ([**@simon1hofmann**]) +- ✨ Add progressive backend targeting (`QCOProgram::targetBackend` / Python + `target_backend` / `target_device`, `mqt-cc --coupling-map`) + ([**@simon1hofmann**]) - ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) ([**@burgholzer**]) - ✨ Add versioned, relocatable configuration and stable-ID registration for diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 56632be01f..b4eea54bb4 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -265,6 +265,25 @@ nativeGatesMenuOrThrow(const std::vector& names) { return nativeGatesMenuOrThrow(names); } +[[nodiscard]] std::vector> +couplingFromDevice(const nb::object& device) { + std::vector> edges; + const nb::object cmap = device.attr("coupling_map")(); + if (cmap.is_none()) { + return edges; + } + for (const auto& pair : cmap) { + const auto edge = nb::cast(nb::handle(pair)); + const auto a = + static_cast(nb::cast(edge[0].attr("index")())); + const auto b = + static_cast(nb::cast(edge[1].attr("index")())); + edges.emplace_back(a, b); + edges.emplace_back(b, a); + } + return edges; +} + } // namespace NB_MODULE(MQT_CORE_MODULE_NAME, m) { @@ -449,6 +468,34 @@ operations.)pb"); "coupling"_a, nb::kw_only(), "nlookahead"_a = 1, "alpha"_a = 1.F, "lambda_"_a = 0.5F, "niterations"_a = 1, "ntrials"_a = 4, "seed"_a = 42, "Place and route the program for a coupling graph.") + .def( + "target_backend", + [](mlir::QCOProgram& value, const std::string& nativeGates, + const nb::object& coupling) { + if (coupling.is_none()) { + requireSuccess(value.targetBackend(nativeGates)); + } else { + const auto edges = + nb::cast>>( + coupling); + requireSuccess( + value.targetBackend(nativeGates, std::span(edges))); + } + }, + nb::kw_only(), "native_gates"_a, "coupling"_a = nb::none(), + "Decompose multi-controlled gates, optionally place/route, then fuse " + "to " + "native_gates.") + .def( + "target_device", + [](mlir::QCOProgram& value, const nb::object& device) { + const auto menu = nativeGatesMenuFromDevice(device); + const auto coupling = couplingFromDevice(device); + requireSuccess(value.targetBackend(menu, std::span(coupling))); + }, + "device"_a, + "Target a FoMaC device: derive native menu and coupling, then run " + "target_backend.") .def( "to_qc", [](mlir::QCOProgram& value, const bool copy) { diff --git a/bindings/patterns.txt b/bindings/patterns.txt index 5ee2e8895e..0e4a98dd8b 100644 --- a/bindings/patterns.txt +++ b/bindings/patterns.txt @@ -169,3 +169,12 @@ mqt.core.mlir.compile_program: enable_statistics: bool = False, ) -> QCProgram | QCOProgram | JeffProgram | QIRProgram: \doc + +mqt.core.mlir.QCOProgram.target_backend: + def target_backend( + self, + *, + native_gates: str, + coupling: Sequence[tuple[int, int]] | None = None, + ) -> None: + \doc diff --git a/mlir/include/mlir/Compiler/Programs.h b/mlir/include/mlir/Compiler/Programs.h index 4c6bb67a3d..ce8b5f5a44 100644 --- a/mlir/include/mlir/Compiler/Programs.h +++ b/mlir/include/mlir/Compiler/Programs.h @@ -202,6 +202,13 @@ class QCOProgram final : public Program { float lambda = 0.5F, std::size_t niterations = 1, std::size_t ntrials = 4, std::size_t seed = 42); + /// Progressive backend targeting: decompose multi-controlled gates, + /// optionally place/route on @p coupling, then fuse to @p nativeGates + /// (required, non-empty). + [[nodiscard]] bool targetBackend( + std::string_view nativeGates, + std::span> coupling = {}); + /// Consume this program and convert it to QC. [[nodiscard]] std::optional intoQC() &&; diff --git a/mlir/lib/Compiler/Programs.cpp b/mlir/lib/Compiler/Programs.cpp index 5173aca1df..1bdd17541f 100644 --- a/mlir/lib/Compiler/Programs.cpp +++ b/mlir/lib/Compiler/Programs.cpp @@ -413,6 +413,22 @@ bool QCOProgram::placeAndRoute( "failed to place and route the QCO program")); } +bool QCOProgram::targetBackend( + const std::string_view nativeGates, + const std::span> coupling) { + if (StringRef(nativeGates).trim().empty()) { + mod().emitError("the native gate menu must not be empty"); + return false; + } + if (!decomposeMultiControlled(/*minControls=*/2)) { + return false; + } + if (!coupling.empty() && !placeAndRoute(coupling)) { + return false; + } + return fuseTwoQubitUnitaryRuns(nativeGates); +} + std::optional QCOProgram::intoQC() && { if (failed(runPasses( mod(), [](OpPassManager& pm) { pm.addPass(createQCOToQC()); }, diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt index 0a630338ab..36f17ae0ad 100644 --- a/mlir/tools/mqt-cc/CMakeLists.txt +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -27,3 +27,45 @@ mqt_mlir_target_use_project_options(mqt-cc) llvm_update_compile_flags(mqt-cc) mlir_check_all_link_libraries(mqt-cc) export_executable_symbols_for_plugins(mqt-cc) + +if(BUILD_MQT_CORE_TESTS) + set_target_properties(mqt-cc PROPERTIES EXCLUDE_FROM_ALL FALSE) + set(MQT_CC_NATIVE_GATES_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/tests/native-gates.qasm) + + add_test(NAME mqt-cc-native-gates-qco-optimized + COMMAND $ ${MQT_CC_NATIVE_GATES_INPUT} --emit=qco-optimized + --native-gates=u,cx) + set_tests_properties( + mqt-cc-native-gates-qco-optimized + PROPERTIES LABELS mqt-mlir-unittests PASS_REGULAR_EXPRESSION "qco\\.ctrl" + FAIL_REGULAR_EXPRESSION "qco\\.swap|qco\\.u2|qc\\.h") + + add_test( + NAME mqt-cc-native-gates-raw-qco + COMMAND ${CMAKE_COMMAND} -DMQT_CC=$ -DINPUT=${MQT_CC_NATIVE_GATES_INPUT} -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-native-gates-raw-qco.cmake) + set_tests_properties(mqt-cc-native-gates-raw-qco PROPERTIES LABELS mqt-mlir-unittests) + + add_test( + NAME mqt-cc-native-gates-empty + COMMAND ${CMAKE_COMMAND} -DMQT_CC=$ -DINPUT=${MQT_CC_NATIVE_GATES_INPUT} -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-native-gates-empty.cmake) + set_tests_properties(mqt-cc-native-gates-empty PROPERTIES LABELS mqt-mlir-unittests) + + add_test(NAME mqt-cc-coupling-map-qco-optimized + COMMAND $ ${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm + --emit=qco-optimized --native-gates=u,cx --coupling-map=0-1,1-2) + set_tests_properties( + mqt-cc-coupling-map-qco-optimized + PROPERTIES LABELS mqt-mlir-unittests PASS_REGULAR_EXPRESSION "qco\\.ctrl" + FAIL_REGULAR_EXPRESSION "qco\\.swap;qco\\.ctrl\\(%0\\).*targets.*%2") + + add_test( + NAME mqt-cc-coupling-map-requires-native-gates + COMMAND + ${CMAKE_COMMAND} -DMQT_CC=$ + -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-coupling-map-requires-native-gates.cmake) + set_tests_properties(mqt-cc-coupling-map-requires-native-gates PROPERTIES LABELS + mqt-mlir-unittests) +endif() diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 0e31eb7682..1113896fed 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -17,6 +17,7 @@ #include "mlir/Dialect/QC/IR/QCDialect.h" #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" #include "mlir/Support/Passes.h" @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +92,12 @@ static llvm::cl::opt nativeGates( "pass"), llvm::cl::value_desc("csv"), llvm::cl::init("")); +static llvm::cl::opt couplingMap( + "coupling-map", + llvm::cl::desc("Undirected coupling edges as comma-separated pairs 'u-v' " + "(requires --native-gates). Example: 0-1,1-2"), + llvm::cl::value_desc("edges"), llvm::cl::init("")); + namespace { enum class InputFormat : std::uint8_t { MLIR, QASM, Jeff }; enum class InputDialect : std::uint8_t { QC, QCO }; @@ -182,6 +190,38 @@ parseOutputFormat(const StringRef format) { return std::nullopt; } +static LogicalResult +parseCouplingMap(StringRef text, + SmallVectorImpl>& out) { + out.clear(); + text = text.trim(); + if (text.empty()) { + return success(); + } + while (!text.empty()) { + auto [piece, rest] = text.split(','); + text = rest; + piece = piece.trim(); + if (piece.empty()) { + continue; + } + auto [left, right] = piece.split('-'); + left = left.trim(); + right = right.trim(); + size_t a = 0; + size_t b = 0; + if (left.getAsInteger(10, a) || right.getAsInteger(10, b) || left.empty() || + right.empty()) { + llvm::errs() << "invalid --coupling-map entry '" << piece + << "' (expected u-v)\n"; + return failure(); + } + out.emplace_back(a, b); + out.emplace_back(b, a); + } + return success(); +} + static llvm::cl::opt enableDecomposeMultiControlled( "decompose-multi-controlled", llvm::cl::desc( @@ -407,6 +447,25 @@ int main(int argc, char** argv) { "QCO optimization.\n"; return 1; } + SmallVector> couplingEdges; + if (failed(parseCouplingMap(couplingMap.getValue(), couplingEdges))) { + return 1; + } + if (couplingMap.getNumOccurrences() > 0 && couplingEdges.empty()) { + llvm::errs() << "--coupling-map must not be empty.\n"; + return 1; + } + if (!couplingEdges.empty() && nativeGateMenu.empty()) { + llvm::errs() << "--coupling-map requires --native-gates.\n"; + return 1; + } + if (couplingMap.getNumOccurrences() > 0 && + (*parsedOutputFormat == OutputFormat::QCImport || + *parsedOutputFormat == OutputFormat::QCO)) { + llvm::errs() << "--coupling-map requires an output that passes through " + "QCO optimization.\n"; + return 1; + } if (enableDecomposeMultiControlled && !isDecomposeMultiControlledConfigValid( decomposeMultiControlledMinControls.getValue())) { @@ -457,6 +516,15 @@ int main(int argc, char** argv) { } populateQCOCleanupPipeline(pm); if (!nativeGateMenu.empty()) { + if (!enableDecomposeMultiControlled) { + populateDecomposeMultiControlledPipeline(pm, /*minControls=*/2); + } + if (!couplingEdges.empty()) { + DenseSet> couplingSet( + couplingEdges.begin(), couplingEdges.end()); + pm.addPass(qco::createMappingPass(couplingSet, + qco::MappingPassOptions{})); + } pm.addPass(qco::createFuseTwoQubitUnitaryRuns( qco::FuseTwoQubitUnitaryRunsOptions{ .nativeGates = nativeGateMenu.str(), diff --git a/mlir/tools/mqt-cc/tests/check-coupling-map-requires-native-gates.cmake b/mlir/tools/mqt-cc/tests/check-coupling-map-requires-native-gates.cmake new file mode 100644 index 0000000000..6d75321c6a --- /dev/null +++ b/mlir/tools/mqt-cc/tests/check-coupling-map-requires-native-gates.cmake @@ -0,0 +1,23 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(expected "--coupling-map requires --native-gates") + +execute_process( + COMMAND "${MQT_CC}" "${INPUT}" --emit=qco-optimized --coupling-map=0-1 + RESULT_VARIABLE result + ERROR_VARIABLE error) + +if(result EQUAL 0) + message(FATAL_ERROR "mqt-cc accepted --coupling-map without --native-gates") +endif() + +string(FIND "${error}" "${expected}" diagnostic_position) +if(diagnostic_position EQUAL -1) + message(FATAL_ERROR "mqt-cc did not emit the expected diagnostic:\n${error}") +endif() diff --git a/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake b/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake new file mode 100644 index 0000000000..8f90d34047 --- /dev/null +++ b/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake @@ -0,0 +1,26 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(expected "--native-gates must not be empty") + +foreach(native_gates_arg IN ITEMS "--native-gates=" "--native-gates= ") + execute_process( + COMMAND "${MQT_CC}" "${INPUT}" --emit=qco-optimized ${native_gates_arg} + RESULT_VARIABLE result + ERROR_VARIABLE error) + + if(result EQUAL 0) + message(FATAL_ERROR "mqt-cc accepted empty ${native_gates_arg} with --emit=qco-optimized") + endif() + + string(FIND "${error}" "${expected}" diagnostic_position) + if(diagnostic_position EQUAL -1) + message( + FATAL_ERROR "mqt-cc did not emit the expected diagnostic for ${native_gates_arg}:\n${error}") + endif() +endforeach() diff --git a/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake b/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake new file mode 100644 index 0000000000..2f6b9609a1 --- /dev/null +++ b/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake @@ -0,0 +1,27 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(expected "--native-gates requires an output that passes through QCO optimization") + +foreach(emit_format IN ITEMS qco qc-import) + execute_process( + COMMAND "${MQT_CC}" "${INPUT}" --emit=${emit_format} --native-gates=u,cx + RESULT_VARIABLE result + ERROR_VARIABLE error) + + if(result EQUAL 0) + message(FATAL_ERROR "mqt-cc accepted --native-gates with --emit=${emit_format} output") + endif() + + string(FIND "${error}" "${expected}" diagnostic_position) + if(diagnostic_position EQUAL -1) + message( + FATAL_ERROR "mqt-cc did not emit the expected diagnostic for --emit=${emit_format}:\n${error}" + ) + endif() +endforeach() diff --git a/mlir/tools/mqt-cc/tests/coupling-line.qasm b/mlir/tools/mqt-cc/tests/coupling-line.qasm new file mode 100644 index 0000000000..e2a6c622d5 --- /dev/null +++ b/mlir/tools/mqt-cc/tests/coupling-line.qasm @@ -0,0 +1,11 @@ +// Copyright (c) 2026 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +cx q[0], q[2]; diff --git a/mlir/tools/mqt-cc/tests/native-gates.qasm b/mlir/tools/mqt-cc/tests/native-gates.qasm new file mode 100644 index 0000000000..931e9e0bc3 --- /dev/null +++ b/mlir/tools/mqt-cc/tests/native-gates.qasm @@ -0,0 +1,13 @@ +// Copyright (c) 2026 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +OPENQASM 3.0; +include "stdgates.inc"; + +qubit[2] q; +h q[0]; +swap q[0], q[1]; diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 0f4d09072d..e40ef852a9 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -16,8 +16,11 @@ #include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QIR/Builder/QIRProgramBuilder.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" +#include "mlir/Dialect/Utils/Utils.h" #include "mlir/Support/IRVerification.h" #include "mlir/Support/Passes.h" #include "qc_programs.h" @@ -27,6 +30,8 @@ #include #include +#include +#include #include #include #include @@ -61,6 +66,7 @@ using namespace mlir; using namespace mlir::qc; using namespace mlir::qco; using namespace mlir::qir; +using namespace mlir::utils; using QCProgramBuilderFn = NamedMLIRBuilder; using QIRProgramBuilderFn = NamedMLIRBuilder; @@ -68,6 +74,46 @@ using QuantumComputationBuilderFn = NamedBuilder<::qc::QuantumComputation>; namespace { +/// Return true if two-qubit unitaries in a straight-line entry point obey +/// coupling constraints. +static bool isExecutableStraightLine( + func::FuncOp entry, + const DenseSet>& couplingSet) { + DenseMap m; + for (Operation& op : entry.getFunctionBody().getOps()) { + if (auto staticOp = dyn_cast(op)) { + m.try_emplace(staticOp.getQubit(), staticOp.getIndex()); + continue; + } + + if (auto unitaryOp = dyn_cast(op)) { + if (!isa(op) && unitaryOp.getNumQubits() > 1) { + assert(unitaryOp.getNumQubits() <= 2 && "expected two-qubit decomp."); + const auto hwA = m.at(unitaryOp.getInputQubit(0)); + const auto hwB = m.at(unitaryOp.getInputQubit(1)); + if (!couplingSet.contains(std::make_pair(hwA, hwB))) { + return false; + } + } + for (const auto [pred, succ] : llvm::zip_equal( + unitaryOp.getInputQubits(), unitaryOp.getOutputQubits())) { + m.try_emplace(succ, m.at(pred)); + } + continue; + } + + if (auto resetOp = dyn_cast(op)) { + m.try_emplace(resetOp.getQubitOut(), m.at(resetOp.getQubitIn())); + continue; + } + + if (auto measOp = dyn_cast(op)) { + m.try_emplace(measOp.getQubitOut(), m.at(measOp.getQubitIn())); + } + } + return true; +} + struct CompilerPipelineTestCase { std::string name; QuantumComputationBuilderFn quantumComputationBuilder; @@ -454,6 +500,69 @@ cx q[0], q[2]; EXPECT_EQ(loopProgram->str().find("scf.for"), std::string::npos); } +TEST_F(CompilerPipelineTest, TargetBackendMenuOnlyFuses) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[2] q; +h q[0]; +cx q[0], q[1]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + ASSERT_TRUE(qco->cleanup()); + ASSERT_TRUE(qco->targetBackend("u,cx")); + const auto ir = qco->str(); + EXPECT_EQ(ir.find("qco.h"), std::string::npos); + EXPECT_NE(ir.find("qco.u"), std::string::npos); +} + +TEST_F(CompilerPipelineTest, TargetBackendWithCouplingLowersSwaps) { + // CX on (0,2) needs routing on a line 0-1-2. + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +cx q[0], q[2]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + ASSERT_TRUE(qco->cleanup()); + const std::vector> coupling = { + {0, 1}, {1, 0}, {1, 2}, {2, 1}}; + ASSERT_TRUE(qco->targetBackend("u,cx", coupling)); + const auto ir = qco->str(); + EXPECT_EQ(ir.find("qco.swap"), std::string::npos); + EXPECT_NE(ir.find("qco.ctrl"), std::string::npos); + EXPECT_EQ(ir.find("qco.ctrl(%0) targets (%arg0 = %2)"), std::string::npos); + + auto module = parseRecordedModule(ir); + ASSERT_TRUE(module); + const DenseSet> couplingSet(coupling.begin(), + coupling.end()); + EXPECT_TRUE( + isExecutableStraightLine(getEntryPoint(module.get()), couplingSet)); +} + +TEST_F(CompilerPipelineTest, TargetBackendRejectsEmptyMenu) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit q; +h q; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + EXPECT_FALSE(qco->targetBackend("")); + EXPECT_FALSE(qco->targetBackend(" ")); + const std::vector> coupling = {{0, 1}, + {1, 0}}; + EXPECT_FALSE(qco->targetBackend("", coupling)); +} + /** * @brief Test: default compilation returns the requested typed program format */ diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 20d4a793a7..77e1288125 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -169,6 +169,17 @@ class QCOProgram(Program): ) -> None: """Place and route the program for a coupling graph.""" + def target_backend( + self, + *, + native_gates: str, + coupling: Sequence[tuple[int, int]] | None = None, + ) -> None: + """Decompose multi-controlled gates, optionally place/route, then fuse to native_gates.""" + + def target_device(self, device: object) -> None: + """Target a FoMaC device: derive native menu and coupling, then run target_backend.""" + def to_qc(self, *, copy: bool = False) -> QCProgram: """Convert this program to QC. diff --git a/test/python/test_target_backend.py b/test/python/test_target_backend.py new file mode 100644 index 0000000000..24c1013791 --- /dev/null +++ b/test/python/test_target_backend.py @@ -0,0 +1,56 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for progressive QCOProgram.target_backend / target_device.""" + +from __future__ import annotations + +import pytest +from plugins.qiskit.test_mock_backend import MockQDMIDevice + +from mqt.core.ir import QuantumComputation +from mqt.core.mlir import OutputFormat, QCOProgram, compile_program + + +def test_target_backend_menu_only() -> None: + """Menu-only targeting removes H in favor of native u factors.""" + qc = QuantumComputation(2) + qc.h(0) + qc.cx(0, 1) + qco = compile_program(qc, output=OutputFormat.QCO) + assert isinstance(qco, QCOProgram) + qco.target_backend(native_gates="u,cx") + assert "qco.h" not in qco.ir + + +def test_target_device_with_coupling() -> None: + """Device-derived menu+coupling lowers CX(0,2) without leftover swaps.""" + device = MockQDMIDevice( + num_qubits=3, + operations=["u", "cx"], + coupling_map=[(0, 1), (1, 2)], + ) + qc = QuantumComputation(3) + qc.cx(0, 2) + qco = compile_program(qc, output=OutputFormat.QCO) + assert isinstance(qco, QCOProgram) + qco.target_device(device) + assert "qco.swap" not in qco.ir + assert "qco.ctrl" in qco.ir + # Unrouted CX(0,2) would keep static qubits 0 and 2 on the same ctrl. + assert "qco.ctrl(%0) targets (%arg0 = %2)" not in qco.ir + + +def test_target_backend_rejects_empty_menu() -> None: + """Empty native_gates must fail.""" + qc = QuantumComputation(1) + qc.h(0) + qco = compile_program(qc, output=OutputFormat.QCO) + assert isinstance(qco, QCOProgram) + with pytest.raises(RuntimeError, match=r"(?i)fail|empty|native"): + qco.target_backend(native_gates="") From eb4be52eca1894fc53a215f151e504d240ef0e82 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Wed, 29 Jul 2026 15:27:39 +0200 Subject: [PATCH 03/13] =?UTF-8?q?=F0=9F=94=A5=20Drop=20mqt-cc=20native-gat?= =?UTF-8?q?es=20CMake=20tests=20restored=20at=20rebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match main / fuse tip (b5143e73a): native-gates coverage stays in the compiler API and native-synthesis suite; only keep --coupling-map driver tests. --- mlir/tools/mqt-cc/CMakeLists.txt | 21 --------------- .../tests/check-native-gates-empty.cmake | 26 ------------------ .../tests/check-native-gates-raw-qco.cmake | 27 ------------------- mlir/tools/mqt-cc/tests/native-gates.qasm | 13 --------- 4 files changed, 87 deletions(-) delete mode 100644 mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake delete mode 100644 mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake delete mode 100644 mlir/tools/mqt-cc/tests/native-gates.qasm diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt index 36f17ae0ad..1bbb4ab884 100644 --- a/mlir/tools/mqt-cc/CMakeLists.txt +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -30,27 +30,6 @@ export_executable_symbols_for_plugins(mqt-cc) if(BUILD_MQT_CORE_TESTS) set_target_properties(mqt-cc PROPERTIES EXCLUDE_FROM_ALL FALSE) - set(MQT_CC_NATIVE_GATES_INPUT ${CMAKE_CURRENT_SOURCE_DIR}/tests/native-gates.qasm) - - add_test(NAME mqt-cc-native-gates-qco-optimized - COMMAND $ ${MQT_CC_NATIVE_GATES_INPUT} --emit=qco-optimized - --native-gates=u,cx) - set_tests_properties( - mqt-cc-native-gates-qco-optimized - PROPERTIES LABELS mqt-mlir-unittests PASS_REGULAR_EXPRESSION "qco\\.ctrl" - FAIL_REGULAR_EXPRESSION "qco\\.swap|qco\\.u2|qc\\.h") - - add_test( - NAME mqt-cc-native-gates-raw-qco - COMMAND ${CMAKE_COMMAND} -DMQT_CC=$ -DINPUT=${MQT_CC_NATIVE_GATES_INPUT} -P - ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-native-gates-raw-qco.cmake) - set_tests_properties(mqt-cc-native-gates-raw-qco PROPERTIES LABELS mqt-mlir-unittests) - - add_test( - NAME mqt-cc-native-gates-empty - COMMAND ${CMAKE_COMMAND} -DMQT_CC=$ -DINPUT=${MQT_CC_NATIVE_GATES_INPUT} -P - ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-native-gates-empty.cmake) - set_tests_properties(mqt-cc-native-gates-empty PROPERTIES LABELS mqt-mlir-unittests) add_test(NAME mqt-cc-coupling-map-qco-optimized COMMAND $ ${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm diff --git a/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake b/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake deleted file mode 100644 index 8f90d34047..0000000000 --- a/mlir/tools/mqt-cc/tests/check-native-gates-empty.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(expected "--native-gates must not be empty") - -foreach(native_gates_arg IN ITEMS "--native-gates=" "--native-gates= ") - execute_process( - COMMAND "${MQT_CC}" "${INPUT}" --emit=qco-optimized ${native_gates_arg} - RESULT_VARIABLE result - ERROR_VARIABLE error) - - if(result EQUAL 0) - message(FATAL_ERROR "mqt-cc accepted empty ${native_gates_arg} with --emit=qco-optimized") - endif() - - string(FIND "${error}" "${expected}" diagnostic_position) - if(diagnostic_position EQUAL -1) - message( - FATAL_ERROR "mqt-cc did not emit the expected diagnostic for ${native_gates_arg}:\n${error}") - endif() -endforeach() diff --git a/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake b/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake deleted file mode 100644 index 2f6b9609a1..0000000000 --- a/mlir/tools/mqt-cc/tests/check-native-gates-raw-qco.cmake +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -set(expected "--native-gates requires an output that passes through QCO optimization") - -foreach(emit_format IN ITEMS qco qc-import) - execute_process( - COMMAND "${MQT_CC}" "${INPUT}" --emit=${emit_format} --native-gates=u,cx - RESULT_VARIABLE result - ERROR_VARIABLE error) - - if(result EQUAL 0) - message(FATAL_ERROR "mqt-cc accepted --native-gates with --emit=${emit_format} output") - endif() - - string(FIND "${error}" "${expected}" diagnostic_position) - if(diagnostic_position EQUAL -1) - message( - FATAL_ERROR "mqt-cc did not emit the expected diagnostic for --emit=${emit_format}:\n${error}" - ) - endif() -endforeach() diff --git a/mlir/tools/mqt-cc/tests/native-gates.qasm b/mlir/tools/mqt-cc/tests/native-gates.qasm deleted file mode 100644 index 931e9e0bc3..0000000000 --- a/mlir/tools/mqt-cc/tests/native-gates.qasm +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2026 Munich Quantum Software Company GmbH -// All rights reserved. -// -// SPDX-License-Identifier: MIT -// -// Licensed under the MIT License - -OPENQASM 3.0; -include "stdgates.inc"; - -qubit[2] q; -h q[0]; -swap q[0], q[1]; From ef919bd0583dd1bae6bbbeabdd6c3ab054e2cb3b Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 09:32:17 +0200 Subject: [PATCH 04/13] =?UTF-8?q?=E2=9C=A8=20Enhance=20NativeGateset=20to?= =?UTF-8?q?=20support=20additional=20entanglers=20and=20update=20menu=20st?= =?UTF-8?q?ring=20representation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Transforms/Decomposition/NativeGateset.h | 2 +- .../Decomposition/NativeGateset.cpp | 28 +++++++++++++- .../Decomposition/test_weyl_decomposition.cpp | 38 +++++++++++++++++++ test/python/test_native_gates_from_device.py | 10 +++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h index 38cfc6112c..07944d47cc 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h @@ -88,7 +88,7 @@ struct NativeGateset { * @brief Comma-separated menu for the selected Euler factors and entangler. * * Token order is deterministic (Euler constituents, then entangler), e.g. - * `"x,sx,rz,cz"` or `"u,cx"`. + * `"x,sx,rz,cz"`, `"u,rxx"`, or `"u,ecr"`. */ [[nodiscard]] std::string toMenuString() const; diff --git a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp index b9e416768a..b88ad5b88c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Decomposition/NativeGateset.cpp @@ -380,10 +380,34 @@ std::string NativeGateset::toMenuString() const { append("rz"); break; } - if (*entangler == NativeGateKind::CZ) { + switch (*entangler) { + case NativeGateKind::RXX: + append("rxx"); + break; + case NativeGateKind::RYY: + append("ryy"); + break; + case NativeGateKind::RZX: + append("rzx"); + break; + case NativeGateKind::RZZ: + append("rzz"); + break; + case NativeGateKind::ISWAP: + append("iswap"); + break; + case NativeGateKind::CZ: append("cz"); - } else { + break; + case NativeGateKind::CX: append("cx"); + break; + case NativeGateKind::ECR: + append("ecr"); + break; + default: + llvm_unreachable( + "only RXX/RYY/RZX/RZZ/ISWAP/CZ/CX/ECR are valid entanglers"); } return out; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index 2307f9f234..a9f56c6787 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -801,6 +801,44 @@ TEST(NativeGatesetFromNamesTest, MapsCnotAlias) { EXPECT_EQ(gs->toMenuString(), "u,cx"); } +TEST(NativeGatesetFromNamesTest, PrefersRxxOverOtherEntanglers) { + const SmallVector names = {"u", "ecr", "cx", "cz", "iswap", + "rzz", "rzx", "ryy", "rxx"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->entangler, NativeGateKind::RXX); + EXPECT_EQ(gs->toMenuString(), "u,rxx"); +} + +TEST(NativeGatesetFromNamesTest, EmitsEachNewEntanglerToken) { + const auto ecr = NativeGateset::fromOperationNames( + SmallVector{"x", "sx", "rz", "ecr"}); + ASSERT_TRUE(ecr); + EXPECT_EQ(ecr->entangler, NativeGateKind::ECR); + EXPECT_EQ(ecr->toMenuString(), "x,sx,rz,ecr"); + + const auto iswap = + NativeGateset::fromOperationNames(SmallVector{"u", "iswap"}); + ASSERT_TRUE(iswap); + EXPECT_EQ(iswap->entangler, NativeGateKind::ISWAP); + EXPECT_EQ(iswap->toMenuString(), "u,iswap"); + + const auto ryy = + NativeGateset::fromOperationNames(SmallVector{"u", "ryy"}); + ASSERT_TRUE(ryy); + EXPECT_EQ(ryy->toMenuString(), "u,ryy"); + + const auto rzx = + NativeGateset::fromOperationNames(SmallVector{"u", "rzx"}); + ASSERT_TRUE(rzx); + EXPECT_EQ(rzx->toMenuString(), "u,rzx"); + + const auto rzz = + NativeGateset::fromOperationNames(SmallVector{"u", "rzz"}); + ASSERT_TRUE(rzz); + EXPECT_EQ(rzz->toMenuString(), "u,rzz"); +} + TEST(NativeSpecTest, ResolvesEulerBasisFromGateset) { const auto uGateset = NativeGateset::parse("u,cx"); ASSERT_TRUE(uGateset); diff --git a/test/python/test_native_gates_from_device.py b/test/python/test_native_gates_from_device.py index fffe7efdfa..1e77afa3e5 100644 --- a/test/python/test_native_gates_from_device.py +++ b/test/python/test_native_gates_from_device.py @@ -35,6 +35,16 @@ def test_native_gates_from_operation_names_iqm_prx() -> None: assert native_gates_from_operation_names(["prx", "cz"]) == "r,cz" +def test_native_gates_from_operation_names_prefers_rxx() -> None: + """New bases participate; preference picks RXX when present.""" + assert native_gates_from_operation_names(["u", "cx", "cz", "ecr", "iswap", "rxx", "ryy", "rzx", "rzz"]) == "u,rxx" + + +def test_native_gates_from_operation_names_ecr() -> None: + """ECR-only menus are emitted correctly.""" + assert native_gates_from_operation_names(["x", "sx", "rz", "ecr"]) == "x,sx,rz,ecr" + + def test_native_gates_from_operation_names_rejects_insufficient() -> None: """Reject name lists that lack a supported Euler + entangler pair.""" with pytest.raises(ValueError, match="native-gates"): From 6ab13a7a53758acf3a5b5200bd5cd07f9df8948d Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 10:06:22 +0200 Subject: [PATCH 05/13] =?UTF-8?q?=F0=9F=8E=A8=20Rename=20backend=20targeti?= =?UTF-8?q?ng=20to=20native=20targeting=20in=20QCOProgram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 +-- bindings/mlir/register_mlir.cpp | 11 ++-- bindings/patterns.txt | 4 +- mlir/include/mlir/Compiler/Programs.h | 10 ++-- mlir/lib/Compiler/Programs.cpp | 25 +++++++-- .../Compiler/test_compiler_pipeline.cpp | 54 ++++++++++++++++--- python/mqt/core/mlir.pyi | 4 +- ...arget_backend.py => test_target_native.py} | 34 ++++++++++-- 8 files changed, 114 insertions(+), 34 deletions(-) rename test/python/{test_target_backend.py => test_target_native.py} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfda1c3df0..e786a40d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,8 @@ releases may include breaking changes. ### Added -- ✨ Derive a native-gates menu from device/backend operation names - (`NativeGateset::fromOperationNames`) ([**@simon1hofmann**]) -- ✨ Add progressive backend targeting (`QCOProgram::targetBackend` / Python - `target_backend` / `target_device`, `mqt-cc --coupling-map`) +- ✨ Add progressive native targeting (`QCOProgram::targetNative` / Python + `target_native` / `target_device`, `mqt-cc --coupling-map`) ([**@simon1hofmann**]) - ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) ([**@burgholzer**]) diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index b4eea54bb4..68d0cb45ca 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -469,17 +469,16 @@ operations.)pb"); "lambda_"_a = 0.5F, "niterations"_a = 1, "ntrials"_a = 4, "seed"_a = 42, "Place and route the program for a coupling graph.") .def( - "target_backend", + "target_native", [](mlir::QCOProgram& value, const std::string& nativeGates, const nb::object& coupling) { if (coupling.is_none()) { - requireSuccess(value.targetBackend(nativeGates)); + requireSuccess(value.targetNative(nativeGates)); } else { const auto edges = nb::cast>>( coupling); - requireSuccess( - value.targetBackend(nativeGates, std::span(edges))); + requireSuccess(value.targetNative(nativeGates, std::span(edges))); } }, nb::kw_only(), "native_gates"_a, "coupling"_a = nb::none(), @@ -491,11 +490,11 @@ operations.)pb"); [](mlir::QCOProgram& value, const nb::object& device) { const auto menu = nativeGatesMenuFromDevice(device); const auto coupling = couplingFromDevice(device); - requireSuccess(value.targetBackend(menu, std::span(coupling))); + requireSuccess(value.targetNative(menu, std::span(coupling))); }, "device"_a, "Target a FoMaC device: derive native menu and coupling, then run " - "target_backend.") + "target_native.") .def( "to_qc", [](mlir::QCOProgram& value, const bool copy) { diff --git a/bindings/patterns.txt b/bindings/patterns.txt index 0e4a98dd8b..a232dc58cf 100644 --- a/bindings/patterns.txt +++ b/bindings/patterns.txt @@ -170,8 +170,8 @@ mqt.core.mlir.compile_program: ) -> QCProgram | QCOProgram | JeffProgram | QIRProgram: \doc -mqt.core.mlir.QCOProgram.target_backend: - def target_backend( +mqt.core.mlir.QCOProgram.target_native: + def target_native( self, *, native_gates: str, diff --git a/mlir/include/mlir/Compiler/Programs.h b/mlir/include/mlir/Compiler/Programs.h index ce8b5f5a44..0fe2a0745c 100644 --- a/mlir/include/mlir/Compiler/Programs.h +++ b/mlir/include/mlir/Compiler/Programs.h @@ -202,10 +202,12 @@ class QCOProgram final : public Program { float lambda = 0.5F, std::size_t niterations = 1, std::size_t ntrials = 4, std::size_t seed = 42); - /// Progressive backend targeting: decompose multi-controlled gates, - /// optionally place/route on @p coupling, then fuse to @p nativeGates - /// (required, non-empty). - [[nodiscard]] bool targetBackend( + /// Progressive native targeting: decompose multi-controlled gates, + /// optionally place/route on @p coupling (treated as undirected; reverse + /// edges are added automatically), then fuse to @p nativeGates (required, + /// non-empty, and must parse as a supported native menu). The menu is + /// validated before any IR mutation. + [[nodiscard]] bool targetNative( std::string_view nativeGates, std::span> coupling = {}); diff --git a/mlir/lib/Compiler/Programs.cpp b/mlir/lib/Compiler/Programs.cpp index 1bdd17541f..afc212f846 100644 --- a/mlir/lib/Compiler/Programs.cpp +++ b/mlir/lib/Compiler/Programs.cpp @@ -21,6 +21,7 @@ #include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h" #include "mlir/Dialect/QC/Translation/TranslateQuantumComputationToQC.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" #include "mlir/Dialect/QCO/Transforms/Mapping/Mapping.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QTensor/IR/QTensorDialect.h" @@ -413,19 +414,37 @@ bool QCOProgram::placeAndRoute( "failed to place and route the QCO program")); } -bool QCOProgram::targetBackend( +bool QCOProgram::targetNative( const std::string_view nativeGates, const std::span> coupling) { if (StringRef(nativeGates).trim().empty()) { mod().emitError("the native gate menu must not be empty"); return false; } - if (!decomposeMultiControlled(/*minControls=*/2)) { + if (!qco::decomposition::NativeGateset::parse(nativeGates).has_value()) { + mod().emitError("unsupported native gate menu '") + << nativeGates + << "' (expected a recognised Euler basis plus one entangler)"; return false; } - if (!coupling.empty() && !placeAndRoute(coupling)) { + if (!decomposeMultiControlled(/*minControls=*/2)) { return false; } + if (!coupling.empty()) { + // Treat coupling as undirected: placeAndRoute requires both (u,v) and + // (v,u). + SmallVector> symmetric; + symmetric.reserve(coupling.size() * 2); + for (const auto& [u, v] : coupling) { + symmetric.emplace_back(u, v); + if (u != v) { + symmetric.emplace_back(v, u); + } + } + if (!placeAndRoute(symmetric)) { + return false; + } + } return fuseTwoQubitUnitaryRuns(nativeGates); } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index e40ef852a9..60a0ed4a75 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -500,7 +500,7 @@ cx q[0], q[2]; EXPECT_EQ(loopProgram->str().find("scf.for"), std::string::npos); } -TEST_F(CompilerPipelineTest, TargetBackendMenuOnlyFuses) { +TEST_F(CompilerPipelineTest, TargetNativeMenuOnlyFuses) { const std::string qasm = R"(OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; @@ -512,13 +512,13 @@ cx q[0], q[1]; auto qco = std::move(*qc).intoQCO(); ASSERT_TRUE(qco); ASSERT_TRUE(qco->cleanup()); - ASSERT_TRUE(qco->targetBackend("u,cx")); + ASSERT_TRUE(qco->targetNative("u,cx")); const auto ir = qco->str(); EXPECT_EQ(ir.find("qco.h"), std::string::npos); EXPECT_NE(ir.find("qco.u"), std::string::npos); } -TEST_F(CompilerPipelineTest, TargetBackendWithCouplingLowersSwaps) { +TEST_F(CompilerPipelineTest, TargetNativeWithCouplingLowersSwaps) { // CX on (0,2) needs routing on a line 0-1-2. const std::string qasm = R"(OPENQASM 3.0; include "stdgates.inc"; @@ -532,7 +532,7 @@ cx q[0], q[2]; ASSERT_TRUE(qco->cleanup()); const std::vector> coupling = { {0, 1}, {1, 0}, {1, 2}, {2, 1}}; - ASSERT_TRUE(qco->targetBackend("u,cx", coupling)); + ASSERT_TRUE(qco->targetNative("u,cx", coupling)); const auto ir = qco->str(); EXPECT_EQ(ir.find("qco.swap"), std::string::npos); EXPECT_NE(ir.find("qco.ctrl"), std::string::npos); @@ -546,7 +546,7 @@ cx q[0], q[2]; isExecutableStraightLine(getEntryPoint(module.get()), couplingSet)); } -TEST_F(CompilerPipelineTest, TargetBackendRejectsEmptyMenu) { +TEST_F(CompilerPipelineTest, TargetNativeRejectsEmptyMenu) { const std::string qasm = R"(OPENQASM 3.0; include "stdgates.inc"; qubit q; @@ -556,11 +556,49 @@ h q; ASSERT_TRUE(qc); auto qco = std::move(*qc).intoQCO(); ASSERT_TRUE(qco); - EXPECT_FALSE(qco->targetBackend("")); - EXPECT_FALSE(qco->targetBackend(" ")); + EXPECT_FALSE(qco->targetNative("")); + EXPECT_FALSE(qco->targetNative(" ")); const std::vector> coupling = {{0, 1}, {1, 0}}; - EXPECT_FALSE(qco->targetBackend("", coupling)); + EXPECT_FALSE(qco->targetNative("", coupling)); +} + +TEST_F(CompilerPipelineTest, TargetNativeRejectsInvalidMenuWithoutMutating) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit q; +h q; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + ASSERT_TRUE(qco->cleanup()); + const auto before = qco->str(); + EXPECT_FALSE(qco->targetNative("not-a-gate")); + EXPECT_FALSE(qco->targetNative("cx")); + EXPECT_EQ(qco->str(), before); + EXPECT_NE(before.find("qco.h"), std::string::npos); +} + +TEST_F(CompilerPipelineTest, TargetNativeAcceptsOneWayCoupling) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +cx q[0], q[2]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + ASSERT_TRUE(qco->cleanup()); + // One direction only; targetNative must symmetrize before placeAndRoute. + const std::vector> coupling = {{0, 1}, + {1, 2}}; + ASSERT_TRUE(qco->targetNative("u,cx", coupling)); + const auto ir = qco->str(); + EXPECT_EQ(ir.find("qco.swap"), std::string::npos); + EXPECT_NE(ir.find("qco.ctrl"), std::string::npos); } /** diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index 77e1288125..ec5f33748e 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -169,7 +169,7 @@ class QCOProgram(Program): ) -> None: """Place and route the program for a coupling graph.""" - def target_backend( + def target_native( self, *, native_gates: str, @@ -178,7 +178,7 @@ class QCOProgram(Program): """Decompose multi-controlled gates, optionally place/route, then fuse to native_gates.""" def target_device(self, device: object) -> None: - """Target a FoMaC device: derive native menu and coupling, then run target_backend.""" + """Target a FoMaC device: derive native menu and coupling, then run target_native.""" def to_qc(self, *, copy: bool = False) -> QCProgram: """Convert this program to QC. diff --git a/test/python/test_target_backend.py b/test/python/test_target_native.py similarity index 58% rename from test/python/test_target_backend.py rename to test/python/test_target_native.py index 24c1013791..2381d3205a 100644 --- a/test/python/test_target_backend.py +++ b/test/python/test_target_native.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""Tests for progressive QCOProgram.target_backend / target_device.""" +"""Tests for progressive QCOProgram.target_native / target_device.""" from __future__ import annotations @@ -17,14 +17,14 @@ from mqt.core.mlir import OutputFormat, QCOProgram, compile_program -def test_target_backend_menu_only() -> None: +def test_target_native_menu_only() -> None: """Menu-only targeting removes H in favor of native u factors.""" qc = QuantumComputation(2) qc.h(0) qc.cx(0, 1) qco = compile_program(qc, output=OutputFormat.QCO) assert isinstance(qco, QCOProgram) - qco.target_backend(native_gates="u,cx") + qco.target_native(native_gates="u,cx") assert "qco.h" not in qco.ir @@ -46,11 +46,35 @@ def test_target_device_with_coupling() -> None: assert "qco.ctrl(%0) targets (%arg0 = %2)" not in qco.ir -def test_target_backend_rejects_empty_menu() -> None: +def test_target_native_rejects_empty_menu() -> None: """Empty native_gates must fail.""" qc = QuantumComputation(1) qc.h(0) qco = compile_program(qc, output=OutputFormat.QCO) assert isinstance(qco, QCOProgram) with pytest.raises(RuntimeError, match=r"(?i)fail|empty|native"): - qco.target_backend(native_gates="") + qco.target_native(native_gates="") + + +def test_target_native_rejects_invalid_menu() -> None: + """Unsupported menus fail before mutating the IR.""" + qc = QuantumComputation(1) + qc.h(0) + qco = compile_program(qc, output=OutputFormat.QCO) + assert isinstance(qco, QCOProgram) + before = qco.ir + with pytest.raises(RuntimeError, match=r"(?i)unsupported|native|fail"): + qco.target_native(native_gates="not-a-gate") + assert qco.ir == before + + +def test_target_native_one_way_coupling() -> None: + """One-direction coupling edges are treated as undirected.""" + qc = QuantumComputation(3) + qc.cx(0, 2) + qco = compile_program(qc, output=OutputFormat.QCO) + assert isinstance(qco, QCOProgram) + qco.target_native(native_gates="u,cx", coupling=[(0, 1), (1, 2)]) + assert "qco.swap" not in qco.ir + assert "qco.ctrl" in qco.ir + assert "qco.ctrl(%0) targets (%arg0 = %2)" not in qco.ir From 8dd7e3bd6e15df61fe83e37281498189ec28f5ca Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 10:22:49 +0200 Subject: [PATCH 06/13] =?UTF-8?q?=F0=9F=94=A5=20Drop=20`std::`=20and=20`ll?= =?UTF-8?q?vm::`=20when=20not=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/mlir/register_mlir.cpp | 26 ++++++++----------- mlir/include/mlir/Compiler/Programs.h | 13 +++++----- .../Transforms/Decomposition/NativeGateset.h | 7 +++-- mlir/lib/Compiler/Programs.cpp | 13 +++++----- mlir/tools/mqt-cc/mqt-cc.cpp | 5 ++-- .../Compiler/test_compiler_pipeline.cpp | 10 +++---- 6 files changed, 32 insertions(+), 42 deletions(-) diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 68d0cb45ca..50dc685e58 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -12,8 +12,7 @@ #include "mlir/Compiler/Programs.h" #include "mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h" -#include -#include +#include #include #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) @@ -242,7 +241,7 @@ compileProgram(const nb::object& program, const mlir::ProgramFormat output, [[nodiscard]] std::string nativeGatesMenuOrThrow(const std::vector& names) { - llvm::SmallVector refs; + mlir::SmallVector refs; refs.reserve(names.size()); for (const auto& name : names) { refs.emplace_back(name); @@ -265,19 +264,17 @@ nativeGatesMenuOrThrow(const std::vector& names) { return nativeGatesMenuOrThrow(names); } -[[nodiscard]] std::vector> +[[nodiscard]] std::vector> couplingFromDevice(const nb::object& device) { - std::vector> edges; + std::vector> edges; const nb::object cmap = device.attr("coupling_map")(); if (cmap.is_none()) { return edges; } for (const auto& pair : cmap) { const auto edge = nb::cast(nb::handle(pair)); - const auto a = - static_cast(nb::cast(edge[0].attr("index")())); - const auto b = - static_cast(nb::cast(edge[1].attr("index")())); + const auto a = static_cast(nb::cast(edge[0].attr("index")())); + const auto b = static_cast(nb::cast(edge[1].attr("index")())); edges.emplace_back(a, b); edges.emplace_back(b, a); } @@ -457,10 +454,10 @@ operations.)pb"); .def( "place_and_route", [](mlir::QCOProgram& value, - const std::vector>& coupling, - const std::size_t nlookahead, const float alpha, - const float lambda, const std::size_t niterations, - const std::size_t ntrials, const std::size_t seed) { + const std::vector>& coupling, + const size_t nlookahead, const float alpha, const float lambda, + const size_t niterations, const size_t ntrials, + const size_t seed) { requireSuccess(value.placeAndRoute(std::span(coupling), nlookahead, alpha, lambda, niterations, ntrials, seed)); @@ -476,8 +473,7 @@ operations.)pb"); requireSuccess(value.targetNative(nativeGates)); } else { const auto edges = - nb::cast>>( - coupling); + nb::cast>>(coupling); requireSuccess(value.targetNative(nativeGates, std::span(edges))); } }, diff --git a/mlir/include/mlir/Compiler/Programs.h b/mlir/include/mlir/Compiler/Programs.h index 0fe2a0745c..67d88feb68 100644 --- a/mlir/include/mlir/Compiler/Programs.h +++ b/mlir/include/mlir/Compiler/Programs.h @@ -197,19 +197,18 @@ class QCOProgram final : public Program { /// Place and route the program on a coupling graph. [[nodiscard]] bool - placeAndRoute(std::span> coupling, - std::size_t nlookahead = 1, float alpha = 1.F, - float lambda = 0.5F, std::size_t niterations = 1, - std::size_t ntrials = 4, std::size_t seed = 42); + placeAndRoute(std::span> coupling, + size_t nlookahead = 1, float alpha = 1.F, float lambda = 0.5F, + size_t niterations = 1, size_t ntrials = 4, size_t seed = 42); /// Progressive native targeting: decompose multi-controlled gates, /// optionally place/route on @p coupling (treated as undirected; reverse /// edges are added automatically), then fuse to @p nativeGates (required, /// non-empty, and must parse as a supported native menu). The menu is /// validated before any IR mutation. - [[nodiscard]] bool targetNative( - std::string_view nativeGates, - std::span> coupling = {}); + [[nodiscard]] bool + targetNative(std::string_view nativeGates, + std::span> coupling = {}); /// Consume this program and convert it to QC. [[nodiscard]] std::optional intoQC() &&; diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h index 07944d47cc..d453bda59d 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/NativeGateset.h @@ -13,8 +13,7 @@ #include "mlir/Dialect/QCO/Transforms/Decomposition/Euler.h" #include "mlir/Dialect/QCO/Utils/Matrix.h" -#include -#include +#include #include #include @@ -59,7 +58,7 @@ struct TwoQubitNativeDecomposition; * `rxx`/`ryy`/`rzx`/`rzz` at a fixed angle of π/2. */ struct NativeGateset { - llvm::DenseSet gates; + DenseSet gates; std::optional eulerBasis; std::optional entangler; @@ -82,7 +81,7 @@ struct NativeGateset { * @return Resolved gateset, or `std::nullopt` when no supported menu exists. */ [[nodiscard]] static std::optional - fromOperationNames(llvm::ArrayRef names); + fromOperationNames(ArrayRef names); /** * @brief Comma-separated menu for the selected Euler factors and entangler. diff --git a/mlir/lib/Compiler/Programs.cpp b/mlir/lib/Compiler/Programs.cpp index afc212f846..096c692ffd 100644 --- a/mlir/lib/Compiler/Programs.cpp +++ b/mlir/lib/Compiler/Programs.cpp @@ -393,11 +393,10 @@ bool QCOProgram::decomposeMultiControlled(const uint64_t minControls) { } bool QCOProgram::placeAndRoute( - const std::span> coupling, - const std::size_t nlookahead, const float alpha, const float lambda, - const std::size_t niterations, const std::size_t ntrials, - const std::size_t seed) { - DenseSet> couplingSet; + const std::span> coupling, + const size_t nlookahead, const float alpha, const float lambda, + const size_t niterations, const size_t ntrials, const size_t seed) { + DenseSet> couplingSet; couplingSet.insert(coupling.begin(), coupling.end()); qco::MappingPassOptions options; options.nlookahead = nlookahead; @@ -416,7 +415,7 @@ bool QCOProgram::placeAndRoute( bool QCOProgram::targetNative( const std::string_view nativeGates, - const std::span> coupling) { + const std::span> coupling) { if (StringRef(nativeGates).trim().empty()) { mod().emitError("the native gate menu must not be empty"); return false; @@ -433,7 +432,7 @@ bool QCOProgram::targetNative( if (!coupling.empty()) { // Treat coupling as undirected: placeAndRoute requires both (u,v) and // (v,u). - SmallVector> symmetric; + SmallVector> symmetric; symmetric.reserve(coupling.size() * 2); for (const auto& [u, v] : coupling) { symmetric.emplace_back(u, v); diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 1113896fed..93d208f513 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -434,8 +434,7 @@ int main(int argc, char** argv) { "QCO optimization.\n"; return 1; } - const llvm::StringRef nativeGateMenu = - llvm::StringRef(nativeGates.getValue()).trim(); + const StringRef nativeGateMenu = StringRef(nativeGates.getValue()).trim(); if (nativeGates.getNumOccurrences() > 0 && nativeGateMenu.empty()) { llvm::errs() << "--native-gates must not be empty.\n"; return 1; @@ -520,7 +519,7 @@ int main(int argc, char** argv) { populateDecomposeMultiControlledPipeline(pm, /*minControls=*/2); } if (!couplingEdges.empty()) { - DenseSet> couplingSet( + DenseSet> couplingSet( couplingEdges.begin(), couplingEdges.end()); pm.addPass(qco::createMappingPass(couplingSet, qco::MappingPassOptions{})); diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 60a0ed4a75..cb87d40cb1 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -482,7 +482,7 @@ cx q[0], q[2]; const auto beforeTwoQubitFusion = qco.str(); EXPECT_TRUE(qco.fuseTwoQubitUnitaryRuns("u,cx")); EXPECT_NE(qco.str(), beforeTwoQubitFusion); - const std::vector> coupling = { + const std::vector> coupling = { {0, 1}, {1, 0}, {1, 2}, {2, 1}}; EXPECT_TRUE(qco.placeAndRoute(coupling)); EXPECT_TRUE(qco.runPassPipeline("mqt-qco-default", true, true)); @@ -530,7 +530,7 @@ cx q[0], q[2]; auto qco = std::move(*qc).intoQCO(); ASSERT_TRUE(qco); ASSERT_TRUE(qco->cleanup()); - const std::vector> coupling = { + const std::vector> coupling = { {0, 1}, {1, 0}, {1, 2}, {2, 1}}; ASSERT_TRUE(qco->targetNative("u,cx", coupling)); const auto ir = qco->str(); @@ -558,8 +558,7 @@ h q; ASSERT_TRUE(qco); EXPECT_FALSE(qco->targetNative("")); EXPECT_FALSE(qco->targetNative(" ")); - const std::vector> coupling = {{0, 1}, - {1, 0}}; + const std::vector> coupling = {{0, 1}, {1, 0}}; EXPECT_FALSE(qco->targetNative("", coupling)); } @@ -593,8 +592,7 @@ cx q[0], q[2]; ASSERT_TRUE(qco); ASSERT_TRUE(qco->cleanup()); // One direction only; targetNative must symmetrize before placeAndRoute. - const std::vector> coupling = {{0, 1}, - {1, 2}}; + const std::vector> coupling = {{0, 1}, {1, 2}}; ASSERT_TRUE(qco->targetNative("u,cx", coupling)); const auto ir = qco->str(); EXPECT_EQ(ir.find("qco.swap"), std::string::npos); From 4c2b4194a8cd40e129bdf5943eba12b75e6fd366 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 10:30:41 +0200 Subject: [PATCH 07/13] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unused=20`DenseSe?= =?UTF-8?q?t`=20and=20`DenseMap`=20includes=20from=20`mqt-cc.cpp`=20and=20?= =?UTF-8?q?`test=5Fcompiler=5Fpipeline.cpp`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/tools/mqt-cc/mqt-cc.cpp | 1 - mlir/unittests/Compiler/test_compiler_pipeline.cpp | 2 -- 2 files changed, 3 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 93d208f513..3e62d0117d 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index cb87d40cb1..0df1ed37d5 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -30,8 +30,6 @@ #include #include -#include -#include #include #include #include From 45c8ea730cd4056abe9c7cb6947b88e53fdb0659 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 10:54:11 +0200 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/tools/mqt-cc/mqt-cc.cpp | 1 + mlir/unittests/Compiler/test_compiler_pipeline.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 3e62d0117d..638d013e1a 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include #include diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 0df1ed37d5..e25a3df9e3 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -47,6 +48,7 @@ #include #include +#include #include #include #include @@ -70,8 +72,6 @@ using QCProgramBuilderFn = NamedMLIRBuilder; using QIRProgramBuilderFn = NamedMLIRBuilder; using QuantumComputationBuilderFn = NamedBuilder<::qc::QuantumComputation>; -namespace { - /// Return true if two-qubit unitaries in a straight-line entry point obey /// coupling constraints. static bool isExecutableStraightLine( @@ -112,6 +112,8 @@ static bool isExecutableStraightLine( return true; } +namespace { + struct CompilerPipelineTestCase { std::string name; QuantumComputationBuilderFn quantumComputationBuilder; From 729ef971c01975890939b062bba54e7c69adb822 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 11:07:09 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=E2=98=82=EF=B8=8F=20Increase=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/tools/mqt-cc/CMakeLists.txt | 32 +++++++++++++++++++ .../mqt-cc/tests/check-mqt-cc-fails.cmake | 25 +++++++++++++++ .../Compiler/test_compiler_pipeline.cpp | 18 +++++++++++ .../Decomposition/test_weyl_decomposition.cpp | 25 +++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 mlir/tools/mqt-cc/tests/check-mqt-cc-fails.cmake diff --git a/mlir/tools/mqt-cc/CMakeLists.txt b/mlir/tools/mqt-cc/CMakeLists.txt index 1bbb4ab884..3e700a18c7 100644 --- a/mlir/tools/mqt-cc/CMakeLists.txt +++ b/mlir/tools/mqt-cc/CMakeLists.txt @@ -47,4 +47,36 @@ if(BUILD_MQT_CORE_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-coupling-map-requires-native-gates.cmake) set_tests_properties(mqt-cc-coupling-map-requires-native-gates PROPERTIES LABELS mqt-mlir-unittests) + + add_test( + NAME mqt-cc-coupling-map-must-not-be-empty + COMMAND + ${CMAKE_COMMAND} -DMQT_CC=$ + -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm + "-DARGS=--emit=qco-optimized;--native-gates=u,cx;--coupling-map=" + "-DEXPECTED=--coupling-map must not be empty" -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-mqt-cc-fails.cmake) + set_tests_properties(mqt-cc-coupling-map-must-not-be-empty PROPERTIES LABELS mqt-mlir-unittests) + + add_test( + NAME mqt-cc-coupling-map-rejects-invalid-entry + COMMAND + ${CMAKE_COMMAND} -DMQT_CC=$ + -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm + "-DARGS=--emit=qco-optimized;--native-gates=u,cx;--coupling-map=0-x" + "-DEXPECTED=invalid --coupling-map entry" -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-mqt-cc-fails.cmake) + set_tests_properties(mqt-cc-coupling-map-rejects-invalid-entry PROPERTIES LABELS + mqt-mlir-unittests) + + add_test( + NAME mqt-cc-native-gates-requires-qco-optimization + COMMAND + ${CMAKE_COMMAND} -DMQT_CC=$ + -DINPUT=${CMAKE_CURRENT_SOURCE_DIR}/tests/coupling-line.qasm + "-DARGS=--emit=qco;--native-gates=u,cx" + "-DEXPECTED=--native-gates requires an output that passes through QCO optimization" -P + ${CMAKE_CURRENT_SOURCE_DIR}/tests/check-mqt-cc-fails.cmake) + set_tests_properties(mqt-cc-native-gates-requires-qco-optimization PROPERTIES LABELS + mqt-mlir-unittests) endif() diff --git a/mlir/tools/mqt-cc/tests/check-mqt-cc-fails.cmake b/mlir/tools/mqt-cc/tests/check-mqt-cc-fails.cmake new file mode 100644 index 0000000000..eb0b0ba706 --- /dev/null +++ b/mlir/tools/mqt-cc/tests/check-mqt-cc-fails.cmake @@ -0,0 +1,25 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +if(NOT DEFINED EXPECTED) + message(FATAL_ERROR "EXPECTED must be set") +endif() + +execute_process( + COMMAND ${MQT_CC} ${INPUT} ${ARGS} + RESULT_VARIABLE result + ERROR_VARIABLE error) + +if(result EQUAL 0) + message(FATAL_ERROR "mqt-cc unexpectedly succeeded:\n${error}") +endif() + +string(FIND "${error}" "${EXPECTED}" diagnostic_position) +if(diagnostic_position EQUAL -1) + message(FATAL_ERROR "mqt-cc did not emit the expected diagnostic '${EXPECTED}':\n${error}") +endif() diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index e25a3df9e3..0721783bb0 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -599,6 +599,24 @@ cx q[0], q[2]; EXPECT_NE(ir.find("qco.ctrl"), std::string::npos); } +TEST_F(CompilerPipelineTest, TargetNativeFailsWhenArchitectureTooSmall) { + const std::string qasm = R"(OPENQASM 3.0; +include "stdgates.inc"; +qubit[3] q; +cx q[0], q[1]; +x q[2]; +)"; + auto qc = QCProgram::fromQASMString(qasm); + ASSERT_TRUE(qc); + auto qco = std::move(*qc).intoQCO(); + ASSERT_TRUE(qco); + ASSERT_TRUE(qco->cleanup()); + // Coupling only spans two hardware qubits; three live program qubits must + // fail. + const std::vector> coupling = {{0, 1}}; + EXPECT_FALSE(qco->targetNative("u,cx", coupling)); +} + /** * @brief Test: default compilation returns the requested typed program format */ diff --git a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp index a9f56c6787..a97aea2264 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Decomposition/test_weyl_decomposition.cpp @@ -785,6 +785,31 @@ TEST(NativeGatesetFromNamesTest, RotationPairXzx) { EXPECT_EQ(gs->toMenuString(), "rx,rz,cx"); } +TEST(NativeGatesetFromNamesTest, RotationPairXyx) { + const SmallVector names = {"rx", "ry", "cx"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::XYX); + EXPECT_EQ(gs->toMenuString(), "rx,ry,cx"); +} + +TEST(NativeGatesetFromNamesTest, RotationPairZyz) { + const SmallVector names = {"ry", "rz", "cx"}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::ZYZ); + EXPECT_EQ(gs->toMenuString(), "ry,rz,cx"); +} + +TEST(NativeGatesetFromNamesTest, IgnoresEmptyAndNormalizesAliases) { + const SmallVector names = {" ", " U3 ", "", " CNOT "}; + const auto gs = NativeGateset::fromOperationNames(names); + ASSERT_TRUE(gs); + EXPECT_EQ(gs->eulerBasis, EulerBasis::U); + EXPECT_EQ(gs->entangler, NativeGateKind::CX); + EXPECT_EQ(gs->toMenuString(), "u,cx"); +} + TEST(NativeGatesetFromNamesTest, RejectsInsufficientMenus) { EXPECT_FALSE(NativeGateset::fromOperationNames( SmallVector{"h", "measure"})); From a0d895977d6f19285c29cbe9ae6ecdeada720c82 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 11:53:19 +0200 Subject: [PATCH 10/13] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- bindings/mlir/register_mlir.cpp | 15 ++++++++++++--- .../unittests/Compiler/test_compiler_pipeline.cpp | 7 +++++++ python/mqt/core/mlir.pyi | 12 +++++++++++- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e786a40d88..d291370259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ releases may include breaking changes. ### Added - ✨ Add progressive native targeting (`QCOProgram::targetNative` / Python - `target_native` / `target_device`, `mqt-cc --coupling-map`) + `target_native` / `target_device`, `mqt-cc --coupling-map`) ([#1969]) ([**@simon1hofmann**]) - ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) ([**@burgholzer**]) @@ -675,6 +675,7 @@ changelogs._ +[#1969]: https://github.com/munich-quantum-toolkit/core/pull/1969 [#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 [#1961]: https://github.com/munich-quantum-toolkit/core/pull/1961 [#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 diff --git a/bindings/mlir/register_mlir.cpp b/bindings/mlir/register_mlir.cpp index 50dc685e58..5e9cf23f5a 100644 --- a/bindings/mlir/register_mlir.cpp +++ b/bindings/mlir/register_mlir.cpp @@ -583,9 +583,18 @@ LLVM bitcode.)pb"); &BooleanMemberAdapter<&mlir::QIRProgram::writeBitcode>::call, "path"_a, "Write this program as LLVM bitcode."); - m.def("native_gates_from_operation_names", &nativeGatesMenuOrThrow, "names"_a, - "Derive a comma-separated native-gates menu from operation name " - "strings."); + m.def( + "native_gates_from_operation_names", &nativeGatesMenuOrThrow, "names"_a, + R"pb(Derive a comma-separated native-gates menu from operation name strings. + +Args: + names: Operation name strings (aliases such as ``u3`` / ``cnot`` are normalized). + +Returns: + Comma-separated native gate menu string. + +Raises: + ValueError: When no supported menu can be derived.)pb"); m.def("native_gates_from_device", &nativeGatesMenuFromDevice, "device"_a, R"pb(Derive a comma-separated native-gates menu from a FoMaC device. diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 0721783bb0..3e016180f3 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -597,6 +597,13 @@ cx q[0], q[2]; const auto ir = qco->str(); EXPECT_EQ(ir.find("qco.swap"), std::string::npos); EXPECT_NE(ir.find("qco.ctrl"), std::string::npos); + + auto module = parseRecordedModule(ir); + ASSERT_TRUE(module); + const DenseSet> couplingSet = { + {0, 1}, {1, 0}, {1, 2}, {2, 1}}; + EXPECT_TRUE( + isExecutableStraightLine(getEntryPoint(module.get()), couplingSet)); } TEST_F(CompilerPipelineTest, TargetNativeFailsWhenArchitectureTooSmall) { diff --git a/python/mqt/core/mlir.pyi b/python/mqt/core/mlir.pyi index ec5f33748e..98d77b5544 100644 --- a/python/mqt/core/mlir.pyi +++ b/python/mqt/core/mlir.pyi @@ -253,7 +253,17 @@ class QIRProgram(Program): """Write this program as LLVM bitcode.""" def native_gates_from_operation_names(names: Sequence[str]) -> str: - """Derive a comma-separated native-gates menu from operation name strings.""" + """Derive a comma-separated native-gates menu from operation name strings. + + Args: + names: Operation name strings (aliases such as ``u3`` / ``cnot`` are normalized). + + Returns: + Comma-separated native gate menu string. + + Raises: + ValueError: When no supported menu can be derived. + """ def native_gates_from_device(device: object) -> str: """Derive a comma-separated native-gates menu from a FoMaC device. From e3b8b9ec4e7dbbdc3bb05965fce3810df78dc042 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Thu, 30 Jul 2026 12:03:50 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=F0=9F=90=87=20Address=20rabbit's=20comme?= =?UTF-8?q?nts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Compiler/test_compiler_pipeline.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 3e016180f3..9c76148a40 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -48,7 +48,6 @@ #include #include -#include #include #include #include @@ -85,13 +84,18 @@ static bool isExecutableStraightLine( } if (auto unitaryOp = dyn_cast(op)) { - if (!isa(op) && unitaryOp.getNumQubits() > 1) { - assert(unitaryOp.getNumQubits() <= 2 && "expected two-qubit decomp."); - const auto hwA = m.at(unitaryOp.getInputQubit(0)); - const auto hwB = m.at(unitaryOp.getInputQubit(1)); - if (!couplingSet.contains(std::make_pair(hwA, hwB))) { + if (!isa(op)) { + const auto numQubits = unitaryOp.getNumQubits(); + if (numQubits > 2) { return false; } + if (numQubits > 1) { + const auto hwA = m.at(unitaryOp.getInputQubit(0)); + const auto hwB = m.at(unitaryOp.getInputQubit(1)); + if (!couplingSet.contains(std::make_pair(hwA, hwB))) { + return false; + } + } } for (const auto [pred, succ] : llvm::zip_equal( unitaryOp.getInputQubits(), unitaryOp.getOutputQubits())) { From 3543d41786306873ef93c0417d2be1c420f5652f Mon Sep 17 00:00:00 2001 From: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:41:13 +0200 Subject: [PATCH 12/13] Update CHANGELOG.md to remove outdated entries Removed entries related to binary-safe QDMI program submission and versioned configuration from the changelog. Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> --- CHANGELOG.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886199619a..d5693a915c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,6 @@ releases may include breaking changes. - ✨ Add progressive native targeting (`QCOProgram::targetNative` / Python `target_native` / `target_device`, `mqt-cc --coupling-map`) ([#1969]) ([**@simon1hofmann**]) -- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC ([#1957]) - ([**@burgholzer**]) -- ✨ Add versioned, relocatable configuration and stable-ID registration for - QDMI device libraries, including disabled-ID reservations, fresh device - sessions, idempotent registration, and external-device target metadata - ([#1912]) ([**@burgholzer**]) - ✨ Add and improve QIR generation support in the MQT Compiler Collection ([#1264], [#1446], [#1513], [#1521], [#1548], [#1567], [#1569], [#1570], [#1572], [#1580], [#1620], [#1624], [#1626], [#1648], [#1710], [#1751], From 49b75dd0f8be157e0998180b3ef00213427612d1 Mon Sep 17 00:00:00 2001 From: Simon Hofmann Date: Mon, 3 Aug 2026 09:22:02 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=F0=9F=A7=AA=20Keep=20TargetNative=20inva?= =?UTF-8?q?lid-menu=20test=20IR=20alive=20after=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scalar `qubit q` programs are DCE'd by QCO cleanup with the current OpenQASM lowering; use a one-qubit register instead. Assisted-by: Cursor Grok 4.5 via Cursor --- mlir/unittests/Compiler/test_compiler_pipeline.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 9a66e8259a..d046ca986c 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -1054,8 +1054,8 @@ h q; TEST_F(CompilerPipelineTest, TargetNativeRejectsInvalidMenuWithoutMutating) { const std::string qasm = R"(OPENQASM 3.0; include "stdgates.inc"; -qubit q; -h q; +qubit[1] q; +h q[0]; )"; auto qc = QCProgram::fromQASMString(qasm); ASSERT_TRUE(qc);