From 3a3ae93a1fc8a7fa92543bf3945f0e9d05160c3d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 27 Aug 2025 18:11:51 +0200 Subject: [PATCH 01/22] :sparkles: Implement swap reconstruction MLIR pass --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 1 + .../mlir/Dialect/MQTOpt/Transforms/Passes.td | 8 ++ .../MQTOpt/Transforms/SwapReconstruction.cpp | 45 ++++++ .../Transforms/SwapReconstructionPattern.cpp | 133 ++++++++++++++++++ .../Transforms/swap-reconstruction.mlir | 41 ++++++ 5 files changed, 228 insertions(+) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp create mode 100644 mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index 23ef1c3114..b0bc72a347 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -28,6 +28,7 @@ namespace mqt::ir::opt { void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns); +void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkPushPatterns(mlir::RewritePatternSet& patterns); void populateToQuantumComputationPatterns(mlir::RewritePatternSet& patterns, diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 48fbd4bd52..09cf19bd6e 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -64,6 +64,14 @@ def ElidePermutations : Pass<"elide-permutations", "mlir::ModuleOp"> { }]; } +def SwapReconstruction : Pass<"swap-reconstruction", "mlir::ModuleOp"> { + let summary = "This pass searches for CNOTs that are equivalent to a SWAP and converts them into a SWAP gate."; + let description = [{ + Multiple CNOTs that are equivalent to a SWAP are merge and converted into a SWAP gate. + Additionally, it may insert two self-cancelling CNOTs to allow for the insertion of a SWAP. + }]; +} + def QuantumSinkPass : Pass<"quantum-sink", "mlir::ModuleOp"> { let summary = "This pass attempts to push down operations into branches for possible optimizations."; let description = [{ diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp new file mode 100644 index 0000000000..642abc4521 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/Common/Compat.h" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" + +#include +#include +#include + +namespace mqt::ir::opt { + +#define GEN_PASS_DEF_SWAPRECONSTRUCTION +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" + +/** + * @brief This pattern attempts to merge consecutive rotation gates. + */ +struct SwapReconstruction final + : impl::SwapReconstructionBase { + + void runOnOperation() override { + // Get the current operation being operated on. + auto op = getOperation(); + auto* ctx = &getContext(); + + // Define the set of patterns to use. + mlir::RewritePatternSet patterns(ctx); + populateSwapReconstructionPatterns(patterns); + + // Apply patterns in an iterative and greedy manner. + if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp new file mode 100644 index 0000000000..4449d262a4 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/IR/BuiltinAttributes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mqt::ir::opt { +/** + * @brief This pattern attempts to find three CNOT gates next to each other + * which are equivalent to a SWAP. These gates will be removed and replaced by a + * SWAP operation. + */ +struct SwapReconstructionPattern final : mlir::OpRewritePattern { + + explicit SwapReconstructionPattern(mlir::MLIRContext* context) + : OpRewritePattern(context) {} + + /** + * @brief Checks if two consecutive gates have reversed control and target + * qubits. + * + * ┌───┐ + * a: ──■──┤ X ├ + * ┌─┴─┐└─┬─┘ + * b: ┤ X ├──■── + * └───┘ + * + * @param a The first gate. + * @param b The second gate. + * @return True if the gates match the pattern described above, otherwise + * false. + */ + [[nodiscard]] static bool isReverseCNotPattern(XOp& a, XOp& b) { + // TODO: allow negative ctrl qubits (at least for first CNOT)? + auto ctrlQubitsA = a.getPosCtrlOutQubits(); + auto ctrlQubitsB = b.getPosCtrlInQubits(); + auto targetQubitsA = a.getOutQubits(); + auto targetQubitsB = b.getInQubits(); + + return ctrlQubitsA.size() == 1 && ctrlQubitsB.size() == 1 && + ctrlQubitsA == targetQubitsB && targetQubitsA == ctrlQubitsB; + } + + mlir::LogicalResult + matchAndRewrite(XOp op, mlir::PatternRewriter& rewriter) const override { + // operation can only be part of a series of CNOTs that are equivalent to a + // SWAP if it has exactly one control qubit + auto ctrlQubits = op.getPosCtrlInQubits(); + if (ctrlQubits.size() != 1) { + return mlir::failure(); + } + + auto& firstCNot = op; + if (auto secondCNot = findCandidate(firstCNot)) { + if (auto thirdCNot = findCandidate(*secondCNot)) { + replaceWithSwap(rewriter, firstCNot, *secondCNot, *thirdCNot); + } + } + + return mlir::failure(); + } + + /** + * @brief Find a user of the given operation for which isReverseCNotPattern() + * with the given operation is true. + */ + static std::optional findCandidate(XOp& op) { + for (auto* user : op->getUsers()) { + if (auto cnot = llvm::dyn_cast(user)) { + if (isReverseCNotPattern(op, cnot)) { + return cnot; + } + } + } + return std::nullopt; + } + + /** + * @brief Replace the three given XOp by a single SWAPOp. + */ + static void replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& a, XOp& b, + XOp& c) { + auto inQubits = a.getInQubits(); + assert(!inQubits.empty()); + auto qubitType = inQubits.front().getType(); + + auto newSwapLocation = a->getLoc(); + auto newSwapInQubits = a.getAllInQubits(); + + auto newSwap = rewriter.create( + newSwapLocation, mlir::TypeRange{qubitType, qubitType}, + mlir::TypeRange{}, mlir::TypeRange{}, mlir::DenseF64ArrayAttr{}, + mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, newSwapInQubits, + mlir::ValueRange{}, mlir::ValueRange{}); + + auto newSwapOutQubits = newSwap.getOutQubits(); + assert(newSwapOutQubits.size() == 2); + + // replace three operations by single swap; perform swap on output qubits + rewriter.replaceOp(c, {newSwapOutQubits[1], newSwapOutQubits[0]}); + rewriter.eraseOp(b); + rewriter.eraseOp(a); + } +}; + +/** + * @brief Populates the given pattern set with the `MergeRotationGatesPattern`. + * + * @param patterns The pattern set to populate. + */ +void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns) { + patterns.add(patterns.getContext()); +} + +} // namespace mqt::ir::opt diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir new file mode 100644 index 0000000000..14ac8b548a --- /dev/null +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s -split-input-file --swap-reconstruction | FileCheck %s + +// ----- +// This test checks that consecutive CNOT gates which match a SWAP gate are merged correctly. + +module { + // CHECK-LABEL: func.func @testSingleSwapReconstruction + func.func @testSingleSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // ============================ Check for operations that should be inserted ============================ + // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] : !mqtopt.Qubit, !mqtopt.Qubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() + + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + + return + } +} From 262a04b937f7fb35029a6ed2412658f6f4061166 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 27 Aug 2025 18:36:14 +0200 Subject: [PATCH 02/22] more tests --- .../Transforms/swap-reconstruction.mlir | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 14ac8b548a..d04d3f7f9d 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -39,3 +39,39 @@ module { return } } + + +// ----- +// This test checks that consecutive CNOT gates with more than one control qubit are not merged. + +module { + // CHECK-LABEL: func.func @testTooManyControlsNoSwapReconstruction + func.func @testTooManyControlsNoSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should not be canceled =========================== + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q1_2:.*]], %[[Q02_2:.*]]:2 = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]], %[[Q2_0]] + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q02_2]]#0 ctrl %[[Q1_2]] + + // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q1_3]] + // CHECK: mqtopt.deallocQubit %[[Q02_2]]#1 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q2_1 + + return + } +} From cc52d8a9b5dcf463876a57b915460f7a286ffcbf Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 28 Aug 2025 19:30:27 +0200 Subject: [PATCH 03/22] first attempt --- .../Transforms/SwapReconstructionPattern.cpp | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 4449d262a4..0a92291bb4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -70,9 +70,14 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto& firstCNot = op; if (auto secondCNot = findCandidate(firstCNot)) { - if (auto thirdCNot = findCandidate(*secondCNot)) { - replaceWithSwap(rewriter, firstCNot, *secondCNot, *thirdCNot); + auto thirdCNot = findCandidate(*secondCNot); + if (!thirdCNot) { + // insert self-cancelling CNOT with same control/target as second CNOT + // before first CNOT + thirdCNot = insertSelfCancellingCNot(rewriter, *secondCNot); } + replaceWithSwap(rewriter, firstCNot, *secondCNot, *thirdCNot); + return mlir::success(); } return mlir::failure(); @@ -93,6 +98,43 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } + static XOp duplicateXOp(mlir::PatternRewriter& rewriter, XOp& previousOp, + bool swapTargetControl) { + auto resultType = previousOp.getOutQubits().getType(); + auto posCtrlResultType = previousOp.getPosCtrlOutQubits().getType(); + auto negCtrlResultType = previousOp.getNegCtrlOutQubits().getType(); + auto input = previousOp.getOutQubits(); + auto posCtrlInput = previousOp.getPosCtrlInQubits(); + auto negCtrlInput = previousOp.getNegCtrlInQubits(); + + rewriter.setInsertionPointAfter(previousOp); + if (swapTargetControl) { + return rewriter.create( + previousOp->getLoc(), resultType, posCtrlResultType, + negCtrlResultType, mlir::DenseF64ArrayAttr{}, + mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, posCtrlInput, input, + negCtrlInput); + } + return rewriter.create( + previousOp->getLoc(), resultType, posCtrlResultType, negCtrlResultType, + mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + mlir::ValueRange{}, input, posCtrlInput, negCtrlInput); + } + + static XOp insertSelfCancellingCNot(mlir::PatternRewriter& rewriter, + XOp& previousOp) { + auto previousUsers = previousOp->getUsers(); + + auto firstOp = duplicateXOp(rewriter, previousOp, true); + auto secondOp = duplicateXOp(rewriter, firstOp, false); + + rewriter.replaceAllOpUsesWith(previousOp, secondOp); + + // return first inserted operation which will be used in the swap + // reconstruction + return firstOp; + } + /** * @brief Replace the three given XOp by a single SWAPOp. */ From a27b480ea50196226c8762492181b9a7973eed4b Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 28 Aug 2025 20:16:24 +0200 Subject: [PATCH 04/22] second attempt --- .../Transforms/SwapReconstructionPattern.cpp | 69 +++++------------- .../Transforms/swap-reconstruction.mlir | 72 +++++++++++++++++++ 2 files changed, 89 insertions(+), 52 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 0a92291bb4..6fae309916 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -70,19 +70,24 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto& firstCNot = op; if (auto secondCNot = findCandidate(firstCNot)) { - auto thirdCNot = findCandidate(*secondCNot); - if (!thirdCNot) { - // insert self-cancelling CNOT with same control/target as second CNOT - // before first CNOT - thirdCNot = insertSelfCancellingCNot(rewriter, *secondCNot); + if (auto thirdCNot = findCandidate(*secondCNot)) { + replaceWithSwap(rewriter, *thirdCNot); + eraseOperation(rewriter, *secondCNot); + eraseOperation(rewriter, firstCNot); + } else { + replaceWithSwap(rewriter, *secondCNot); } - replaceWithSwap(rewriter, firstCNot, *secondCNot, *thirdCNot); return mlir::success(); } return mlir::failure(); } + static void eraseOperation(mlir::PatternRewriter& rewriter, XOp& op) { + rewriter.replaceAllOpUsesWith(op, op.getAllInQubits()); + rewriter.eraseOp(op); + } + /** * @brief Find a user of the given operation for which isReverseCNotPattern() * with the given operation is true. @@ -98,54 +103,16 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } - static XOp duplicateXOp(mlir::PatternRewriter& rewriter, XOp& previousOp, - bool swapTargetControl) { - auto resultType = previousOp.getOutQubits().getType(); - auto posCtrlResultType = previousOp.getPosCtrlOutQubits().getType(); - auto negCtrlResultType = previousOp.getNegCtrlOutQubits().getType(); - auto input = previousOp.getOutQubits(); - auto posCtrlInput = previousOp.getPosCtrlInQubits(); - auto negCtrlInput = previousOp.getNegCtrlInQubits(); - - rewriter.setInsertionPointAfter(previousOp); - if (swapTargetControl) { - return rewriter.create( - previousOp->getLoc(), resultType, posCtrlResultType, - negCtrlResultType, mlir::DenseF64ArrayAttr{}, - mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, posCtrlInput, input, - negCtrlInput); - } - return rewriter.create( - previousOp->getLoc(), resultType, posCtrlResultType, negCtrlResultType, - mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, - mlir::ValueRange{}, input, posCtrlInput, negCtrlInput); - } - - static XOp insertSelfCancellingCNot(mlir::PatternRewriter& rewriter, - XOp& previousOp) { - auto previousUsers = previousOp->getUsers(); - - auto firstOp = duplicateXOp(rewriter, previousOp, true); - auto secondOp = duplicateXOp(rewriter, firstOp, false); - - rewriter.replaceAllOpUsesWith(previousOp, secondOp); - - // return first inserted operation which will be used in the swap - // reconstruction - return firstOp; - } - /** * @brief Replace the three given XOp by a single SWAPOp. */ - static void replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& a, XOp& b, - XOp& c) { - auto inQubits = a.getInQubits(); + static void replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op) { + auto inQubits = op.getInQubits(); assert(!inQubits.empty()); auto qubitType = inQubits.front().getType(); - auto newSwapLocation = a->getLoc(); - auto newSwapInQubits = a.getAllInQubits(); + auto newSwapLocation = op->getLoc(); + auto newSwapInQubits = op.getAllInQubits(); auto newSwap = rewriter.create( newSwapLocation, mlir::TypeRange{qubitType, qubitType}, @@ -156,10 +123,8 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto newSwapOutQubits = newSwap.getOutQubits(); assert(newSwapOutQubits.size() == 2); - // replace three operations by single swap; perform swap on output qubits - rewriter.replaceOp(c, {newSwapOutQubits[1], newSwapOutQubits[0]}); - rewriter.eraseOp(b); - rewriter.eraseOp(a); + // replace operation by swap; perform swap on output qubits + rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); } }; diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index d04d3f7f9d..da85c04a93 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -56,6 +56,9 @@ module { // CHECK: %[[Q1_2:.*]], %[[Q02_2:.*]]:2 = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]], %[[Q2_0]] // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q02_2]]#0 ctrl %[[Q1_2]] + // ========================== Check for operations that should not be inserted =========================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_3]] // CHECK: mqtopt.deallocQubit %[[Q02_2]]#1 @@ -75,3 +78,72 @@ module { return } } + + +// ----- +// This test checks that consecutive CNOT gates with same target and control are not merged. + +module { + // CHECK-LABEL: func.func @testWrongPatternNoSwapReconstruction + func.func @testWrongPatternNoSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q0_1]] ctrl %[[Q1_1]] + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q0_2]] ctrl %[[Q1_2]] + + // ========================== Check for operations that should not be inserted =========================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + + // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q1_3]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + + return + } +} + + +// ----- +// This test checks that two CNOT gates are merged by inserting self-cancelling CNOT gates. + +module { + // CHECK-LABEL: func.func @testAdvancedSwapReconstruction + func.func @testAdvancedSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] + + // ========================== Check for operations that should be inserted ============================== + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[ANY:.*]] = mqtopt.swap() + + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 + + return + } +} From 7bf4ea5f40aee088884be722de4e2b57637d8288 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 29 Aug 2025 13:29:09 +0200 Subject: [PATCH 05/22] third attempt --- .../Transforms/SwapReconstructionPattern.cpp | 44 +++++++++++++++---- .../Transforms/swap-reconstruction.mlir | 8 ++-- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 6fae309916..c2633fb8d7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -12,8 +12,9 @@ #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include "mlir/IR/BuiltinAttributes.h" +#include #include -#include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include namespace mqt::ir::opt { /** @@ -28,10 +30,11 @@ namespace mqt::ir::opt { * which are equivalent to a SWAP. These gates will be removed and replaced by a * SWAP operation. */ +template struct SwapReconstructionPattern final : mlir::OpRewritePattern { explicit SwapReconstructionPattern(mlir::MLIRContext* context) - : OpRewritePattern(context) {} + : OpRewritePattern(context, calculateBenefit()) {} /** * @brief Checks if two consecutive gates have reversed control and target @@ -71,13 +74,25 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto& firstCNot = op; if (auto secondCNot = findCandidate(firstCNot)) { if (auto thirdCNot = findCandidate(*secondCNot)) { + // ┌───┐ + // a: ──■──┤ X ├──■── a a: ──╳── b + // ┌─┴─┐└─┬─┘┌─┴─┐ => | + // b: ┤ X ├──■──┤ X ├ b b: ──╳── a + // └───┘ └───┘ replaceWithSwap(rewriter, *thirdCNot); eraseOperation(rewriter, *secondCNot); eraseOperation(rewriter, firstCNot); - } else { - replaceWithSwap(rewriter, *secondCNot); + return mlir::success(); + } + if constexpr (advancedSwapReconstruction) { + // ┌───┐ ┌───┐ ┌───┐ + // a: ──■──┤ X ├ a a: ──■──┤ X ├──■────■── a a: ──╳──┤ X ├ b + // ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | └─┬─┘ + // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a + // └───┘ └───┘ └───┘└───┘ + replaceWithSwap(rewriter, firstCNot); + return mlir::success(); } - return mlir::success(); } return mlir::failure(); @@ -92,7 +107,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { * @brief Find a user of the given operation for which isReverseCNotPattern() * with the given operation is true. */ - static std::optional findCandidate(XOp& op) { + [[nodiscard]] static std::optional findCandidate(XOp& op) { for (auto* user : op->getUsers()) { if (auto cnot = llvm::dyn_cast(user)) { if (isReverseCNotPattern(op, cnot)) { @@ -106,7 +121,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { /** * @brief Replace the three given XOp by a single SWAPOp. */ - static void replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op) { + static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op) { auto inQubits = op.getInQubits(); assert(!inQubits.empty()); auto qubitType = inQubits.front().getType(); @@ -114,6 +129,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto newSwapLocation = op->getLoc(); auto newSwapInQubits = op.getAllInQubits(); + rewriter.setInsertionPointAfter(op); auto newSwap = rewriter.create( newSwapLocation, mlir::TypeRange{qubitType, qubitType}, mlir::TypeRange{}, mlir::TypeRange{}, mlir::DenseF64ArrayAttr{}, @@ -125,6 +141,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // replace operation by swap; perform swap on output qubits rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); + return newSwap; + } + + static int calculateBenefit() { + // prefer simple swap reconstruction + return advancedSwapReconstruction ? 1 : 10; } }; @@ -134,7 +156,13 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { * @param patterns The pattern set to populate. */ void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns) { - patterns.add(patterns.getContext()); + // match two different patterns for simple (3 CNOT -> 1 SWAP) and advanced + // reconstruction (2 CNOT -> 1 SWAP + 1 CNOT) to avoid applying an advanced + // reconstruction where a simple one would have been possible; an alternative + // would be to not check both the users and the previous operations to see if + // it is three CNOTs in the correct configuration + patterns.add>(patterns.getContext()); + patterns.add>(patterns.getContext()); } } // namespace mqt::ir::opt diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index da85c04a93..744e20763a 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -129,11 +129,11 @@ module { // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK: %[[ANY:.*]] = mqtopt.swap() + // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]] %[Q1_0] + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q01_1]]#1 ctrl %[[Q01_1]]#0 - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // CHECK: mqtopt.deallocQubit %[[Q1_2]] + // CHECK: mqtopt.deallocQubit %[[Q0_2]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit From 383ae20bc8a02c63682fcb83fc45e046e780eed1 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 29 Aug 2025 14:58:19 +0200 Subject: [PATCH 06/22] fifth attempt --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 1 + .../MQTOpt/Transforms/SwapReconstruction.cpp | 9 ++++ .../Transforms/SwapReconstructionPattern.cpp | 51 ++++++++++++++++--- .../Transforms/swap-reconstruction.mlir | 14 ++--- 4 files changed, 63 insertions(+), 12 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index b0bc72a347..abff88c84a 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -29,6 +29,7 @@ void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns); void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); +void populateAdvancedSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkPushPatterns(mlir::RewritePatternSet& patterns); void populateToQuantumComputationPatterns(mlir::RewritePatternSet& patterns, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp index 642abc4521..3446283ae2 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp @@ -39,6 +39,15 @@ struct SwapReconstruction final if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { signalPassFailure(); } + + // Define the second set of patterns to be used. + patterns.clear(); + populateAdvancedSwapReconstructionPatterns(patterns); + + // Apply patterns in an iterative and greedy manner. + if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { + signalPassFailure(); + } } }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index c2633fb8d7..dec96a6ac7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -91,6 +91,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a // └───┘ └───┘ └───┘└───┘ replaceWithSwap(rewriter, firstCNot); + // swapTargetControl(rewriter, *secondCNot); return mlir::success(); } } @@ -140,29 +141,67 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { assert(newSwapOutQubits.size() == 2); // replace operation by swap; perform swap on output qubits - rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); + rewriter.replaceAllOpUsesWith(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); + rewriter.eraseOp(op); return newSwap; } - static int calculateBenefit() { - // prefer simple swap reconstruction - return advancedSwapReconstruction ? 1 : 10; + static void swapTargetControl(mlir::PatternRewriter& rewriter, XOp& op) { + auto location = op->getLoc(); + + rewriter.setInsertionPointAfter(op); + auto newCNot = rewriter.create( + location, op.getPosCtrlOutQubits().getType(), + op.getOutQubits().getType(), + op.getNegCtrlOutQubits().getType(), mlir::DenseF64ArrayAttr{}, + mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, + op.getPosCtrlInQubits(), op.getInQubits(), op.getNegCtrlInQubits()); + + // rewriter.replaceOp(op, {newCNot.getPosCtrlOutQubits()[0], newCNot.getOutQubits()[0]}); + rewriter.replaceAllOpUsesWith(op, newCNot); + rewriter.eraseOp(op); + // assert(op.getInQubits().size() == 1); + // assert(op.getPosCtrlInQubits().size() == 1); + // assert(op.getNegCtrlInQubits().size() == 0); + // rewriter.modifyOpInPlace(op, [&]() { + // auto firstOperand = op.getOperand(0); + // auto secondOperand = op.getOperand(1); + + // op.setOperand(0, secondOperand); + // op.setOperand(1, firstOperand); + // }); + } + + static mlir::PatternBenefit calculateBenefit() { + // prefer simple swap reconstruction + return advancedSwapReconstruction ? 10 : 100; } }; /** - * @brief Populates the given pattern set with the `MergeRotationGatesPattern`. + * @brief Populates the given pattern set with the simple + * `SwapReconstructionPattern`. * * @param patterns The pattern set to populate. */ void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns) { + patterns.add>(patterns.getContext()); +} + +/** + * @brief Populates the given pattern set with the advanced + * `SwapReconstructionPattern`. + * + * @param patterns The pattern set to populate. + */ +void populateAdvancedSwapReconstructionPatterns( + mlir::RewritePatternSet& patterns) { // match two different patterns for simple (3 CNOT -> 1 SWAP) and advanced // reconstruction (2 CNOT -> 1 SWAP + 1 CNOT) to avoid applying an advanced // reconstruction where a simple one would have been possible; an alternative // would be to not check both the users and the previous operations to see if // it is three CNOTs in the correct configuration patterns.add>(patterns.getContext()); - patterns.add>(patterns.getContext()); } } // namespace mqt::ir::opt diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 744e20763a..2d0d47ab0e 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -125,12 +125,12 @@ module { // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // ========================== Check for operations that should be canceled ============================== - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] + // CHECK-NOT: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]] %[Q1_0] - // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q01_1]]#1 ctrl %[[Q01_1]]#0 + // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] + // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#1 ctrl %[[Q01_1]]#0 // CHECK: mqtopt.deallocQubit %[[Q1_2]] // CHECK: mqtopt.deallocQubit %[[Q0_2]] @@ -140,10 +140,12 @@ module { %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_3, %q0_3 = mqtopt.h() %q1_2 ctrl %q0_2: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_2 + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 return } } + From fd8bfc4f84f8349809da824663a2952b4508bf30 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 29 Aug 2025 16:31:17 +0200 Subject: [PATCH 07/22] first working draft --- .../MQTOpt/Transforms/SwapReconstructionPattern.cpp | 12 +++++------- .../MQTOpt/Transforms/swap-reconstruction.mlir | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index dec96a6ac7..a3cfb9b876 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -91,7 +91,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a // └───┘ └───┘ └───┘└───┘ replaceWithSwap(rewriter, firstCNot); - // swapTargetControl(rewriter, *secondCNot); + swapTargetControl(rewriter, *secondCNot); return mlir::success(); } } @@ -141,8 +141,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { assert(newSwapOutQubits.size() == 2); // replace operation by swap; perform swap on output qubits - rewriter.replaceAllOpUsesWith(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); - rewriter.eraseOp(op); + rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); return newSwap; } @@ -155,11 +154,10 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { op.getOutQubits().getType(), op.getNegCtrlOutQubits().getType(), mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, - op.getPosCtrlInQubits(), op.getInQubits(), op.getNegCtrlInQubits()); + op.getInQubits(), op.getPosCtrlInQubits(), op.getNegCtrlInQubits()); - // rewriter.replaceOp(op, {newCNot.getPosCtrlOutQubits()[0], newCNot.getOutQubits()[0]}); - rewriter.replaceAllOpUsesWith(op, newCNot); - rewriter.eraseOp(op); + rewriter.replaceOp(op, {newCNot.getPosCtrlOutQubits()[0], newCNot.getOutQubits()[0]}); + // rewriter.replaceOp(op, newCNot); // assert(op.getInQubits().size() == 1); // assert(op.getPosCtrlInQubits().size() == 1); // assert(op.getNegCtrlInQubits().size() == 0); diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 2d0d47ab0e..85ae62eb4a 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -140,7 +140,7 @@ module { %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_3, %q0_3 = mqtopt.h() %q1_2 ctrl %q0_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_3, %q0_3 = mqtopt.h() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_3 mqtopt.deallocQubit %q1_3 From 591d132d00aa3599cc1a21ea76d35a9ff05ab6ad Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 29 Aug 2025 18:47:31 +0200 Subject: [PATCH 08/22] fix swap logic (hopefully) --- .../Transforms/SwapReconstructionPattern.cpp | 52 +++++++++++-------- .../Transforms/swap-reconstruction.mlir | 12 ++--- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index a3cfb9b876..8ed9a45c95 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -29,6 +29,9 @@ namespace mqt::ir::opt { * @brief This pattern attempts to find three CNOT gates next to each other * which are equivalent to a SWAP. These gates will be removed and replaced by a * SWAP operation. + * + * If advancedSwapReconstruction is set to true, this pattern will also add a + * SWAP gate for two CNOTs by inserting the third as two self-cancelling CNOTs. */ template struct SwapReconstructionPattern final : mlir::OpRewritePattern { @@ -91,6 +94,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a // └───┘ └───┘ └───┘└───┘ replaceWithSwap(rewriter, firstCNot); + // eraseOperation(rewriter, *secondCNot); swapTargetControl(rewriter, *secondCNot); return mlir::success(); } @@ -129,6 +133,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto newSwapLocation = op->getLoc(); auto newSwapInQubits = op.getAllInQubits(); + assert(newSwapInQubits.size() == 2); rewriter.setInsertionPointAfter(op); auto newSwap = rewriter.create( @@ -141,33 +146,34 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { assert(newSwapOutQubits.size() == 2); // replace operation by swap; perform swap on output qubits - rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); + // rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); + rewriter.replaceOp(op, newSwap); return newSwap; } static void swapTargetControl(mlir::PatternRewriter& rewriter, XOp& op) { - auto location = op->getLoc(); - - rewriter.setInsertionPointAfter(op); - auto newCNot = rewriter.create( - location, op.getPosCtrlOutQubits().getType(), - op.getOutQubits().getType(), - op.getNegCtrlOutQubits().getType(), mlir::DenseF64ArrayAttr{}, - mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, - op.getInQubits(), op.getPosCtrlInQubits(), op.getNegCtrlInQubits()); - - rewriter.replaceOp(op, {newCNot.getPosCtrlOutQubits()[0], newCNot.getOutQubits()[0]}); - // rewriter.replaceOp(op, newCNot); - // assert(op.getInQubits().size() == 1); - // assert(op.getPosCtrlInQubits().size() == 1); - // assert(op.getNegCtrlInQubits().size() == 0); - // rewriter.modifyOpInPlace(op, [&]() { - // auto firstOperand = op.getOperand(0); - // auto secondOperand = op.getOperand(1); - - // op.setOperand(0, secondOperand); - // op.setOperand(1, firstOperand); - // }); + // auto location = op->getLoc(); + + // rewriter.setInsertionPointAfter(op); + // assert(op.getPosCtrlOutQubits().getType() == + // op.getOutQubits().getType()); auto newCNot = rewriter.create( + // location, op.getPosCtrlOutQubits().getType(), + // op.getOutQubits().getType(), op.getNegCtrlOutQubits().getType(), + // mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + // mlir::ValueRange{}, op.getPosCtrlInQubits(), op.getInQubits(), + // op.getNegCtrlInQubits()); + + // assert(newCNot.getOutQubits().size() == 1); + // assert(newCNot.getPosCtrlOutQubits().size() == 1); + // assert(newCNot.getNegCtrlOutQubits().empty()); + // rewriter.replaceOp( + // op, newCNot); + rewriter.modifyOpInPlace(op, [&]() { + assert(op.getInQubits().size() == 1); + assert(op.getPosCtrlInQubits().size() == 1); + assert(op.getNegCtrlInQubits().empty()); + op->setOperands({ op.getPosCtrlInQubits().front(), op.getInQubits().front() }); + }); } static mlir::PatternBenefit calculateBenefit() { diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 85ae62eb4a..33a9b601d0 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -23,8 +23,8 @@ module { // ========================== Check for operations that should be canceled ============================== // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -130,22 +130,20 @@ module { // ========================== Check for operations that should be inserted ============================== // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] - // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#1 ctrl %[[Q01_1]]#0 + // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 - // CHECK: mqtopt.deallocQubit %[[Q1_2]] // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_2]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_3, %q0_3 = mqtopt.h() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_3 - mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 return } } - From 4be381522ea934cfea0c901461b25548f3b3b40f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 29 Aug 2025 18:50:25 +0200 Subject: [PATCH 09/22] minor cleanup --- .../Transforms/SwapReconstructionPattern.cpp | 32 +++++++------------ 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 8ed9a45c95..d22ad22a79 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -94,7 +94,6 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a // └───┘ └───┘ └───┘└───┘ replaceWithSwap(rewriter, firstCNot); - // eraseOperation(rewriter, *secondCNot); swapTargetControl(rewriter, *secondCNot); return mlir::success(); } @@ -103,8 +102,13 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return mlir::failure(); } + /** + * @brief Remove given operation from circuit. + */ static void eraseOperation(mlir::PatternRewriter& rewriter, XOp& op) { + // "skip" operation by using its input directly as output rewriter.replaceAllOpUsesWith(op, op.getAllInQubits()); + // the operation has no more users now and can be deleted rewriter.eraseOp(op); } @@ -124,7 +128,10 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { } /** - * @brief Replace the three given XOp by a single SWAPOp. + * @brief Replace the given XOp by a single SWAPOp. + * + * @note The result qubits will NOT be swapped since that is already done + * implicitly by the CNOT pattern. */ static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op) { auto inQubits = op.getInQubits(); @@ -145,34 +152,17 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto newSwapOutQubits = newSwap.getOutQubits(); assert(newSwapOutQubits.size() == 2); - // replace operation by swap; perform swap on output qubits - // rewriter.replaceOp(op, {newSwapOutQubits[1], newSwapOutQubits[0]}); rewriter.replaceOp(op, newSwap); return newSwap; } static void swapTargetControl(mlir::PatternRewriter& rewriter, XOp& op) { - // auto location = op->getLoc(); - - // rewriter.setInsertionPointAfter(op); - // assert(op.getPosCtrlOutQubits().getType() == - // op.getOutQubits().getType()); auto newCNot = rewriter.create( - // location, op.getPosCtrlOutQubits().getType(), - // op.getOutQubits().getType(), op.getNegCtrlOutQubits().getType(), - // mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, - // mlir::ValueRange{}, op.getPosCtrlInQubits(), op.getInQubits(), - // op.getNegCtrlInQubits()); - - // assert(newCNot.getOutQubits().size() == 1); - // assert(newCNot.getPosCtrlOutQubits().size() == 1); - // assert(newCNot.getNegCtrlOutQubits().empty()); - // rewriter.replaceOp( - // op, newCNot); rewriter.modifyOpInPlace(op, [&]() { assert(op.getInQubits().size() == 1); assert(op.getPosCtrlInQubits().size() == 1); assert(op.getNegCtrlInQubits().empty()); - op->setOperands({ op.getPosCtrlInQubits().front(), op.getInQubits().front() }); + op->setOperands( + {op.getPosCtrlInQubits().front(), op.getInQubits().front()}); }); } From 1035f2d0388fbb1be938ef53799ba2fdb95ffdbe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 16:51:49 +0000 Subject: [PATCH 10/22] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index abff88c84a..aa92cbd97f 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -29,7 +29,8 @@ void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns); void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); -void populateAdvancedSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); +void populateAdvancedSwapReconstructionPatterns( + mlir::RewritePatternSet& patterns); void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkPushPatterns(mlir::RewritePatternSet& patterns); void populateToQuantumComputationPatterns(mlir::RewritePatternSet& patterns, From 14a953bde44aa91fb70f48995254a009499b4d45 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 3 Sep 2025 10:53:19 +0200 Subject: [PATCH 11/22] first snapshot --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 2 - .../MQTOpt/Transforms/SwapReconstruction.cpp | 12 +- .../Transforms/SwapReconstructionPattern.cpp | 240 +++++++++++------- .../Transforms/swap-reconstruction.mlir | 42 +++ 4 files changed, 188 insertions(+), 108 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index aa92cbd97f..b0bc72a347 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -29,8 +29,6 @@ void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns); void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns); -void populateAdvancedSwapReconstructionPatterns( - mlir::RewritePatternSet& patterns); void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkPushPatterns(mlir::RewritePatternSet& patterns); void populateToQuantumComputationPatterns(mlir::RewritePatternSet& patterns, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp index 3446283ae2..c873cc0052 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstruction.cpp @@ -21,7 +21,8 @@ namespace mqt::ir::opt { #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" /** - * @brief This pattern attempts to merge consecutive rotation gates. + * @brief This pass uses the swap reconstruction patterns to replace according + * CNOT patterns with SWAP gates. */ struct SwapReconstruction final : impl::SwapReconstructionBase { @@ -39,15 +40,6 @@ struct SwapReconstruction final if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { signalPassFailure(); } - - // Define the second set of patterns to be used. - patterns.clear(); - populateAdvancedSwapReconstructionPatterns(patterns); - - // Apply patterns in an iterative and greedy manner. - if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { - signalPassFailure(); - } } }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index d22ad22a79..f24677c53d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -30,73 +31,127 @@ namespace mqt::ir::opt { * which are equivalent to a SWAP. These gates will be removed and replaced by a * SWAP operation. * - * If advancedSwapReconstruction is set to true, this pattern will also add a - * SWAP gate for two CNOTs by inserting the third as two self-cancelling CNOTs. + * Examples: + * ┌───┐ ┌───┐ + * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── + * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ + * └───┘ └───┘ └───┘└───┘ └───┘ + * + * ──■────■── ──■────■────■────■── ──■────■── + * | ┌─┴─┐ | ┌─┴─┐ | | | | + * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── + * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ + * └───┘ └───┘ └───┘└───┘ └───┘ + * + * ──□────□── ──□────□────□────□── ──□────□── + * | ┌─┴─┐ | ┌─┴─┐ | | | | + * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── + * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ + * └───┘ └───┘ └───┘└───┘ └───┘ */ -template +template struct SwapReconstructionPattern final : mlir::OpRewritePattern { explicit SwapReconstructionPattern(mlir::MLIRContext* context) - : OpRewritePattern(context, calculateBenefit()) {} + : OpRewritePattern(context) {} /** - * @brief Checks if two consecutive gates have reversed control and target - * qubits. - * - * ┌───┐ - * a: ──■──┤ X ├ - * ┌─┴─┐└─┬─┘ - * b: ┤ X ├──■── - * └───┘ * - * @param a The first gate. - * @param b The second gate. - * @return True if the gates match the pattern described above, otherwise - * false. */ - [[nodiscard]] static bool isReverseCNotPattern(XOp& a, XOp& b) { - // TODO: allow negative ctrl qubits (at least for first CNOT)? - auto ctrlQubitsA = a.getPosCtrlOutQubits(); - auto ctrlQubitsB = b.getPosCtrlInQubits(); - auto targetQubitsA = a.getOutQubits(); - auto targetQubitsB = b.getInQubits(); - - return ctrlQubitsA.size() == 1 && ctrlQubitsB.size() == 1 && - ctrlQubitsA == targetQubitsB && targetQubitsA == ctrlQubitsB; + [[nodiscard]] static mlir::Value getCNotOutTarget(XOp& op) { + auto&& outQubits = op.getOutQubits(); + assert(outQubits.size() == 1); + return outQubits.front(); + } + + [[nodiscard]] static mlir::Value getCNotInTarget(XOp& op) { + auto&& inQubits = op.getInQubits(); + assert(inQubits.size() == 1); + return inQubits.front(); + } + + [[nodiscard]] static bool isCandidate(XOp& op, XOp& nextOp) { + auto&& opOutTarget = getCNotOutTarget(op); + auto&& nextInTarget = getCNotInTarget(nextOp); + auto&& opOutPosCtrlQubits = op.getPosCtrlOutQubits(); + auto&& nextInPosCtrlQubits = nextOp.getPosCtrlInQubits(); + auto&& opOutNegCtrlQubits = op.getNegCtrlOutQubits(); + auto&& nextInNegCtrlQubits = nextOp.getNegCtrlInQubits(); + + auto isSubset = [](auto&& set, auto&& subsetCandidate, + std::optional ignoredMismatch = + std::nullopt) -> bool { + if (subsetCandidate.size() > set.size()) { + return false; + } + + for (auto&& element : set) { + if (!llvm::is_contained(subsetCandidate, element) && + (!ignoredMismatch || *ignoredMismatch == element)) { + return false; + } + } + return true; + }; + + // llvm::SmallSetVector posCtrlDiff{ + // nextInPosCtrlQubits.begin(), nextInPosCtrlQubits.end()}; + // posCtrlDiff.set_subtract(opOutPosCtrlQubits); + // bool posCtrlMatches = posCtrlDiff.size() == 1 && posCtrlDiff.front() == + // opOutTarget; + + // bool negCtrlMatches = + // llvm::set_is_subset(opOutNegCtrlQubits, nextInNegCtrlQubits); + + bool targetIsPosCtrl = + llvm::is_contained(nextInPosCtrlQubits, opOutTarget) && + llvm::is_contained(opOutPosCtrlQubits, nextInTarget); + + bool posCtrlMatches = + isSubset(opOutPosCtrlQubits, nextInPosCtrlQubits, opOutTarget); + bool negCtrlMatches = isSubset(opOutNegCtrlQubits, nextInNegCtrlQubits); + + // TODO: early return possible for better performance? + if constexpr (matchControlledSwap) { + return targetIsPosCtrl && posCtrlMatches && negCtrlMatches; + } else { + return targetIsPosCtrl && opOutPosCtrlQubits.size() == 1 && + nextInPosCtrlQubits.size() == 1 && opOutNegCtrlQubits.empty() && + nextInNegCtrlQubits.empty(); + } } + /** + * @brief If pattern is applicable, perform MLIR rewrite. + * + * Steps: + * - Find CNOT with at least one control qubit (1st CNOT) + * - Check if it has adjacent CNOT with subset of control qubits (2nd CNOT) + * (- Theoretically place two CNOTs identical to 2nd CNOT on other side of + * 1st CNOT) + * - Replace 1st CNOT by SWAP with identical control qubits (also cancels + * out 2nd CNOT and one inserted CNOT); use target of 2nd CNOT as second + * target for the swap + * - Move 2nd CNOT to other side of SWAP (takes the place of the left-over + * inserted CNOT) + */ mlir::LogicalResult matchAndRewrite(XOp op, mlir::PatternRewriter& rewriter) const override { - // operation can only be part of a series of CNOTs that are equivalent to a - // SWAP if it has exactly one control qubit - auto ctrlQubits = op.getPosCtrlInQubits(); - if (ctrlQubits.size() != 1) { + // check if at least one positive control; rely on other pattern for + // negative control decomposition + if (op.getPosCtrlInQubits().empty()) { return mlir::failure(); } auto& firstCNot = op; if (auto secondCNot = findCandidate(firstCNot)) { - if (auto thirdCNot = findCandidate(*secondCNot)) { - // ┌───┐ - // a: ──■──┤ X ├──■── a a: ──╳── b - // ┌─┴─┐└─┬─┘┌─┴─┐ => | - // b: ┤ X ├──■──┤ X ├ b b: ──╳── a - // └───┘ └───┘ - replaceWithSwap(rewriter, *thirdCNot); - eraseOperation(rewriter, *secondCNot); - eraseOperation(rewriter, firstCNot); - return mlir::success(); - } - if constexpr (advancedSwapReconstruction) { - // ┌───┐ ┌───┐ ┌───┐ - // a: ──■──┤ X ├ a a: ──■──┤ X ├──■────■── a a: ──╳──┤ X ├ b - // ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | └─┬─┘ - // b: ┤ X ├──■── b b: ┤ X ├──■──┤ X ├┤ X ├ b b: ──╳────■── a - // └───┘ └───┘ └───┘└───┘ - replaceWithSwap(rewriter, firstCNot); - swapTargetControl(rewriter, *secondCNot); - return mlir::success(); - } + auto secondSwapTargetOut = getCNotInTarget(*secondCNot); + auto newSwap = replaceWithSwap(rewriter, firstCNot, secondSwapTargetOut); + // rewriter.moveOpAfter(*secondCNot, newSwap); + return mlir::success(); } return mlir::failure(); @@ -119,7 +174,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { [[nodiscard]] static std::optional findCandidate(XOp& op) { for (auto* user : op->getUsers()) { if (auto cnot = llvm::dyn_cast(user)) { - if (isReverseCNotPattern(op, cnot)) { + if (isCandidate(op, cnot)) { return cnot; } } @@ -127,75 +182,68 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } + static mlir::Value getCorrespondingInput(mlir::Operation* op, + mlir::Value out) { + for (auto&& result : op->getResults()) { + if (result == out) { + auto resultIndex = result.getResultNumber(); + return op->getOperand(resultIndex); + } + } + } + /** * @brief Replace the given XOp by a single SWAPOp. * * @note The result qubits will NOT be swapped since that is already done * implicitly by the CNOT pattern. */ - static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op) { - auto inQubits = op.getInQubits(); - assert(!inQubits.empty()); - auto qubitType = inQubits.front().getType(); + static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op, + const mlir::Value& secondTargetOut) { + auto firstTarget = getCNotInTarget(op); + auto secondTargetIn = getCorrespondingInput(op, secondTargetOut); + auto qubitType = firstTarget.getType(); auto newSwapLocation = op->getLoc(); - auto newSwapInQubits = op.getAllInQubits(); - assert(newSwapInQubits.size() == 2); + auto newSwapInQubits = {firstTarget, + secondTargetIn}; + llvm::SmallVector newSwapInPosCtrlQubits; + for (auto&& posCtrlInQubit : op.getPosCtrlInQubits()) { + if (posCtrlInQubit != secondTargetIn) { + newSwapInPosCtrlQubits.push_back(posCtrlInQubit); + } + } + auto newSwapInNegCtrlQubits = op.getNegCtrlInQubits(); + + auto newSwapOutType = + llvm::SmallVector{newSwapInQubits.size(), qubitType}; + auto newSwapOutPosCtrlType = + llvm::SmallVector{newSwapInPosCtrlQubits.size(), qubitType}; + auto newSwapOutNegCtrlType = op.getNegCtrlOutQubits().getType(); rewriter.setInsertionPointAfter(op); auto newSwap = rewriter.create( - newSwapLocation, mlir::TypeRange{qubitType, qubitType}, - mlir::TypeRange{}, mlir::TypeRange{}, mlir::DenseF64ArrayAttr{}, + newSwapLocation, newSwapOutType, newSwapOutPosCtrlType, + newSwapOutNegCtrlType, mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, newSwapInQubits, - mlir::ValueRange{}, mlir::ValueRange{}); - - auto newSwapOutQubits = newSwap.getOutQubits(); - assert(newSwapOutQubits.size() == 2); + newSwapInPosCtrlQubits, newSwapInNegCtrlQubits); rewriter.replaceOp(op, newSwap); return newSwap; } - - static void swapTargetControl(mlir::PatternRewriter& rewriter, XOp& op) { - rewriter.modifyOpInPlace(op, [&]() { - assert(op.getInQubits().size() == 1); - assert(op.getPosCtrlInQubits().size() == 1); - assert(op.getNegCtrlInQubits().empty()); - op->setOperands( - {op.getPosCtrlInQubits().front(), op.getInQubits().front()}); - }); - } - - static mlir::PatternBenefit calculateBenefit() { - // prefer simple swap reconstruction - return advancedSwapReconstruction ? 10 : 100; - } }; /** - * @brief Populates the given pattern set with the simple + * @brief Populates the given pattern set with the * `SwapReconstructionPattern`. * * @param patterns The pattern set to populate. */ void populateSwapReconstructionPatterns(mlir::RewritePatternSet& patterns) { - patterns.add>(patterns.getContext()); -} - -/** - * @brief Populates the given pattern set with the advanced - * `SwapReconstructionPattern`. - * - * @param patterns The pattern set to populate. - */ -void populateAdvancedSwapReconstructionPatterns( - mlir::RewritePatternSet& patterns) { - // match two different patterns for simple (3 CNOT -> 1 SWAP) and advanced - // reconstruction (2 CNOT -> 1 SWAP + 1 CNOT) to avoid applying an advanced - // reconstruction where a simple one would have been possible; an alternative - // would be to not check both the users and the previous operations to see if - // it is three CNOTs in the correct configuration - patterns.add>(patterns.getContext()); + patterns.add>(patterns.getContext()); + // only match controlled swap on full three CNOT pattern since this cannot be + // cancelled out by an elide permutations optimization + patterns.add>(patterns.getContext()); } } // namespace mqt::ir::opt diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 33a9b601d0..383318723a 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -147,3 +147,45 @@ module { return } } + + +// ----- +// This test checks that controlled CNOT gates are merged into a controlled SWAP gate. + +module { + // CHECK-LABEL: func.func @testControlledSwapReconstruction + func.func @testControlledSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q3_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK-NOT: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] + + // ========================== Check for operations that should be inserted ============================== + // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] + // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 + + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + %q3_0 = mqtopt.allocQubit + + %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0, %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + %q1_2, %q0_2, %q2_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1, %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 + mqtopt.deallocQubit %q2_2 + mqtopt.deallocQubit %q3_2 + + return + } +} From 7f3f8b224588c0e585147a7e530032ff1c1ed5b1 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 3 Sep 2025 14:41:25 +0200 Subject: [PATCH 12/22] non-working second snapshot --- .../Transforms/SwapReconstructionPattern.cpp | 76 +++++++++--- .../Transforms/swap-reconstruction.mlir | 111 ++++++++++++++++-- 2 files changed, 155 insertions(+), 32 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index f24677c53d..dad93b8362 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -128,7 +128,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { * @brief If pattern is applicable, perform MLIR rewrite. * * Steps: - * - Find CNOT with at least one control qubit (1st CNOT) + * - Find CNOT with at least one positive control qubit (1st CNOT) * - Check if it has adjacent CNOT with subset of control qubits (2nd CNOT) * (- Theoretically place two CNOTs identical to 2nd CNOT on other side of * 1st CNOT) @@ -149,9 +149,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto& firstCNot = op; if (auto secondCNot = findCandidate(firstCNot)) { auto secondSwapTargetOut = getCNotInTarget(*secondCNot); - auto newSwap = replaceWithSwap(rewriter, firstCNot, secondSwapTargetOut); - // rewriter.moveOpAfter(*secondCNot, newSwap); - return mlir::success(); + if (auto secondSwapTarget = + getCorrespondingInput(firstCNot, secondSwapTargetOut)) { + auto newSwap = replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); + relocateOperationAfter(rewriter, firstCNot, newSwap); + return mlir::success(); + } } return mlir::failure(); @@ -167,6 +170,34 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { rewriter.eraseOp(op); } + static void relocateOperationAfter(mlir::PatternRewriter& rewriter, XOp& op, + mlir::Operation* nextOp) { + auto&& currentOperands = op->getOperands(); + llvm::SmallVector newOperandsOp(currentOperands.size()); + llvm::SmallVector> + changedOperandsNextOp; + for (std::size_t i = 0; i < currentOperands.size(); ++i) { + auto&& currentOperand = currentOperands[i]; + if (auto newOperandIndex = + getCorrespondingOutputIndex(nextOp, currentOperand)) { + newOperandsOp[i] = nextOp->getResult(*newOperandIndex); + changedOperandsNextOp.push_back({*newOperandIndex, currentOperand}); + } + } + + // update nextOp to use the previous operand of the operation (since it will + // be moved behind it) + rewriter.modifyOpInPlace(nextOp, [&]() { + for (auto&& [changedIndex, newOperand] : changedOperandsNextOp) { + nextOp->setOperand(changedIndex, newOperand); + } + }); + // if nextOp uses an operand, use the corresponding output of nextOp as new + // operand + rewriter.modifyOpInPlace(op, [&]() { op->setOperands(newOperandsOp); }); + rewriter.moveOpBefore(nextOp, op); + } + /** * @brief Find a user of the given operation for which isReverseCNotPattern() * with the given operation is true. @@ -182,14 +213,26 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } - static mlir::Value getCorrespondingInput(mlir::Operation* op, - mlir::Value out) { + static std::optional getCorrespondingInput(mlir::Operation* op, + mlir::Value out) { for (auto&& result : op->getResults()) { if (result == out) { auto resultIndex = result.getResultNumber(); return op->getOperand(resultIndex); } } + return std::nullopt; + } + + static std::optional + getCorrespondingOutputIndex(mlir::Operation* op, mlir::Value in) { + for (auto&& opOperand : op->getOpOperands()) { + if (opOperand.get() == in) { + auto operandIndex = opOperand.getOperandNumber(); + return operandIndex; + } + } + return std::nullopt; } /** @@ -199,14 +242,11 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { * implicitly by the CNOT pattern. */ static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op, - const mlir::Value& secondTargetOut) { + const mlir::Value& secondTargetIn) { auto firstTarget = getCNotInTarget(op); - auto secondTargetIn = getCorrespondingInput(op, secondTargetOut); auto qubitType = firstTarget.getType(); - auto newSwapLocation = op->getLoc(); - auto newSwapInQubits = {firstTarget, - secondTargetIn}; + auto newSwapInQubits = {firstTarget, secondTargetIn}; llvm::SmallVector newSwapInPosCtrlQubits; for (auto&& posCtrlInQubit : op.getPosCtrlInQubits()) { if (posCtrlInQubit != secondTargetIn) { @@ -222,14 +262,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto newSwapOutNegCtrlType = op.getNegCtrlOutQubits().getType(); rewriter.setInsertionPointAfter(op); - auto newSwap = rewriter.create( - newSwapLocation, newSwapOutType, newSwapOutPosCtrlType, - newSwapOutNegCtrlType, mlir::DenseF64ArrayAttr{}, - mlir::DenseBoolArrayAttr{}, mlir::ValueRange{}, newSwapInQubits, - newSwapInPosCtrlQubits, newSwapInNegCtrlQubits); - - rewriter.replaceOp(op, newSwap); - return newSwap; + + return rewriter.replaceOpWithNewOp( + op, newSwapOutType, newSwapOutPosCtrlType, newSwapOutNegCtrlType, + mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + mlir::ValueRange{}, newSwapInQubits, newSwapInPosCtrlQubits, + newSwapInNegCtrlQubits); } }; diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 383318723a..e638b53881 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -19,22 +19,22 @@ module { // ============================ Check for operations that should be inserted ============================ // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 // ========================== Check for operations that should be canceled ============================== // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_2]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_3 - mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 return } @@ -165,21 +165,62 @@ module { // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] - // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 + // CHECK: %[[Q01_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#2 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#3 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + %q3_0 = mqtopt.allocQubit + + %q0_1, %q1_1, %q2_0, %q3_0 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_2, %q3_2 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_2 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 + mqtopt.deallocQubit %q2_2 + mqtopt.deallocQubit %q3_2 + + return + } +} + + +// ----- +// This test checks that controlled CNOT gates with simpler controls on the surrounding CNOT gates are merged into a controlled SWAP gate. + +module { + // CHECK-LABEL: func.func @testControlledSwapReconstructionSimplerSurroundingCNots + func.func @testControlledSwapReconstructionSimplerSurroundingCNots() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q3_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] - // CHECK: mqtopt.deallocQubit %[[ANY:.*]] + // ========================== Check for operations that should be inserted ============================== + // CHECK: %[[Q0123_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] negctrl %[[Q3_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#2 + // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#3 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit %q2_0 = mqtopt.allocQubit %q3_0 = mqtopt.allocQubit - %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0, %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit - %q1_2, %q0_2, %q2_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1, %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit, !mqtopt.Qubit + %q0_1, %q1_1, %q2_0, %q3_0 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_2, %q3_2 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_2 mqtopt.deallocQubit %q1_2 @@ -189,3 +230,47 @@ module { return } } + + +// ----- +// This test checks that controlled CNOT gates with differing controls are merged into a controlled SWAP gate. + +module { + // CHECK-LABEL: func.func @testNoControlledSwapReconstruction + func.func @testControlledSwapReconstruction() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q3_0:.*]] = mqtopt.allocQubit + + // ========================= Check for operations that should be kept as-is ============================= + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]], %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] negctrl %[[Q3_0]] + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]], %[[Q3_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] negctrl %[[Q3_1]] + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]], %[[Q2_2:.*]], %[[Q3_3:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] negctrl %[[Q3_0]] + + // ======================== Check for operations that should not be inserted ============================ + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + + // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q1_3]] + // CHECK: mqtopt.deallocQubit %[[Q2_2]] + // CHECK: mqtopt.deallocQubit %[[Q3_3]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + %q3_0 = mqtopt.allocQubit + + %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q1_2, %q0_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_2, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_1 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q2_2 + mqtopt.deallocQubit %q3_3 + + return + } +} + From c5d02c27335244e7a8ae65e5cdbc8bcfd55cb976 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 3 Sep 2025 17:13:38 +0200 Subject: [PATCH 13/22] kind of working snapshot --- .../Transforms/SwapReconstructionPattern.cpp | 26 +++++++++++-------- .../Transforms/swap-reconstruction.mlir | 26 +++++++++---------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index dad93b8362..2b0388f4f5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -152,7 +152,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { if (auto secondSwapTarget = getCorrespondingInput(firstCNot, secondSwapTargetOut)) { auto newSwap = replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); - relocateOperationAfter(rewriter, firstCNot, newSwap); + relocateOperationAfter(rewriter, newSwap, *secondCNot); return mlir::success(); } } @@ -170,21 +170,28 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { rewriter.eraseOp(op); } - static void relocateOperationAfter(mlir::PatternRewriter& rewriter, XOp& op, + static void relocateOperationAfter(mlir::PatternRewriter& rewriter, + mlir::Operation* op, mlir::Operation* nextOp) { - auto&& currentOperands = op->getOperands(); - llvm::SmallVector newOperandsOp(currentOperands.size()); + llvm::SmallVector newOperandsOp(op->getNumOperands()); llvm::SmallVector> changedOperandsNextOp; - for (std::size_t i = 0; i < currentOperands.size(); ++i) { - auto&& currentOperand = currentOperands[i]; + for (std::size_t i = 0; i < newOperandsOp.size(); ++i) { + auto&& currentOperand = op->getOperand(i); + ; + auto&& currentResult = op->getResult(i); if (auto newOperandIndex = - getCorrespondingOutputIndex(nextOp, currentOperand)) { + getCorrespondingOutputIndex(nextOp, currentResult)) { newOperandsOp[i] = nextOp->getResult(*newOperandIndex); changedOperandsNextOp.push_back({*newOperandIndex, currentOperand}); + } else { + newOperandsOp[i] = currentOperand; } } + // if nextOp uses an operand, change the operand to be the corresponding + // output of op as new operand + rewriter.modifyOpInPlace(op, [&]() { op->setOperands(newOperandsOp); }); // update nextOp to use the previous operand of the operation (since it will // be moved behind it) rewriter.modifyOpInPlace(nextOp, [&]() { @@ -192,10 +199,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { nextOp->setOperand(changedIndex, newOperand); } }); - // if nextOp uses an operand, use the corresponding output of nextOp as new - // operand - rewriter.modifyOpInPlace(op, [&]() { op->setOperands(newOperandsOp); }); - rewriter.moveOpBefore(nextOp, op); + rewriter.moveOpAfter(op, nextOp); } /** diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index e638b53881..4a6f9a95a9 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -177,14 +177,14 @@ module { %q2_0 = mqtopt.allocQubit %q3_0 = mqtopt.allocQubit - %q0_1, %q1_1, %q2_0, %q3_0 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q0_3, %q1_3, %q2_2, %q3_2 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_2 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_3, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_2 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_2 - mqtopt.deallocQubit %q2_2 - mqtopt.deallocQubit %q3_2 + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q2_3 + mqtopt.deallocQubit %q3_3 return } @@ -218,14 +218,14 @@ module { %q2_0 = mqtopt.allocQubit %q3_0 = mqtopt.allocQubit - %q0_1, %q1_1, %q2_0, %q3_0 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q0_3, %q1_3, %q2_2, %q3_2 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_2 - mqtopt.deallocQubit %q2_2 - mqtopt.deallocQubit %q3_2 + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q2_1 + mqtopt.deallocQubit %q3_1 return } From ddd1cea845a824311a6e2a64f96ffc0f820b7f9c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 3 Sep 2025 22:43:15 +0200 Subject: [PATCH 14/22] working non-controlled version --- .../Transforms/SwapReconstructionPattern.cpp | 51 ++++++++++++++---- .../Transforms/swap-reconstruction.mlir | 54 ++++--------------- 2 files changed, 51 insertions(+), 54 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 2b0388f4f5..20469d95e1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -152,7 +152,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { if (auto secondSwapTarget = getCorrespondingInput(firstCNot, secondSwapTargetOut)) { auto newSwap = replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); - relocateOperationAfter(rewriter, newSwap, *secondCNot); + swapOperationOrder(rewriter, newSwap, *secondCNot); return mlir::success(); } } @@ -170,35 +170,68 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { rewriter.eraseOp(op); } - static void relocateOperationAfter(mlir::PatternRewriter& rewriter, - mlir::Operation* op, - mlir::Operation* nextOp) { + /** + * @brief Move any operation by one after another operation. + * + * Compared to mlir::PatternRewriter::moveOpAfter(), this function will handle + * qubits which are used by both operations. However, op must not create new + * values which are then used by nextOp. + * + * @param rewriter Pattern rewriter used to apply changes + * @param op Operation to be moved + * @param nextOp Operation after op past which the other operation should be + * moved; no third operation can be between these operation which uses any of + * the out qubits of the other operation + */ + static void swapOperationOrder(mlir::PatternRewriter& rewriter, + mlir::Operation* op, mlir::Operation* nextOp) { + // collect inputs which must be updated between the two swapped llvm::SmallVector newOperandsOp(op->getNumOperands()); llvm::SmallVector> changedOperandsNextOp; for (std::size_t i = 0; i < newOperandsOp.size(); ++i) { auto&& currentOperand = op->getOperand(i); - ; auto&& currentResult = op->getResult(i); if (auto newOperandIndex = getCorrespondingOutputIndex(nextOp, currentResult)) { newOperandsOp[i] = nextOp->getResult(*newOperandIndex); changedOperandsNextOp.push_back({*newOperandIndex, currentOperand}); } else { + // if operand is not used by nextOp, simply use the current one newOperandsOp[i] = currentOperand; } } - // if nextOp uses an operand, change the operand to be the corresponding - // output of op as new operand - rewriter.modifyOpInPlace(op, [&]() { op->setOperands(newOperandsOp); }); + // update all users of nextOp to now use the result of op instead + auto&& opResults = op->getResults(); + auto&& nextOpResults = nextOp->getResults(); + llvm::SmallVector userUpdates; + for (auto* user : nextOp->getUsers()) { + for (auto&& operand : user->getOpOperands()) { + auto nextOpIt = llvm::find(nextOpResults, operand.get()); + if (nextOpIt != nextOpResults.end()) { + if (auto nextOpInput = getCorrespondingInput(nextOp, *nextOpIt)) { + auto opIt = llvm::find(opResults, *nextOpInput); + if (opIt != opResults.end()) { + // operand of user which matches a result of nextOp is also a result of op + rewriter.modifyOpInPlace(user, [&]() { user->setOperand(operand.getOperandNumber(), *opIt); }); // TODO: do this in out-most loop for performance + } + } + } + } + } + // update nextOp to use the previous operand of the operation (since it will - // be moved behind it) + // be moved behind it) as its new operand; only need to update these + // operands which were operands of the other operation rewriter.modifyOpInPlace(nextOp, [&]() { for (auto&& [changedIndex, newOperand] : changedOperandsNextOp) { nextOp->setOperand(changedIndex, newOperand); } }); + // if nextOp uses an operand, change the operand to be the corresponding + // output of op as new operand + rewriter.modifyOpInPlace(op, [&]() { op->setOperands(newOperandsOp); }); rewriter.moveOpAfter(op, nextOp); } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 4a6f9a95a9..8b180831d0 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -17,15 +17,15 @@ module { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // ============================ Check for operations that should be inserted ============================ - // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] : !mqtopt.Qubit, !mqtopt.Qubit - // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 + // ========================== Check for operations that should be inserted ============================== + // CHECK: %[[Q1_1:.*]], %[[Q0_1:.*]] = mqtopt.x() %[[Q1_0]] ctrl %[[Q0_0]] + // CHECK: %[[Q01_2:.*]]:2 = mqtopt.swap() %[[Q0_1]], %[[Q1_1]] : !mqtopt.Qubit, !mqtopt.Qubit // ========================== Check for operations that should be canceled ============================== // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() - // CHECK: mqtopt.deallocQubit %[[Q0_2]] - // CHECK: mqtopt.deallocQubit %[[Q1_2]] + // CHECK: mqtopt.deallocQubit %[[Q01_2]]#0 + // CHECK: mqtopt.deallocQubit %[[Q01_2]]#1 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -54,13 +54,12 @@ module { // ========================== Check for operations that should not be canceled =========================== // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] // CHECK: %[[Q1_2:.*]], %[[Q02_2:.*]]:2 = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]], %[[Q2_0]] - // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q02_2]]#0 ctrl %[[Q1_2]] // ========================== Check for operations that should not be inserted =========================== // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() - // CHECK: mqtopt.deallocQubit %[[Q0_3]] - // CHECK: mqtopt.deallocQubit %[[Q1_3]] + // CHECK: mqtopt.deallocQubit %[[Q02_2]]#0 + // CHECK: mqtopt.deallocQubit %[[Q1_2]] // CHECK: mqtopt.deallocQubit %[[Q02_2]]#1 %q0_0 = mqtopt.allocQubit @@ -69,10 +68,9 @@ module { %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q1_2, %q0_2, %q2_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit - %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_3 - mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 mqtopt.deallocQubit %q2_1 return @@ -115,40 +113,6 @@ module { } -// ----- -// This test checks that two CNOT gates are merged by inserting self-cancelling CNOT gates. - -module { - // CHECK-LABEL: func.func @testAdvancedSwapReconstruction - func.func @testAdvancedSwapReconstruction() { - // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - - // ========================== Check for operations that should be canceled ============================== - // CHECK-NOT: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] - - // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q01_1:.*]]:2 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] - // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q01_1]]#0 ctrl %[[Q01_1]]#1 - - // CHECK: mqtopt.deallocQubit %[[Q0_2]] - // CHECK: mqtopt.deallocQubit %[[Q1_2]] - - %q0_0 = mqtopt.allocQubit - %q1_0 = mqtopt.allocQubit - - %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_2 - - return - } -} - - // ----- // This test checks that controlled CNOT gates are merged into a controlled SWAP gate. From 621edfbf871ca01f4a68b7e6ff7657cf857d8344 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 3 Sep 2025 23:16:57 +0200 Subject: [PATCH 15/22] first non-working draft for full swap matching --- .../Transforms/SwapReconstructionPattern.cpp | 75 ++++++++++++++----- .../Transforms/swap-reconstruction.mlir | 34 ++++----- 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 20469d95e1..d367a6b64e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -88,24 +88,15 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return false; } - for (auto&& element : set) { - if (!llvm::is_contained(subsetCandidate, element) && - (!ignoredMismatch || *ignoredMismatch == element)) { + for (auto&& element : subsetCandidate) { + if (!llvm::is_contained(set, element) && + (!ignoredMismatch || *ignoredMismatch != element)) { return false; } } return true; }; - // llvm::SmallSetVector posCtrlDiff{ - // nextInPosCtrlQubits.begin(), nextInPosCtrlQubits.end()}; - // posCtrlDiff.set_subtract(opOutPosCtrlQubits); - // bool posCtrlMatches = posCtrlDiff.size() == 1 && posCtrlDiff.front() == - // opOutTarget; - - // bool negCtrlMatches = - // llvm::set_is_subset(opOutNegCtrlQubits, nextInNegCtrlQubits); - bool targetIsPosCtrl = llvm::is_contained(nextInPosCtrlQubits, opOutTarget) && llvm::is_contained(opOutPosCtrlQubits, nextInTarget); @@ -114,7 +105,7 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { isSubset(opOutPosCtrlQubits, nextInPosCtrlQubits, opOutTarget); bool negCtrlMatches = isSubset(opOutNegCtrlQubits, nextInNegCtrlQubits); - // TODO: early return possible for better performance? + // TODO: early return for slightly better performance? if constexpr (matchControlledSwap) { return targetIsPosCtrl && posCtrlMatches && negCtrlMatches; } else { @@ -151,9 +142,21 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto secondSwapTargetOut = getCNotInTarget(*secondCNot); if (auto secondSwapTarget = getCorrespondingInput(firstCNot, secondSwapTargetOut)) { - auto newSwap = replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); - swapOperationOrder(rewriter, newSwap, *secondCNot); - return mlir::success(); + if constexpr (onlyMatchFullSwapPattern) { + // if enabled, check if there is a third CNOT which must be equal + // to the first one + if (auto thirdCNot = checkThirdCNot(firstCNot, *secondCNot)) { + replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); + eraseOperation(rewriter, *secondCNot); + eraseOperation(rewriter, *thirdCNot); + return mlir::success(); + } + } else { + auto newSwap = + replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); + swapOperationOrder(rewriter, newSwap, *secondCNot); + return mlir::success(); + } } } @@ -213,8 +216,11 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { if (auto nextOpInput = getCorrespondingInput(nextOp, *nextOpIt)) { auto opIt = llvm::find(opResults, *nextOpInput); if (opIt != opResults.end()) { - // operand of user which matches a result of nextOp is also a result of op - rewriter.modifyOpInPlace(user, [&]() { user->setOperand(operand.getOperandNumber(), *opIt); }); // TODO: do this in out-most loop for performance + // operand of user which matches a result of nextOp is also a + // result of op + rewriter.modifyOpInPlace(user, [&]() { + user->setOperand(operand.getOperandNumber(), *opIt); + }); // TODO: do this in out-most loop for performance } } } @@ -250,6 +256,39 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } + /** + * @brief Find a user of the given operation for which isReverseCNotPattern() + * with the given operation is true. + */ + [[nodiscard]] static std::optional checkThirdCNot(XOp& firstCNot, + XOp& secondCNot) { + // check if gate is equal, ignoring another operation (secondCNot) + // in-between + auto equalsThrough = [&](XOp& a, XOp& b, XOp& inbetween) { + auto&& aOutput = a->getResults(); + auto&& bInput = b->getOperands(); + + for (auto&& inbetweenOut : bInput) { + if (auto inbetweenIn = getCorrespondingInput(inbetween, inbetweenOut)) { + if (!llvm::is_contained(aOutput, inbetweenIn)) { + return false; + } + } else if (!llvm::is_contained(aOutput, inbetweenOut)) { + return false; + } + } + return true; + }; + for (auto* user : secondCNot->getUsers()) { + if (auto cnot = llvm::dyn_cast(user)) { + if (equalsThrough(firstCNot, cnot, secondCNot)) { + return cnot; + } + } + } + return std::nullopt; + } + static std::optional getCorrespondingInput(mlir::Operation* op, mlir::Value out) { for (auto&& result : op->getResults()) { diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 8b180831d0..6dd1932feb 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -129,21 +129,21 @@ module { // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q01_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] + // CHECK: %[[Q01_1:.*]]:2, %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#1 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#2 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#3 + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // CHECK: mqtopt.deallocQubit %[[Q3_1]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit %q2_0 = mqtopt.allocQubit %q3_0 = mqtopt.allocQubit - %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q1_2, %q0_2, %q2_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q0_3, %q1_3, %q2_3, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_2 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 nctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 nctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_3, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_2 nctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_3 mqtopt.deallocQubit %q1_3 @@ -170,7 +170,7 @@ module { // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q0123_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] negctrl %[[Q3_0]] + // CHECK: %[[Q0123_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] nctrl %[[Q3_0]] // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#0 // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#1 @@ -183,7 +183,7 @@ module { %q3_0 = mqtopt.allocQubit %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q1_2, %q0_2, %q2_1, %q3_1 = mqtopt.x() %q1_1 ctrl %q0_1, %q2_0 nctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_3 @@ -201,16 +201,16 @@ module { module { // CHECK-LABEL: func.func @testNoControlledSwapReconstruction - func.func @testControlledSwapReconstruction() { + func.func @testNoControlledSwapReconstruction() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q3_0:.*]] = mqtopt.allocQubit // ========================= Check for operations that should be kept as-is ============================= - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]], %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] negctrl %[[Q3_0]] - // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]], %[[Q3_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] negctrl %[[Q3_1]] - // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]], %[[Q2_2:.*]], %[[Q3_3:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] negctrl %[[Q3_0]] + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]], %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] nctrl %[[Q3_0]] + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]], %[[Q3_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] nctrl %[[Q3_1]] + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]], %[[Q2_2:.*]], %[[Q3_3:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] nctrl %[[Q3_0]] // ======================== Check for operations that should not be inserted ============================ // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() @@ -225,9 +225,9 @@ module { %q2_0 = mqtopt.allocQubit %q3_0 = mqtopt.allocQubit - %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 negctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit - %q1_2, %q0_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1 negctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit negctrl !mqtopt.Qubit - %q0_3, %q1_3, %q2_2, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_1 negctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit negctrl !mqtopt.Qubit + %q0_1, %q1_1, %q2_1, %q3_1 = mqtopt.x() %q0_0 ctrl %q1_0, %q2_0 nctrl %q3_0: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit + %q1_2, %q0_2, %q3_2 = mqtopt.x() %q1_1 ctrl %q0_1 nctrl %q3_1: !mqtopt.Qubit ctrl !mqtopt.Qubit nctrl !mqtopt.Qubit + %q0_3, %q1_3, %q2_2, %q3_3 = mqtopt.x() %q0_2 ctrl %q1_2, %q2_1 nctrl %q3_2: !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit nctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_3 mqtopt.deallocQubit %q1_3 From c621e99fadfa7cd80ce520b23342d8870cc64d77 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 4 Sep 2025 11:08:06 +0200 Subject: [PATCH 16/22] fix tests --- .../Transforms/SwapReconstructionPattern.cpp | 88 +++++++++++-------- .../Transforms/swap-reconstruction.mlir | 34 +++---- 2 files changed, 66 insertions(+), 56 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index d367a6b64e..ddf373c061 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -40,15 +40,15 @@ namespace mqt::ir::opt { * * ──■────■── ──■────■────■────■── ──■────■── * | ┌─┴─┐ | ┌─┴─┐ | | | | - * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── - * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + * ──■──┤ X ├ => ──■──┤ X ├──■────■── => ──╳────■── + * ┌─┴─┐└─┬─┘ ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ | ┌─┴─┐ * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ * └───┘ └───┘ └───┘└───┘ └───┘ * * ──□────□── ──□────□────□────□── ──□────□── * | ┌─┴─┐ | ┌─┴─┐ | | | | - * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── - * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + * ──■──┤ X ├ => ──■──┤ X ├──■────■── => ──╳────■── + * ┌─┴─┐└─┬─┘ ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ | ┌─┴─┐ * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ * └───┘ └───┘ └───┘└───┘ └───┘ */ @@ -73,7 +73,17 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return inQubits.front(); } - [[nodiscard]] static bool isCandidate(XOp& op, XOp& nextOp) { + /** + * @brief Check if operations are suitable for a two CNOT swap reconstruction. + * + * @param op One operation to be checked + * @param nextOp A user of op which should be checked + * @param nextIsSubset If true, it will be assumed that op is the "main" + * operation which has to have a superset of controls of + * nextOp; if false, the other way around + */ + [[nodiscard]] static bool isCandidate(XOp& op, XOp& nextOp, + bool nextIsSubset) { auto&& opOutTarget = getCNotOutTarget(op); auto&& nextInTarget = getCNotInTarget(nextOp); auto&& opOutPosCtrlQubits = op.getPosCtrlOutQubits(); @@ -102,8 +112,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { llvm::is_contained(opOutPosCtrlQubits, nextInTarget); bool posCtrlMatches = - isSubset(opOutPosCtrlQubits, nextInPosCtrlQubits, opOutTarget); - bool negCtrlMatches = isSubset(opOutNegCtrlQubits, nextInNegCtrlQubits); + nextIsSubset + ? isSubset(opOutPosCtrlQubits, nextInPosCtrlQubits, opOutTarget) + : isSubset(nextInPosCtrlQubits, opOutPosCtrlQubits, nextInTarget); + bool negCtrlMatches = + nextIsSubset ? isSubset(opOutNegCtrlQubits, nextInNegCtrlQubits) + : isSubset(nextInNegCtrlQubits, opOutNegCtrlQubits); // TODO: early return for slightly better performance? if constexpr (matchControlledSwap) { @@ -138,25 +152,21 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { } auto& firstCNot = op; - if (auto secondCNot = findCandidate(firstCNot)) { - auto secondSwapTargetOut = getCNotInTarget(*secondCNot); - if (auto secondSwapTarget = - getCorrespondingInput(firstCNot, secondSwapTargetOut)) { - if constexpr (onlyMatchFullSwapPattern) { - // if enabled, check if there is a third CNOT which must be equal - // to the first one - if (auto thirdCNot = checkThirdCNot(firstCNot, *secondCNot)) { - replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); - eraseOperation(rewriter, *secondCNot); - eraseOperation(rewriter, *thirdCNot); - return mlir::success(); - } - } else { - auto newSwap = - replaceWithSwap(rewriter, firstCNot, *secondSwapTarget); - swapOperationOrder(rewriter, newSwap, *secondCNot); + if (auto secondCNot = findCandidate(firstCNot, false)) { + auto secondSwapTarget = getCNotOutTarget(firstCNot); + if constexpr (onlyMatchFullSwapPattern) { + // if enabled, check if there is a third CNOT which must be equal + // to the first one + if (auto thirdCNot = checkThirdCNot(firstCNot, *secondCNot)) { + replaceWithSwap(rewriter, *secondCNot, secondSwapTarget); + eraseOperation(rewriter, firstCNot); + eraseOperation(rewriter, *thirdCNot); return mlir::success(); } + } else { + auto newSwap = replaceWithSwap(rewriter, *secondCNot, secondSwapTarget); + swapOperationOrder(rewriter, firstCNot, newSwap); + return mlir::success(); } } @@ -210,21 +220,21 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { auto&& nextOpResults = nextOp->getResults(); llvm::SmallVector userUpdates; for (auto* user : nextOp->getUsers()) { - for (auto&& operand : user->getOpOperands()) { - auto nextOpIt = llvm::find(nextOpResults, operand.get()); - if (nextOpIt != nextOpResults.end()) { - if (auto nextOpInput = getCorrespondingInput(nextOp, *nextOpIt)) { - auto opIt = llvm::find(opResults, *nextOpInput); - if (opIt != opResults.end()) { - // operand of user which matches a result of nextOp is also a - // result of op - rewriter.modifyOpInPlace(user, [&]() { + rewriter.modifyOpInPlace(user, [&]() { + for (auto&& operand : user->getOpOperands()) { + auto nextOpIt = llvm::find(nextOpResults, operand.get()); + if (nextOpIt != nextOpResults.end()) { + if (auto nextOpInput = getCorrespondingInput(nextOp, *nextOpIt)) { + auto opIt = llvm::find(opResults, *nextOpInput); + if (opIt != opResults.end()) { + // operand of user which matches a result of nextOp is also a + // result of op user->setOperand(operand.getOperandNumber(), *opIt); - }); // TODO: do this in out-most loop for performance + } } } } - } + }); } // update nextOp to use the previous operand of the operation (since it will @@ -242,13 +252,13 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { } /** - * @brief Find a user of the given operation for which isReverseCNotPattern() - * with the given operation is true. + * @brief Find a user of the given operation for which isCandidate() is true. */ - [[nodiscard]] static std::optional findCandidate(XOp& op) { + [[nodiscard]] static std::optional findCandidate(XOp& op, bool isThirdCNot) { for (auto* user : op->getUsers()) { if (auto cnot = llvm::dyn_cast(user)) { - if (isCandidate(op, cnot)) { + // TODO: check both directions? + if (isCandidate(op, cnot, isThirdCNot)) { return cnot; } } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 6dd1932feb..24199584e7 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -18,14 +18,14 @@ module { // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q1_1:.*]], %[[Q0_1:.*]] = mqtopt.x() %[[Q1_0]] ctrl %[[Q0_0]] - // CHECK: %[[Q01_2:.*]]:2 = mqtopt.swap() %[[Q0_1]], %[[Q1_1]] : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[Q10_1:.*]]:2 = mqtopt.swap() %[[Q1_0]], %[[Q0_0]] : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q10_1]]#1 ctrl %[[Q10_1]]#0 // ========================== Check for operations that should be canceled ============================== // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() - // CHECK: mqtopt.deallocQubit %[[Q01_2]]#0 - // CHECK: mqtopt.deallocQubit %[[Q01_2]]#1 + // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_2]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -129,10 +129,10 @@ module { // CHECK-NOT: %[[Q0_2:.*]], %[[Q1_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q01_1:.*]]:2, %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] + // CHECK: %[[Q10_1:.*]]:2, %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.swap() %[[Q1_0]], %[[Q0_0]] ctrl %[[Q2_0]] nctrl %[[Q3_0]] - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q10_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q10_1]]#0 // CHECK: mqtopt.deallocQubit %[[Q2_1]] // CHECK: mqtopt.deallocQubit %[[Q3_1]] @@ -170,12 +170,12 @@ module { // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() // ========================== Check for operations that should be inserted ============================== - // CHECK: %[[Q0123_1:.*]]:4 = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] nctrl %[[Q3_0]] + // CHECK: %[[Q10_1:.*]]:2, %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.swap() %[[Q1_0]], %[[Q0_0]] ctrl %[[Q2_0]] nctrl %[[Q3_0]] - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#1 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#2 - // CHECK: mqtopt.deallocQubit %[[Q0123_1]]#3 + // CHECK: mqtopt.deallocQubit %[[Q10_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q10_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // CHECK: mqtopt.deallocQubit %[[Q3_1]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -208,16 +208,16 @@ module { // CHECK: %[[Q3_0:.*]] = mqtopt.allocQubit // ========================= Check for operations that should be kept as-is ============================= - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]], %[[Q2_1:.*]], %[[Q3_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] nctrl %[[Q3_0]] - // CHECK: %[[Q0_2:.*]], %[[Q1_2:.*]], %[[Q3_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] nctrl %[[Q3_1]] - // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]], %[[Q2_2:.*]], %[[Q3_3:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] nctrl %[[Q3_0]] + // CHECK: %[[Q0_1:.*]], %[[Q12_1:.*]]:2, %[[Q3_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]], %[[Q2_0]] nctrl %[[Q3_0]] + // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]], %[[Q3_2:.*]] = mqtopt.x() %[[Q12_1]]#0 ctrl %[[Q0_1]] nctrl %[[Q3_1]] + // CHECK: %[[Q0_3:.*]], %[[Q12_3:.*]]:2, %[[Q3_3:.*]] = mqtopt.x() %[[Q0_2]] ctrl %[[Q1_2]], %[[Q12_1]]#1 nctrl %[[Q3_2]] // ======================== Check for operations that should not be inserted ============================ // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() // CHECK: mqtopt.deallocQubit %[[Q0_3]] - // CHECK: mqtopt.deallocQubit %[[Q1_3]] - // CHECK: mqtopt.deallocQubit %[[Q2_2]] + // CHECK: mqtopt.deallocQubit %[[Q12_3]]#0 + // CHECK: mqtopt.deallocQubit %[[Q12_3]]#1 // CHECK: mqtopt.deallocQubit %[[Q3_3]] %q0_0 = mqtopt.allocQubit From 02f4d1bc4ff1dc9142235560605d12aee6d2b41b Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 4 Sep 2025 11:27:15 +0200 Subject: [PATCH 17/22] update comments --- .../Transforms/SwapReconstructionPattern.cpp | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index ddf373c061..b42bdeb948 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -132,21 +132,31 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { /** * @brief If pattern is applicable, perform MLIR rewrite. * - * Steps: + * Steps (onlyMatchFullSwapPattern == false): * - Find CNOT with at least one positive control qubit (1st CNOT) - * - Check if it has adjacent CNOT with subset of control qubits (2nd CNOT) - * (- Theoretically place two CNOTs identical to 2nd CNOT on other side of - * 1st CNOT) - * - Replace 1st CNOT by SWAP with identical control qubits (also cancels - * out 2nd CNOT and one inserted CNOT); use target of 2nd CNOT as second - * target for the swap - * - Move 2nd CNOT to other side of SWAP (takes the place of the left-over + * - Check if it has adjacent CNOT with superset of control qubits + * (2nd CNOT) + * (- Theoretically place two CNOTs identical to 1st CNOT on other side of + * 2nd CNOT) + * - Replace 2nd CNOT by SWAP with identical control qubits (if controlled + * swaps are enabled), this also cancels out 1st CNOT and one inserted + * CNOT); use target of 1st CNOT as second target for the swap + * - Move 1st CNOT to other side of SWAP (takes the place of the left-over * inserted CNOT) + * + * Steps (onlyMatchFullSwapPattern == true): + * - Find CNOT with at least one positive control qubit (1st CNOT) + * - Check if it has adjacent CNOT with superset of control qubits + * (2nd CNOT) + * - Check if 2nd CNOT has adjacent CNOT identical to 1st CNOT (3rd CNOT) + * - Replace 2nd CNOT by SWAP with identical control qubits (if controlled + * swaps are enabled) + * - Erase 1st and 3rd CNOTs */ mlir::LogicalResult matchAndRewrite(XOp op, mlir::PatternRewriter& rewriter) const override { // check if at least one positive control; rely on other pattern for - // negative control decomposition + // negative control decomposition (TODO?) if (op.getPosCtrlInQubits().empty()) { return mlir::failure(); } @@ -253,11 +263,16 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { /** * @brief Find a user of the given operation for which isCandidate() is true. + * + * @param op Operation for which the users should be scanned for a candidate + * @param isThirdCNot If true, the candidate should have a subset of controls + * of op (a surrounding CNOT, 1st/3rd); if false, it should + * have a superset of controls of op (middle CNOT, 2nd) */ - [[nodiscard]] static std::optional findCandidate(XOp& op, bool isThirdCNot) { + [[nodiscard]] static std::optional findCandidate(XOp& op, + bool isThirdCNot) { for (auto* user : op->getUsers()) { if (auto cnot = llvm::dyn_cast(user)) { - // TODO: check both directions? if (isCandidate(op, cnot, isThirdCNot)) { return cnot; } @@ -267,8 +282,8 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { } /** - * @brief Find a user of the given operation for which isReverseCNotPattern() - * with the given operation is true. + * @brief Check if there is a user for a full CNOT pattern equivalent to a + * SWAP. */ [[nodiscard]] static std::optional checkThirdCNot(XOp& firstCNot, XOp& secondCNot) { @@ -299,6 +314,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } + /** + * @brief Find input qubit for a result qubit value. + * + * @note This only works for operations where the indices of operands and + * results line up. + */ static std::optional getCorrespondingInput(mlir::Operation* op, mlir::Value out) { for (auto&& result : op->getResults()) { @@ -310,6 +331,12 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return std::nullopt; } + /** + * @brief Find result index for an input qubit value. + * + * @note This only works for operations where the indices of operands and + * results line up. + */ static std::optional getCorrespondingOutputIndex(mlir::Operation* op, mlir::Value in) { for (auto&& opOperand : op->getOpOperands()) { @@ -322,10 +349,14 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { } /** - * @brief Replace the given XOp by a single SWAPOp. + * @brief Replace the given XOp by a SWAPOp. + * + * @param rewriter Pattern rewriter used to apply modifications + * @param op Operation to be replaced + * @param secondTargetIn Second target input for new swap operation * * @note The result qubits will NOT be swapped since that is already done - * implicitly by the CNOT pattern. + * implicitly by the CNOT pattern. */ static SWAPOp replaceWithSwap(mlir::PatternRewriter& rewriter, XOp& op, const mlir::Value& secondTargetIn) { From 9d87d244d0c7ce19eebc1c445c773f71b40725ca Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 4 Sep 2025 11:29:25 +0200 Subject: [PATCH 18/22] fix linter issues --- .../MQTOpt/Transforms/SwapReconstructionPattern.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index b42bdeb948..6bdbeb9971 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -13,17 +13,19 @@ #include "mlir/IR/BuiltinAttributes.h" #include +#include #include -#include #include #include #include #include +#include #include #include #include #include #include +#include namespace mqt::ir::opt { /** @@ -107,15 +109,15 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { return true; }; - bool targetIsPosCtrl = + const bool targetIsPosCtrl = llvm::is_contained(nextInPosCtrlQubits, opOutTarget) && llvm::is_contained(opOutPosCtrlQubits, nextInTarget); - bool posCtrlMatches = + const bool posCtrlMatches = nextIsSubset ? isSubset(opOutPosCtrlQubits, nextInPosCtrlQubits, opOutTarget) : isSubset(nextInPosCtrlQubits, opOutPosCtrlQubits, nextInTarget); - bool negCtrlMatches = + const bool negCtrlMatches = nextIsSubset ? isSubset(opOutNegCtrlQubits, nextInNegCtrlQubits) : isSubset(nextInNegCtrlQubits, opOutNegCtrlQubits); @@ -228,7 +230,6 @@ struct SwapReconstructionPattern final : mlir::OpRewritePattern { // update all users of nextOp to now use the result of op instead auto&& opResults = op->getResults(); auto&& nextOpResults = nextOp->getResults(); - llvm::SmallVector userUpdates; for (auto* user : nextOp->getUsers()) { rewriter.modifyOpInPlace(user, [&]() { for (auto&& operand : user->getOpOperands()) { From d46a6b88dd75b11e798a46780452d9a81e5d3c0b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 09:30:02 +0000 Subject: [PATCH 19/22] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir index 24199584e7..ca2d2dc1b3 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction.mlir @@ -237,4 +237,3 @@ module { return } } - From daece3f77a9666dfd681a6f4a79a1e1f8981e9bd Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht <28907748+taminob@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:37:27 +0200 Subject: [PATCH 20/22] Update mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp Co-authored-by: Lukas Burgholzer Signed-off-by: Tamino Bauknecht <28907748+taminob@users.noreply.github.com> --- .../lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 6bdbeb9971..8b546bb8fe 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -10,7 +10,7 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" -#include "mlir/IR/BuiltinAttributes.h" +#include #include #include From ff68d5f8ff2340ff7c5321619ae97ef1ada46121 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:38:51 +0000 Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 8b546bb8fe..7f2ad2fbb0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -10,12 +10,12 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" -#include #include #include #include #include +#include #include #include #include From 4b18b91f743007c2c9fe8694eafdc554ef2dad98 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 4 Sep 2025 16:34:49 +0200 Subject: [PATCH 22/22] improve documentation --- .../mlir/Dialect/MQTOpt/Transforms/Passes.td | 26 ++++++++++++++++--- .../Transforms/SwapReconstructionPattern.cpp | 23 ++++++++-------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 09cf19bd6e..353d081d27 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -65,10 +65,30 @@ def ElidePermutations : Pass<"elide-permutations", "mlir::ModuleOp"> { } def SwapReconstruction : Pass<"swap-reconstruction", "mlir::ModuleOp"> { - let summary = "This pass searches for CNOTs that are equivalent to a SWAP and converts them into a SWAP gate."; + let summary = "This pass searches for CNOTs that can be merged into a SWAP."; let description = [{ - Multiple CNOTs that are equivalent to a SWAP are merge and converted into a SWAP gate. - Additionally, it may insert two self-cancelling CNOTs to allow for the insertion of a SWAP. + Three CNOTs that are equivalent to a SWAP are directly replaced with a (potentially controlled) SWAP gate. + + For swap reconstructions with just two CNOTs (utilizing an insertion of two self-cancelling CNOTs), only non-controlled SWAP gates are inserted. + + Examples of swap reconstructions: + + ``` + ┌───┐ ┌───┐ + ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── + ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ + ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ + └───┘ └───┘ └───┘└───┘ └───┘ + ``` + + ``` + ──■────■────■── ──■── + | ┌─┴─┐ | | + ──■──┤ X ├──■── => ──╳── + ┌─┴─┐└─┬─┘┌─┴─┐ | + ┤ X ├──■──┤ X ├ ──╳── + └───┘ └───┘ + ``` }]; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp index 7f2ad2fbb0..b7ac6e095a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionPattern.cpp @@ -29,30 +29,31 @@ namespace mqt::ir::opt { /** - * @brief This pattern attempts to find three CNOT gates next to each other - * which are equivalent to a SWAP. These gates will be removed and replaced by a - * SWAP operation. + * @brief This pattern attempts to find CNOT patterns which can be replaced by a + * SWAP gate. * - * Examples: + * Example (onlyMatchFullSwapPattern=false): * ┌───┐ ┌───┐ * ──■──┤ X ├ ──■──┤ X ├──■────■── ──╳────■── * ┌─┴─┐└─┬─┘ => ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ => | ┌─┴─┐ * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ * └───┘ └───┘ └───┘└───┘ └───┘ * - * ──■────■── ──■────■────■────■── ──■────■── - * | ┌─┴─┐ | ┌─┴─┐ | | | | - * ──■──┤ X ├ => ──■──┤ X ├──■────■── => ──╳────■── - * ┌─┴─┐└─┬─┘ ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ | ┌─┴─┐ - * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ - * └───┘ └───┘ └───┘└───┘ └───┘ - * + * Example (onlyMatchFullSwapPattern=false, matchControlledSwap=true): * ──□────□── ──□────□────□────□── ──□────□── * | ┌─┴─┐ | ┌─┴─┐ | | | | * ──■──┤ X ├ => ──■──┤ X ├──■────■── => ──╳────■── * ┌─┴─┐└─┬─┘ ┌─┴─┐└─┬─┘┌─┴─┐┌─┴─┐ | ┌─┴─┐ * ┤ X ├──■── ┤ X ├──■──┤ X ├┤ X ├ ──╳──┤ X ├ * └───┘ └───┘ └───┘└───┘ └───┘ + * + * Example (onlyMatchFullSwapPattern=true, matchControlledSwap=true): + * ──■────■────■── ──■── + * | ┌─┴─┐ | | + * ──■──┤ X ├──■── => ──╳── + * ┌─┴─┐└─┬─┘┌─┴─┐ | + * ┤ X ├──■──┤ X ├ ──╳── + * └───┘ └───┘ */ template struct SwapReconstructionPattern final : mlir::OpRewritePattern {