Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
00ad822
Add SWAPAbsorption Pass
MatthiasReumann May 4, 2026
77250d1
🎨 pre-commit fixes
pre-commit-ci[bot] May 4, 2026
515e6bf
Fix linting
MatthiasReumann May 4, 2026
f6e1c39
Merge branch 'feat/swap-absorption-pass' of https://github.com/munich…
MatthiasReumann May 4, 2026
b0474ff
🎨 pre-commit fixes
pre-commit-ci[bot] May 4, 2026
10302c5
remove SwapAbsorbtion::insertStatics()
jmoosburger May 4, 2026
da710c7
change unittests to use static qubits
jmoosburger May 4, 2026
d793c73
SwapAbsorbtion: PassReordersTwoQubitCircuitWithLeadingSwap
jmoosburger May 5, 2026
de89411
🎨 pre-commit fixes
pre-commit-ci[bot] May 6, 2026
3dbe1a3
swap-absorb: PassAbsorbsTwoIndependentSwaps
jmoosburger May 6, 2026
eea2685
Merge branch 'main' into feat/swap-absorption-pass
jmoosburger May 27, 2026
3b2af5d
remove unused includes
jmoosburger May 27, 2026
6312c37
PassAbsorbsSwapWithLeadingSingleQubitGates
jmoosburger May 27, 2026
1a884d8
🎨 pre-commit fixes
pre-commit-ci[bot] May 28, 2026
c4bb64f
PassAbsorbsTwoDependentSwaps
jmoosburger May 28, 2026
a779361
🎨 pre-commit fixes
pre-commit-ci[bot] May 28, 2026
3bf15be
fix some lint warnings
jmoosburger May 29, 2026
ac2a49e
Merge remote-tracking branch 'refs/remotes/origin/feat/swap-absorptio…
jmoosburger May 29, 2026
03917b6
Fix linter error
denialhaag May 29, 2026
8948b78
add pass summary and description
jmoosburger Jun 1, 2026
982efb7
🎨 pre-commit fixes
pre-commit-ci[bot] Jun 1, 2026
343387d
remove empty pass options
jmoosburger Jun 2, 2026
a108a9b
remove unnecessary comment
jmoosburger Jun 2, 2026
5b9c7f7
enhance swap absobrtion according review
jmoosburger Jun 2, 2026
44dfccc
Merge branch 'main' into feat/swap-absorption-pass
MatthiasReumann Jun 3, 2026
4d762f5
Update CHANGELOG.md [skip ci]
MatthiasReumann Jun 3, 2026
806a4a9
Update pass tablegen
MatthiasReumann Jun 3, 2026
4ef7620
🎨 pre-commit fixes
pre-commit-ci[bot] Jun 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**])
Expand Down Expand Up @@ -402,6 +403,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool

<!-- PR links -->

[#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
Expand Down Expand Up @@ -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

<!-- General links -->

Expand Down
14 changes: 14 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
88 changes: 88 additions & 0 deletions mlir/lib/Dialect/QCO/Transforms/Optimizations/SwapAbsorption.cpp
Original file line number Diff line number Diff line change
@@ -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 <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/IR/BuiltinOps.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/Support/LLVM.h>
#include <mlir/Support/WalkResult.h>

#include <utility>

namespace mlir::qco {
#define GEN_PASS_DEF_SWAPABSORPTION
#include "mlir/Dialect/QCO/Transforms/Passes.h.inc"

namespace {
struct SwapAbsorption : impl::SwapAbsorptionBase<SwapAbsorption> {
public:
using SwapAbsorptionBase::SwapAbsorptionBase;

protected:
void runOnOperation() override {
ModuleOp anchor = getOperation();
IRRewriter rewriter(&getContext());

for (auto func : anchor.getOps<func::FuncOp>()) {
SmallVector<SWAPOp> readyToAbsorb;
SmallVector<WireIterator> wires;
do {
wires.clear();
for (auto op : func.getOps<StaticOp>()) {
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<WireIterator> wires,
SmallVector<SWAPOp>& readyToAbsorb) {
std::ignore = walkProgramGraph<WireDirection::Forward>(
wires, [&](const ReadyRange& ready, ReleasedOps& released) {
for (const auto& [op, indices] : ready) {
if (isa<SWAPOp>(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
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <gtest/gtest.h>
#include <llvm/Support/LogicalResult.h>
#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/Dialect/Func/IR/FuncOps.h>
#include <mlir/IR/BuiltinOps.h>
#include <mlir/IR/DialectRegistry.h>
#include <mlir/IR/OperationSupport.h>
#include <mlir/IR/OwningOpRef.h>
#include <mlir/IR/Value.h>
#include <mlir/Pass/PassManager.h>
#include <mlir/Support/LLVM.h>

#include <cassert>
#include <memory>

using namespace mlir;
using namespace mlir::qco;

namespace {

class SwapAbsorbPassTest : public testing::Test {

protected:
void SetUp() override {
DialectRegistry registry;
registry.insert<qco::QCODialect, arith::ArithDialect, func::FuncDialect>();
context = std::make_unique<MLIRContext>();
context->appendDialectRegistry(registry);
context->loadAllAvailableDialects();
}

static void applySwapAbsorb(OwningOpRef<ModuleOp>& moduleOp) {
PassManager pm(moduleOp->getContext());
pm.addPass(qco::createSwapAbsorption());
auto res = pm.run(*moduleOp);

ASSERT_TRUE(succeeded(res));
}

std::unique_ptr<MLIRContext> 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));
}