diff --git a/CHANGELOG.md b/CHANGELOG.md index dad0d3abf3..2b55203b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add a `absorb-swaps` pass for absorbing initial SWAPs ([#1750]) ([**@jmoosburger**], [**@MatthiasReumann**]) - 🚸 Add [CMake presets] to provide a standardized and reproducible way to configure builds ([#1660]) ([**@denialhaag**]) - ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) @@ -402,6 +403,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1750]: https://github.com/munich-quantum-toolkit/core/pull/1750 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 [#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 [#1737]: https://github.com/munich-quantum-toolkit/core/pull/1737 @@ -649,6 +651,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [**@simon1hofmann**]: https://github.com/simon1hofmann [**@keefehuang**]: https://github.com/keefehuang [**@J4MMlE**]: https://github.com/J4MMlE +[**@jmoosburger**]: https://github.com/jmoosburger diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 32f678924e..1799b5aa18 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -174,4 +174,18 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { }]; } +def SwapAbsorption : Pass<"absorb-swaps", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect"]; + let summary = "This pass absorbs SWAP operations into the initial " + "program-to-hardware mapping."; + let description = [{ + For a SWAP operation exchanging static qubits q0 and q1, the pass replaces the use of the + first (second) input qubit with the second (first) output qubit of the SWAP and subsequently + removes the operation. As a result, the initial program-to-hardware mapping is changed. + This process is repeated until no more SWAP operations can be absorbed. + + The pass assumes that the quantum program is already mapped to static qubits. + }]; +} + #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/SwapAbsorption.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/SwapAbsorption.cpp new file mode 100644 index 0000000000..ccce35a807 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/SwapAbsorption.cpp @@ -0,0 +1,88 @@ +/* + * 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 + */ + +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QCO/Utils/Drivers.h" +#include "mlir/Dialect/QCO/Utils/WireIterator.h" + +#include +#include +#include +#include +#include + +#include + +namespace mlir::qco { +#define GEN_PASS_DEF_SWAPABSORPTION +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +namespace { +struct SwapAbsorption : impl::SwapAbsorptionBase { +public: + using SwapAbsorptionBase::SwapAbsorptionBase; + +protected: + void runOnOperation() override { + ModuleOp anchor = getOperation(); + IRRewriter rewriter(&getContext()); + + for (auto func : anchor.getOps()) { + SmallVector readyToAbsorb; + SmallVector wires; + do { + wires.clear(); + for (auto op : func.getOps()) { + wires.emplace_back(op.getQubit()); + } + if (wires.empty()) { + return; + } + + readyToAbsorb.clear(); + findSwapsReadyForAbsorption(wires, readyToAbsorb); + + for (auto swapOp : readyToAbsorb) { + absorbSingleSwap(swapOp, rewriter); + } + } while (!readyToAbsorb.empty()); + } + } + +private: + static void findSwapsReadyForAbsorption(MutableArrayRef wires, + SmallVector& readyToAbsorb) { + std::ignore = walkProgramGraph( + wires, [&](const ReadyRange& ready, ReleasedOps& released) { + for (const auto& [op, indices] : ready) { + if (isa(op)) { + readyToAbsorb.emplace_back(op); + } + released.emplace_back(op); + } + return WalkResult::interrupt(); + }); + } + + static void absorbSingleSwap(SWAPOp swapOp, IRRewriter& rewriter) { + auto in0 = swapOp.getQubit0In(); + auto in1 = swapOp.getQubit1In(); + + auto out0 = swapOp.getQubit0Out(); + auto out1 = swapOp.getQubit1Out(); + + rewriter.replaceAllUsesWith(out0, in1); + rewriter.replaceAllUsesWith(out1, in0); + rewriter.eraseOp(swapOp); + } +}; +} // namespace +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index e80e957680..47a33d0b01 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -7,8 +7,9 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-optimizations) -add_executable(${target_name} test_qco_hadamard_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp test_quantum_loop_unroll.cpp) +add_executable( + ${target_name} test_qco_hadamard_lifting.cpp test_qco_merge_single_qubit_rotation.cpp + test_quantum_loop_unroll.cpp test_swapabsorption.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_swapabsorption.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_swapabsorption.cpp new file mode 100644 index 0000000000..eba0ec646d --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_swapabsorption.cpp @@ -0,0 +1,189 @@ +/* + * 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 + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace mlir; +using namespace mlir::qco; + +namespace { + +class SwapAbsorbPassTest : public testing::Test { + +protected: + void SetUp() override { + DialectRegistry registry; + registry.insert(); + context = std::make_unique(); + context->appendDialectRegistry(registry); + context->loadAllAvailableDialects(); + } + + static void applySwapAbsorb(OwningOpRef& moduleOp) { + PassManager pm(moduleOp->getContext()); + pm.addPass(qco::createSwapAbsorption()); + auto res = pm.run(*moduleOp); + + ASSERT_TRUE(succeeded(res)); + } + + std::unique_ptr context; +}; +}; // namespace + +TEST_F(SwapAbsorbPassTest, PassDoesNotChangeSwaplessProgram) { + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q00 = builder.staticQubit(0); + const auto q10 = builder.staticQubit(1); + + const auto q01 = builder.h(q00); + const auto [q02, q11] = builder.cx(q01, q10); + + builder.sink(q02); + builder.sink(q11); + + auto moduleThroughPass = builder.finalize(); + auto originalModule = moduleThroughPass->clone(); + + applySwapAbsorb(moduleThroughPass); + ASSERT_TRUE(mlir::OperationEquivalence::isEquivalentTo( + moduleThroughPass.get(), originalModule, + mlir::OperationEquivalence::Flags::IgnoreLocations)); +} + +TEST_F(SwapAbsorbPassTest, PassReordersTwoQubitCircuitWithLeadingSwap) { + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q00 = builder.staticQubit(0); + const auto q10 = builder.staticQubit(1); + + const auto [q01, q11] = builder.swap(q00, q10); + + const auto q02 = builder.id(q01); + const auto q12 = builder.id(q11); + + builder.sink(q02); + builder.sink(q12); + + auto moduleThroughPass = builder.finalize(); + applySwapAbsorb(moduleThroughPass); + + ASSERT_EQ(q10, ((IdOp)q02.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q00, ((IdOp)q12.getDefiningOp()).getInputQubit(0)); +} + +TEST_F(SwapAbsorbPassTest, PassAbsorbsTwoIndependentSwaps) { + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q00 = builder.staticQubit(0); + const auto q10 = builder.staticQubit(1); + const auto q20 = builder.staticQubit(2); + const auto q30 = builder.staticQubit(3); + + const auto [q01, q11] = builder.swap(q00, q10); + const auto [q21, q31] = builder.swap(q20, q30); + + const auto q02 = builder.id(q01); + const auto q12 = builder.id(q11); + const auto q22 = builder.id(q21); + const auto q32 = builder.id(q31); + + builder.sink(q02); + builder.sink(q12); + builder.sink(q22); + builder.sink(q32); + + auto moduleThroughPass = builder.finalize(); + applySwapAbsorb(moduleThroughPass); + + ASSERT_EQ(q10, ((IdOp)q02.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q00, ((IdOp)q12.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q30, ((IdOp)q22.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q20, ((IdOp)q32.getDefiningOp()).getInputQubit(0)); +} + +TEST_F(SwapAbsorbPassTest, PassAbsorbsSwapWithLeadingSingleQubitGates) { + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q00 = builder.staticQubit(0); + const auto q10 = builder.staticQubit(1); + + const auto q01 = builder.id(q00); + const auto q11 = builder.id(q10); + + const auto [q02, q12] = builder.swap(q01, q11); + + const auto q03 = builder.id(q02); + const auto q13 = builder.id(q12); + + builder.sink(q03); + builder.sink(q13); + + auto moduleThroughPass = builder.finalize(); + applySwapAbsorb(moduleThroughPass); + + ASSERT_EQ(q11, ((IdOp)q03.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q01, ((IdOp)q13.getDefiningOp()).getInputQubit(0)); +} + +TEST_F(SwapAbsorbPassTest, PassAbsorbsTwoDependentSwaps) { + + qco::QCOProgramBuilder builder(context.get()); + builder.initialize(); + + const auto q00 = builder.staticQubit(0); + const auto q10 = builder.staticQubit(1); + const auto q20 = builder.staticQubit(2); + + const auto [q01, q11] = builder.swap(q00, q10); + const auto [q12, q21] = builder.swap(q11, q20); + + const auto q02 = builder.id(q01); + const auto q13 = builder.id(q12); + const auto q22 = builder.id(q21); + + builder.sink(q02); + builder.sink(q13); + builder.sink(q22); + + auto moduleThroughPass = builder.finalize(); + applySwapAbsorb(moduleThroughPass); + + ASSERT_EQ(q20, ((IdOp)q13.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q00, ((IdOp)q22.getDefiningOp()).getInputQubit(0)); + ASSERT_EQ(q10, ((IdOp)q02.getDefiningOp()).getInputQubit(0)); +}