From 87bb1487c76a7a39925e6417c6945132644d03b3 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 29 Mar 2026 16:02:03 +0200 Subject: [PATCH 1/4] Add skip-two-qubit-block logic --- .../QCO/Transforms/Mapping/Mapping.cpp | 68 +++++++++++++++---- .../QCO/Transforms/Mapping/test_mapping.cpp | 5 ++ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 9d99892a63..ed66316581 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -72,7 +72,7 @@ struct MappingPass : impl::MappingPassBase { */ enum class Direction : std::uint8_t { Forward, Backward }; - struct LayoutInfo; + class LayoutInfo; /** * @brief A qubit layout that maps program and hardware indices without @@ -207,7 +207,7 @@ struct MappingPass : impl::MappingPassBase { SmallVector hardwareToProgram_; private: - friend struct MappingPass::LayoutInfo; + friend class MappingPass::LayoutInfo; Layout() = default; explicit Layout(const std::size_t nqubits) @@ -388,12 +388,10 @@ struct MappingPass : impl::MappingPassBase { const auto [ltr, rtl] = computeBidirectionalLayers(dynQubits); - // Create trials. Currently this includes the identity layout and - // `ntrials` many random layouts. + // Create trials. Currently this includes `ntrials` many random layouts. SmallVector trials; - trials.reserve(1 + this->ntrials); - trials.emplace_back(Layout::identity(arch.nqubits())); + trials.reserve(this->ntrials); for (std::size_t i = 0; i < this->ntrials; ++i) { trials.emplace_back(Layout::random(arch.nqubits(), rng())); } @@ -609,6 +607,52 @@ struct MappingPass : impl::MappingPassBase { map_range(qubits, [](auto q) { return WireIterator(q); })); } + /** + * @returns true if the wire iterator reached the end (Forward) or the + * start (Backward) of the wire. + */ + template static bool proceedOnWire(const WireIterator& it) { + if constexpr (d == Direction::Forward) { + return it != std::default_sentinel; + } else { + return !isa(it.operation()); + } + } + + /** + * @brief Skip the next two-qubit block of two wires. + * @details Advances each of the two wire iterators until a two-qubit op is + * found. If the ops match, repeat this process. Otherwise, stop. + */ + template + static void skipTwoQubitBlock(WireIterator& first, WireIterator& second) { + constexpr auto step = d == Direction::Forward ? 1 : -1; + + const auto advanceUntilTwoQubitOp = [&](WireIterator& it) { + while (proceedOnWire(it)) { + if (auto op = dyn_cast(it.operation())) { + if (op.getNumQubits() > 1) { + break; + } + } + + std::ranges::advance(it, step); + } + }; + + while (true) { + advanceUntilTwoQubitOp(first); + advanceUntilTwoQubitOp(second); + + if (first.operation() != second.operation()) { + break; + } + + std::ranges::advance(first, step); + std::ranges::advance(second, step); + } + } + /** * @brief Collect the layers of independently executable two-qubit gates of a * circuit. @@ -623,22 +667,14 @@ struct MappingPass : impl::MappingPassBase { template static LayeringResult collectLayers(MutableArrayRef wires) { constexpr auto step = d == Direction::Forward ? 1 : -1; - const auto shouldContinue = [](const WireIterator& it) { - if constexpr (d == Direction::Forward) { - return it != std::default_sentinel; - } else { - return !isa(it.operation()); - } - }; LayeringResult result; - DenseMap visited; while (true) { Layer layer{}; Operation* anchor = nullptr; for (const auto [index, it] : enumerate(wires)) { - while (shouldContinue(it)) { + while (proceedOnWire(it)) { const auto res = TypeSwitch(it.operation()) .Case([&](auto) { @@ -661,6 +697,8 @@ struct MappingPass : impl::MappingPassBase { std::ranges::advance(wires[index], step); std::ranges::advance(wires[otherIndex], step); + skipTwoQubitBlock(wires[index], wires[otherIndex]); + if (anchor == nullptr || op->isBeforeInBlock(anchor)) { anchor = op; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index e3b9a48bdb..70a943c536 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -168,6 +168,11 @@ TEST_P(MappingPassTest, Sabre) { builder.cx(q4, q5); builder.cx(q0, q1); + + builder.h(q0); + builder.y(q1); + builder.cx(q0, q1); + builder.cx(q2, q3); builder.h(q2); From 34918d94424a92381a9b76c61be60b5d47f417dd Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 29 Mar 2026 16:05:21 +0200 Subject: [PATCH 2/4] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea91c3ce55..874c2ac90b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - ✨ Add Sampler and Estimator Primitives to the QDMI-Qiskit Interface ([#1507]) ([**@marcelwa**]) - ✨ Add conversions between Jeff and QCO ([#1479], [#1548], [#1565]) ([**@denialhaag**]) -- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583]) ([**@MatthiasReumann**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588]) ([**@MatthiasReumann**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1570], [#1572], [#1573]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) @@ -333,6 +333,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1588]: https://github.com/munich-quantum-toolkit/core/pull/1588 [#1583]: https://github.com/munich-quantum-toolkit/core/pull/1583 [#1581]: https://github.com/munich-quantum-toolkit/core/pull/1581 [#1573]: https://github.com/munich-quantum-toolkit/core/pull/1573 From 148aae6677e5503c648db70fb1afe049ab2fb048 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 29 Mar 2026 16:41:39 +0200 Subject: [PATCH 3/4] Apply bunny suggestions --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 2 +- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index bbb0c05865..b87bd4ca1e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -66,7 +66,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { "improve the initial layout.">, Option<"ntrials", "ntrials", "std::size_t", "4", "The number of (possibly parallel) random trials of " - "the forwards and backwards mechanism.">, + "the forwards and backwards mechanism. Must be > 0.">, Option<"seed", "seed", "std::size_t", "42", "A seed used for randomization.">]; let statistics = [Statistic<"numSwaps", "num-inserted-swaps", diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index ed66316581..c7103de324 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -644,6 +644,10 @@ struct MappingPass : impl::MappingPassBase { advanceUntilTwoQubitOp(first); advanceUntilTwoQubitOp(second); + if (!proceedOnWire(first) || !proceedOnWire(second)) { + break; + } + if (first.operation() != second.operation()) { break; } From d905c5c5e94f554e5e7ad6d93de2cafa6cb5b1a9 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Sun, 29 Mar 2026 17:01:14 +0200 Subject: [PATCH 4/4] Fix documentation --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 3 +-- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index b87bd4ca1e..5d37e25612 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -52,8 +52,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { made during that traversal. By performing multiple traversals, the pass can iteratively refine the mapping and potentially find a more optimal solution. This is behavior is controlled by the `niterations` parameter. - The pass option `ntrials` determines how many random initial layouts the pass explores. Per default, the pass always - explores the identity layout. If compiled with multi-threading on, these trials will be executed in parallel. + The pass option `ntrials` determines how many random initial layouts the pass explores. If compiled with multi-threading on, these trials will be executed in parallel. }]; let options = [Option<"nlookahead", "nlookahead", "std::size_t", "1", "The number of lookahead steps.">, diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c7103de324..db6ebe2606 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -608,7 +608,7 @@ struct MappingPass : impl::MappingPassBase { } /** - * @returns true if the wire iterator reached the end (Forward) or the + * @returns true if the wire iterator has not reached the end (Forward) or the * start (Backward) of the wire. */ template static bool proceedOnWire(const WireIterator& it) {