From 885af2bc30ecaece303073b350be75916ebfbef0 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Mon, 16 Mar 2026 13:45:38 +0100 Subject: [PATCH 01/10] Add multithreaded trials --- .../QCO/Transforms/Mapping/Mapping.cpp | 113 ++++++++++++++---- 1 file changed, 93 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index e72f0309f7..4708465311 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +92,25 @@ struct MappingPass : impl::MappingPassBase { return layout; } + /** + * @brief Constructs a random layout. + * @param nqubits The number of qubits. + * @param seed A seed for randomization. + * @return The random layout. + */ + static Layout random(const std::size_t nqubits, const std::size_t seed) { + SmallVector mapping(nqubits); + std::iota(mapping.begin(), mapping.end(), IndexType{0}); + std::ranges::shuffle(mapping, std::mt19937_64{seed}); + + Layout layout(nqubits); + for (const auto [prog, hw] : enumerate(mapping)) { + layout.add(prog, hw); + } + + return layout; + } + /** * @brief Insert program:hardware index mapping. * @param prog The program index. @@ -320,27 +343,23 @@ struct MappingPass : impl::MappingPassBase { const auto [ltr, rtl] = computeBidirectionalLayers(dyn); - // Use the SABRE Approach to improve the initial layout choice (here: - // identity): Traverse the layers from left-to-right-to-left and - // cold-route along the way. Repeat this procedure "niterations" times. + auto trials = generateTrials(arch); + parallelForEach(&getContext(), trials, [&, this](Trial& res) { + runMappingTrial(ltr, rtl, arch, params, res); + }); - Layout layout = Layout::identity(arch.nqubits()); - for (std::size_t r = 0; r < this->niterations; ++r) { - if (failed(routeCold(ltr, layout, arch, params))) { - signalPassFailure(); - return; - } - if (failed(routeCold(rtl, layout, arch, params))) { - signalPassFailure(); - return; - } + Trial* best = findBestTrial(trials); + if (best == nullptr) { + signalPassFailure(); + return; } // Once the initial layout is found, replace the dynamic with static // qubits ("placement") and hot-route the circuit layer-by-layer. - const auto stat = place(dyn, layout, func.getFunctionBody(), rewriter); - if (failed(routeHot(ltr, layout, stat, arch, params, rewriter))) { + const auto stat = + place(dyn, best->layout, func.getFunctionBody(), rewriter); + if (failed(routeHot(ltr, best->layout, stat, arch, params, rewriter))) { signalPassFailure(); return; }; @@ -348,6 +367,59 @@ struct MappingPass : impl::MappingPassBase { } private: + struct Trial { + explicit Trial(Layout layout) : layout(std::move(layout)) {} + Layout layout; + std::size_t nswaps{}; + bool valid{false}; + }; + + [[nodiscard]] static SmallVector + generateTrials(const Architecture& arch) { + constexpr std::size_t ntrials = 4; // TODO: Pass Option + SmallVector trials; + trials.reserve(ntrials); + for (std::size_t i = 0; i < ntrials; ++i) { + trials.emplace_back(Layout::random(arch.nqubits(), 42)); + } + return trials; + } + + [[nodiscard]] static Trial* findBestTrial(MutableArrayRef trials) { + Trial* best = nullptr; + for (auto& trial : trials) { + if (trial.valid) { + if (best == nullptr || best->nswaps > trial.nswaps) { + best = &trial; + continue; + } + } + } + + return best; + } + + /** + * @brief Run a mapping trial. + * @details Use the SABRE Approach to improve the initial layout choice: + * Traverse the layers from left-to-right-to-left and cold-route + * along the way. Repeat this procedure "niterations" times. + */ + void runMappingTrial(ArrayRef ltr, ArrayRef rtl, + const Architecture& arch, const Parameters& params, + Trial& trial) { + for (std::size_t r = 0; r < this->niterations; ++r) { + if (failed(routeCold(ltr, arch, params, trial))) { + return; + } + if (failed(routeCold(rtl, arch, params, trial))) { + return; + } + } + + trial.valid = true; + } + /** * @brief Collect dynamic qubits contained in the given function body. * @returns a vector of SSA values produced by qco.alloc operations. @@ -553,21 +625,22 @@ struct MappingPass : impl::MappingPassBase { * @details Iterates over a sliding window of layers and uses the A* search * engine to find a sequence of SWAPs that makes that layer executable. * Instead of inserting these SWAPs into the IR, this function only updates - * the layout. + * (and hence modifies) the layout. * @returns failure() if A* search isn't able to find a solution. */ - LogicalResult routeCold(ArrayRef layers, Layout& layout, - const Architecture& arch, const Parameters& params) { + LogicalResult routeCold(ArrayRef layers, const Architecture& arch, + const Parameters& params, Trial& trial) { for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); - const auto swaps = search(window, layout, arch, params); + const auto swaps = search(window, trial.layout, arch, params); if (failed(swaps)) { return failure(); } + trial.nswaps += swaps->size(); for (const auto& [hw0, hw1] : *swaps) { - layout.swap(hw0, hw1); + trial.layout.swap(hw0, hw1); } } From a18eeb86e97ff85f84e04b1db2ed90deeb3d01e8 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 09:08:17 +0100 Subject: [PATCH 02/10] Finalize trial logic --- .../mlir/Dialect/QCO/Transforms/Passes.td | 9 +- .../QCO/Transforms/Mapping/Mapping.cpp | 171 +++++++++++------- .../QCO/Transforms/Mapping/test_mapping.cpp | 7 +- 3 files changed, 118 insertions(+), 69 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 6f090fd44d..67a8b143d2 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -49,6 +49,9 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { each traversal, the pass routes the circuit and updates the dynamic-to-static mapping based on the routing decisions 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. If compiled with multi-threading + on, these trials will be executed in parallel. }]; let options = [ Option<"nlookahead", "nlookahead", "std::size_t", "1", @@ -58,7 +61,11 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { Option<"lambda", "lambda", "float", "0.5F", "The lambda factor in the cost function.">, Option<"niterations", "niterations", "std::size_t", "2", - "The number of forwards and backwards traversal to improve the initial layout."> + "The number of forwards and backwards traversal to improve the initial layout.">, + Option<"ntrials", "ntrials", "std::size_t", "4", + "The number of (possibly parallel) trials of the forwards and backwards mechanism.">, + Option<"seed", "seed", "std::size_t", "42", + "A seed used for randomization."> ]; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 4708465311..352cda9c61 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -323,6 +323,9 @@ struct MappingPass : impl::MappingPassBase { using MappingPassBase::MappingPassBase; void runOnOperation() override { + std::mt19937_64 rng{this->seed}; + IRRewriter rewriter(&getContext()); + Parameters params(this->alpha, this->lambda, this->nlookahead); // TODO: Hardcoded architecture. Architecture arch("RigettiNovera", 9, @@ -331,7 +334,6 @@ struct MappingPass : impl::MappingPassBase { {3, 4}, {4, 3}, {4, 7}, {7, 4}, {4, 5}, {5, 4}, {5, 8}, {8, 5}, {6, 7}, {7, 6}, {7, 8}, {8, 7}}); - IRRewriter rewriter(&getContext()); for (auto func : getOperation().getOps()) { const auto dyn = collectDynamicQubits(func.getFunctionBody()); if (dyn.size() > arch.nqubits()) { @@ -343,12 +345,30 @@ struct MappingPass : impl::MappingPassBase { const auto [ltr, rtl] = computeBidirectionalLayers(dyn); - auto trials = generateTrials(arch); - parallelForEach(&getContext(), trials, [&, this](Trial& res) { - runMappingTrial(ltr, rtl, arch, params, res); - }); + // Create trials. Currently this includes the identity layout and + // `ntrials` many random layouts. + + SmallVector trials; + trials.reserve(1 + this->ntrials); + trials.emplace_back(Layout::identity(arch.nqubits())); + for (std::size_t i = 0; i < this->ntrials; ++i) { + trials.emplace_back(Layout::random(arch.nqubits(), rng())); + } - Trial* best = findBestTrial(trials); + // Execute each of the trials (possibly in parallel). Collect the results + // and find the one with the fewest SWAPs. + + SmallVector> results(trials.size()); + parallelForEach( + &getContext(), enumerate(trials), [&, this](auto indexedTrial) { + auto [idx, layout] = indexedTrial; + auto res = runMappingTrial(ltr, rtl, arch, params, layout); + if (succeeded(res)) { + results[idx] = std::move(*res); + } + }); + + TrialResult* best = findBestTrial(results); if (best == nullptr) { signalPassFailure(); return; @@ -359,7 +379,7 @@ struct MappingPass : impl::MappingPassBase { const auto stat = place(dyn, best->layout, func.getFunctionBody(), rewriter); - if (failed(routeHot(ltr, best->layout, stat, arch, params, rewriter))) { + if (failed(commitTrial(*best, stat, rewriter))) { signalPassFailure(); return; }; @@ -367,57 +387,70 @@ struct MappingPass : impl::MappingPassBase { } private: - struct Trial { - explicit Trial(Layout layout) : layout(std::move(layout)) {} + struct [[nodiscard]] TrialResult { + explicit TrialResult(Layout layout) : layout(std::move(layout)) {} + + /// @brief The computed initial layout. Layout layout; + /// @brief A vector of SWAPs for each layer. + SmallVector> swaps; + /// @brief The number of inserted SWAPs. std::size_t nswaps{}; - bool valid{false}; }; - [[nodiscard]] static SmallVector - generateTrials(const Architecture& arch) { - constexpr std::size_t ntrials = 4; // TODO: Pass Option - SmallVector trials; - trials.reserve(ntrials); - for (std::size_t i = 0; i < ntrials; ++i) { - trials.emplace_back(Layout::random(arch.nqubits(), 42)); - } - return trials; - } - - [[nodiscard]] static Trial* findBestTrial(MutableArrayRef trials) { - Trial* best = nullptr; - for (auto& trial : trials) { - if (trial.valid) { - if (best == nullptr || best->nswaps > trial.nswaps) { - best = &trial; - continue; + /** + * @brief Find the best trial result in terms of the number of SWAPs. + * @returns the best trial result or nullptr if no result is valid. + */ + [[nodiscard]] static TrialResult* + findBestTrial(MutableArrayRef> results) { + TrialResult* best = nullptr; + for (auto& opt : results) { + if (opt.has_value()) { + if (best == nullptr || best->nswaps > opt->nswaps) { + best = &opt.value(); } } } - return best; } /** * @brief Run a mapping trial. - * @details Use the SABRE Approach to improve the initial layout choice: + * @details Use the SABRE Approach to improve the initial layout: * Traverse the layers from left-to-right-to-left and cold-route * along the way. Repeat this procedure "niterations" times. + * @returns the trial result or failure() on failure. */ - void runMappingTrial(ArrayRef ltr, ArrayRef rtl, - const Architecture& arch, const Parameters& params, - Trial& trial) { - for (std::size_t r = 0; r < this->niterations; ++r) { - if (failed(routeCold(ltr, arch, params, trial))) { - return; + FailureOr runMappingTrial(ArrayRef ltr, + ArrayRef rtl, + const Architecture& arch, + const Parameters& params, + Layout& layout) { + // Perform forwards and backwards traversals. + for (std::size_t i = 0; i < this->niterations; ++i) { + if (failed(route(ltr, arch, params, layout, [](const auto&) {}))) { + return failure(); } - if (failed(routeCold(rtl, arch, params, trial))) { - return; + if (failed(route(rtl, arch, params, layout, [](const auto&) {}))) { + return failure(); } } - trial.valid = true; + TrialResult result(layout); // Copies the final initial layout. + + // Helper function that adds the SWAPs to the trial result. + const auto collectSwaps = [&](ArrayRef swaps) { + result.nswaps += swaps.size(); + result.swaps.emplace_back(swaps); + }; + + // Perform final left-to-right traversal whilst collecting SWAPs. + if (failed(route(ltr, arch, params, layout, collectSwaps))) { + return failure(); + } + + return result; } /** @@ -625,23 +658,27 @@ struct MappingPass : impl::MappingPassBase { * @details Iterates over a sliding window of layers and uses the A* search * engine to find a sequence of SWAPs that makes that layer executable. * Instead of inserting these SWAPs into the IR, this function only updates - * (and hence modifies) the layout. + * (and hence modifies) the layout. The function calls the callback @p onSwaps + * for each layer with the found sequence of SWAPs. * @returns failure() if A* search isn't able to find a solution. */ - LogicalResult routeCold(ArrayRef layers, const Architecture& arch, - const Parameters& params, Trial& trial) { + template + LogicalResult route(ArrayRef layers, const Architecture& arch, + const Parameters& params, Layout& layout, + OnSwaps&& onSwaps) { for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); - const auto swaps = search(window, trial.layout, arch, params); + const auto swaps = search(window, layout, arch, params); if (failed(swaps)) { return failure(); } - trial.nswaps += swaps->size(); for (const auto& [hw0, hw1] : *swaps) { - trial.layout.swap(hw0, hw1); + layout.swap(hw0, hw1); } + + std::forward(onSwaps)(*swaps); } return success(); @@ -654,9 +691,9 @@ struct MappingPass : impl::MappingPassBase { * This function inserts SWAP ops. * @returns failure() if A* search isn't able to find a solution. */ - LogicalResult routeHot(ArrayRef ltr, Layout& layout, - ArrayRef statics, const Architecture& arch, - const Parameters& params, IRRewriter& rewriter) { + static LogicalResult commitTrial(const TrialResult& result, + ArrayRef statics, + IRRewriter& rewriter) { // Helper function that advances the iterator to the input qubit (the // operation producing it) of a deallocation or two-qubit op. const auto advFront = [](WireIterator& it) { @@ -675,21 +712,15 @@ struct MappingPass : impl::MappingPassBase { } }; + DenseMap seen; auto wires = toWires(statics); - for (const auto [i, layer] : enumerate(ltr)) { - // Advance all wires to the next front of one-qubit outputs (the SSA - // values). + for (const auto [i, swaps] : enumerate(result.swaps)) { + // Advance all wires to the next front of one-qubit outputs + // (the SSA values). for_each(wires, advFront); - // Collect window and use A* to find and insert a sequence of swaps. - const auto len = std::min(1 + this->nlookahead, ltr.size() - i); - const auto window = ltr.slice(i, len); - const auto swaps = search(window, layout, arch, params); - if (failed(swaps)) { - return failure(); - } - - for (const auto& [hw0, hw1] : *swaps) { + // Apply the sequence of SWAPs and rewire the qubit SSA values. + for (const auto& [hw0, hw1] : swaps) { Operation* op0 = wires[hw0].operation(); Operation* op1 = wires[hw1].operation(); const auto in0 = wires[hw0].qubit(); @@ -714,15 +745,23 @@ struct MappingPass : impl::MappingPassBase { // Jump over the SWAPOp. std::ranges::advance(wires[hw0], 1); std::ranges::advance(wires[hw1], 1); - - layout.swap(hw0, hw1); } - // Jump over two-qubit gates contained in the layer. - for (const auto& [prog0, prog1] : layer) { - std::ranges::advance(wires[layout.getHardwareIndex(prog0)], 1); - std::ranges::advance(wires[layout.getHardwareIndex(prog1)], 1); + // Jump over "ready" two-qubit gates. + for (auto& it : wires) { + auto op = dyn_cast(std::next(it).operation()); + if (op && op.getNumQubits() > 1) { + if (seen.contains(op)) { + std::ranges::advance(it, 1); + std::ranges::advance(*seen[op], 1); + continue; + } + + seen.try_emplace(op, &it); + } } + + seen.clear(); // Prepare for next iteration. } return success(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index 19804d22b0..b0ec1d7044 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -104,8 +104,11 @@ class MappingPassTest : public testing::Test, static void runHeuristicMapping(OwningOpRef& moduleOp) { PassManager pm(moduleOp->getContext()); pm.addPass(createQCToQCO()); - pm.addPass(qco::createMappingPass(qco::MappingPassOptions{ - .nlookahead = 5, .alpha = 1, .lambda = 0.85, .niterations = 2})); + pm.addPass(qco::createMappingPass(qco::MappingPassOptions{.nlookahead = 5, + .alpha = 1, + .lambda = 0.85, + .niterations = 2, + .ntrials = 8})); pm.addPass(createQCOToQC()); auto res = pm.run(*moduleOp); ASSERT_TRUE(succeeded(res)); From bbe9884915af790f39be02bb6918d515a213df6c Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 09:23:19 +0100 Subject: [PATCH 03/10] Apply lint suggestions --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 352cda9c61..c2145874b1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -20,8 +20,6 @@ #include #include #include -#include -#include #include #include #include @@ -40,6 +38,8 @@ #include #include #include +#include +#include #include #include #include @@ -666,6 +666,8 @@ struct MappingPass : impl::MappingPassBase { LogicalResult route(ArrayRef layers, const Architecture& arch, const Parameters& params, Layout& layout, OnSwaps&& onSwaps) { + auto&& callback = std::forward(onSwaps); + for (std::size_t i = 0; i < layers.size(); ++i) { const std::size_t len = std::min(1 + nlookahead, layers.size() - i); const auto window = layers.slice(i, len); @@ -678,7 +680,7 @@ struct MappingPass : impl::MappingPassBase { layout.swap(hw0, hw1); } - std::forward(onSwaps)(*swaps); + std::invoke(callback, *swaps); } return success(); From 453ce91094e961abc13ea8701629acb343ddb0e2 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 09:27:18 +0100 Subject: [PATCH 04/10] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed8c0f998..cbd46115cd 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]) ([**@denialhaag**]) -- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547]) ([**@MatthiasReumann**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568]) ([**@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], [#1548], [#1550], [#1554]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) @@ -331,6 +331,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1568]: https://github.com/munich-quantum-toolkit/core/pull/1568 [#1554]: https://github.com/munich-quantum-toolkit/core/pull/1554 [#1550]: https://github.com/munich-quantum-toolkit/core/pull/1550 [#1549]: https://github.com/munich-quantum-toolkit/core/pull/1549 From b45051a31e4cd1b63b0d26c7cbb26b6df23dfbc6 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 09:39:36 +0100 Subject: [PATCH 05/10] Apply bunny suggestions --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 8 ++++---- .../Dialect/QCO/Transforms/Mapping/test_mapping.cpp | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 67a8b143d2..9303bf4017 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -50,8 +50,8 @@ 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. 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. Per default, the pass always + explores the identity layout. If compiled with multi-threading on, these trials will be executed in parallel. }]; let options = [ Option<"nlookahead", "nlookahead", "std::size_t", "1", @@ -63,8 +63,8 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { Option<"niterations", "niterations", "std::size_t", "2", "The number of forwards and backwards traversal to improve the initial layout.">, Option<"ntrials", "ntrials", "std::size_t", "4", - "The number of (possibly parallel) trials of the forwards and backwards mechanism.">, - Option<"seed", "seed", "std::size_t", "42", + "The number of (possibly parallel) random trials of the forwards and backwards mechanism.">, + Option<"seed", "seed", "std::size_t", "42", "A seed used for randomization."> ]; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp index b0ec1d7044..8b65bf57f6 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/test_mapping.cpp @@ -108,7 +108,8 @@ class MappingPassTest : public testing::Test, .alpha = 1, .lambda = 0.85, .niterations = 2, - .ntrials = 8})); + .ntrials = 8, + .seed = 1337})); pm.addPass(createQCOToQC()); auto res = pm.run(*moduleOp); ASSERT_TRUE(succeeded(res)); From 81dd47843050a03566634bec901e1935a8cd56ab Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 09:43:41 +0100 Subject: [PATCH 06/10] Add A* anti-stuck mechanism --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index c2145874b1..8911052e9e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -511,12 +511,14 @@ struct MappingPass : impl::MappingPassBase { /** * @brief Perform A* search to find a sequence of SWAPs that makes the * two-qubit operations inside the first layer (the front) executable. + * @details The parameter @p maxIterations determines how many nodes will + * be explored until the current search is considered a failure. * @returns a vector of hardware-index pairs (each denoting a SWAP) or * failure() if A* fails. */ [[nodiscard]] static FailureOr> search(ArrayRef layers, const Layout& layout, const Architecture& arch, - const Parameters& params) { + const Parameters& params, const std::size_t maxIterations = 50'000) { Node root(layout); if (root.isGoal(layers.front(), arch)) { @@ -527,7 +529,8 @@ struct MappingPass : impl::MappingPassBase { frontier.emplace(root); DenseSet expansionSet; - while (!frontier.empty()) { + std::size_t i = 0; + while (!frontier.empty(), i < maxIterations) { Node curr = frontier.top(); frontier.pop(); @@ -557,6 +560,8 @@ struct MappingPass : impl::MappingPassBase { } } } + + ++i; } return failure(); From 9cb65053aefd2b37e44d819b7f6e3abe9b5bdfe1 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 10:05:03 +0100 Subject: [PATCH 07/10] Fix max iterations check --- mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 8911052e9e..4a72f5141c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -530,7 +530,7 @@ struct MappingPass : impl::MappingPassBase { DenseSet expansionSet; std::size_t i = 0; - while (!frontier.empty(), i < maxIterations) { + while (!frontier.empty() && i < maxIterations) { Node curr = frontier.top(); frontier.pop(); From 83e9890b7298650d5c277b7bc3e7e9216a5bf491 Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Tue, 17 Mar 2026 10:31:54 +0100 Subject: [PATCH 08/10] Final minor improvements --- .../QCO/Transforms/Mapping/Mapping.cpp | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 4a72f5141c..53d2193803 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -374,15 +374,7 @@ struct MappingPass : impl::MappingPassBase { return; } - // Once the initial layout is found, replace the dynamic with static - // qubits ("placement") and hot-route the circuit layer-by-layer. - - const auto stat = - place(dyn, best->layout, func.getFunctionBody(), rewriter); - if (failed(commitTrial(*best, stat, rewriter))) { - signalPassFailure(); - return; - }; + commitTrial(*best, dyn, func.getFunctionBody(), rewriter); } } @@ -692,15 +684,13 @@ struct MappingPass : impl::MappingPassBase { } /** - * @brief "Hot" routing of the given layers. - * @details Iterates over a sliding window of layers and uses the A* search - * engine to find a sequence of SWAPs that makes that layer executable. - * This function inserts SWAP ops. - * @returns failure() if A* search isn't able to find a solution. + * @brief Performs placement and inserts SWAPs into the IR. + * @details Replace the dynamic with static qubits ("placement") and inserts + * the SWAPs of the trial result into the IR. */ - static LogicalResult commitTrial(const TrialResult& result, - ArrayRef statics, - IRRewriter& rewriter) { + static void commitTrial(const TrialResult& result, + ArrayRef dynQubits, Region& funcBody, + IRRewriter& rewriter) { // Helper function that advances the iterator to the input qubit (the // operation producing it) of a deallocation or two-qubit op. const auto advFront = [](WireIterator& it) { @@ -719,8 +709,9 @@ struct MappingPass : impl::MappingPassBase { } }; + auto wires = toWires(place(dynQubits, result.layout, funcBody, rewriter)); + DenseMap seen; - auto wires = toWires(statics); for (const auto [i, swaps] : enumerate(result.swaps)) { // Advance all wires to the next front of one-qubit outputs // (the SSA values). @@ -770,8 +761,6 @@ struct MappingPass : impl::MappingPassBase { seen.clear(); // Prepare for next iteration. } - - return success(); } }; } // namespace mlir::qco From 7fe9d84fae3753d00fac918fd9ff1d2bb68fdc8b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 18 Mar 2026 12:36:50 +0100 Subject: [PATCH 09/10] Adjust maximum iterations --- .../QCO/Transforms/Mapping/Architecture.h | 6 ++++++ .../QCO/Transforms/Mapping/Architecture.cpp | 8 ++++++++ .../QCO/Transforms/Mapping/Mapping.cpp | 20 +++++++++++++++---- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Architecture.h b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Architecture.h index 657f8bff41..1062e3909b 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Architecture.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Mapping/Architecture.h @@ -64,6 +64,12 @@ class [[nodiscard]] Architecture { [[nodiscard]] mlir::SmallVector neighboursOf(std::size_t u) const; + /** + * @brief Return the maximum degree (connectivity) of any qubit in the + * architecture. + */ + [[nodiscard]] std::size_t maxDegree() const; + private: using Matrix = mlir::SmallVector, 0>; diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp index b7d08e3cd4..2f6454f691 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp @@ -72,3 +72,11 @@ void Architecture::collectNeighbours() { neighbours_[u].push_back(v); } } + +std::size_t Architecture::maxDegree() const { + std::size_t deg = 0; + for (const auto& nbrs : neighbours_) { + deg = std::max(deg, nbrs.size()); + } + return deg; +} diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp index 53d2193803..c8971abbfc 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Mapping.cpp @@ -503,14 +503,26 @@ struct MappingPass : impl::MappingPassBase { /** * @brief Perform A* search to find a sequence of SWAPs that makes the * two-qubit operations inside the first layer (the front) executable. - * @details The parameter @p maxIterations determines how many nodes will - * be explored until the current search is considered a failure. + * @details + * The iteration budget is then b^{3}, which corresponds to + * exhausting all paths of length up to b^{2} in a search tree with branching + * factor b. A hard cap prevents impractical runtimes on larger architectures. + * + * The branching factor b of the A* search is the product of the + * architecture's maximum qubit degree and the maximum number of two-qubit + * gates in any layer: + * + * b = maxDegree × ⌈N/2⌉ + * * @returns a vector of hardware-index pairs (each denoting a SWAP) or * failure() if A* fails. */ [[nodiscard]] static FailureOr> search(ArrayRef layers, const Layout& layout, const Architecture& arch, - const Parameters& params, const std::size_t maxIterations = 50'000) { + const Parameters& params) { + constexpr std::size_t cap = 25'000'000UL; + const std::size_t b = arch.maxDegree() * ((arch.nqubits() + 1) / 2); + const std::size_t budget = std::min(b * b * b, cap); Node root(layout); if (root.isGoal(layers.front(), arch)) { @@ -522,7 +534,7 @@ struct MappingPass : impl::MappingPassBase { DenseSet expansionSet; std::size_t i = 0; - while (!frontier.empty() && i < maxIterations) { + while (!frontier.empty() && i < budget) { Node curr = frontier.top(); frontier.pop(); From b6cdef37244ca748dde7aac8c7b3cc7eaad6586b Mon Sep 17 00:00:00 2001 From: Matthias Reumann Date: Wed, 18 Mar 2026 12:45:05 +0100 Subject: [PATCH 10/10] Fix linting --- mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp b/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp index 2f6454f691..c6375def1c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Mapping/Architecture.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include