diff --git a/.license-tools-config.json b/.license-tools-config.json index f2961007eb..a514d872b1 100644 --- a/.license-tools-config.json +++ b/.license-tools-config.json @@ -11,6 +11,7 @@ ".pyi": "DOCSTRING_STYLE", ".in": "POUND_STYLE", ".mlir": "SLASH_STYLE", + ".pdll": "SLASH_STYLE", ".td": "SLASH_STYLE", ".yaml": "POUND_STYLE", ".toml": "POUND_STYLE", diff --git a/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc b/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc index aea22508b2..675c4b9929 100644 --- a/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc +++ b/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc @@ -6,8 +6,10 @@ // // Licensed under the MIT License -#ifndef @DIALECT_NAME@_STD_OPS -#define @DIALECT_NAME@_STD_OPS +#ifndef @DIALECT_NAME_UPPER@_STD_OPS +#define @DIALECT_NAME_UPPER@_STD_OPS + +include "mlir/Dialect/@DIALECT_NAME@/IR/@DIALECT_NAME@Ops.td" def GPhaseOp : UnitaryOp<"gphase", [NoTarget, OneParameter]> { let summary = "GPhase operation"; @@ -329,4 +331,4 @@ def XXplusYYOp : UnitaryOp<"xx_plus_yy", [TwoTarget, TwoParameters]> { }]; } -#endif // @DIALECT_NAME@_STD_OPS +#endif // @DIALECT_NAME_UPPER@_STD_OPS diff --git a/mlir/include/mlir/Dialect/MQTOpt/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/MQTOpt/IR/CMakeLists.txt index f5310d467d..30b880cc71 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/MQTOpt/IR/CMakeLists.txt @@ -6,7 +6,8 @@ # # Licensed under the MIT License -set(DIALECT_NAME "MQTOPT") +set(DIALECT_NAME "MQTOpt") +set(DIALECT_NAME_UPPER "MQTOPT") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../../Common/IR/StdOps.td.inc ${CMAKE_CURRENT_BINARY_DIR}/MQTOptStdOps.td @ONLY) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index 23ef1c3114..4f87e9f7b7 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -27,7 +27,6 @@ namespace mqt::ir::opt { void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); -void populateElidePermutationsPatterns(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..c65d2e08c2 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -9,6 +9,8 @@ #ifndef MQTO_PASSES #define MQTO_PASSES +include "mlir/Dialect/PDL/IR/PDLDialect.td" +include "mlir/Dialect/PDLInterp/IR/PDLInterpOps.td" include "mlir/Pass/PassBase.td" def MQTCoreRoundTrip : Pass<"mqt-core-round-trip", "mlir::ModuleOp"> { @@ -45,7 +47,7 @@ def MergeRotationGates : Pass<"merge-rotation-gates", "mlir::ModuleOp"> { }]; } -def ElidePermutations : Pass<"elide-permutations", "mlir::ModuleOp"> { +def SwapReconstructionAndElision : Pass<"swap-reconstruction-and-elision", "mlir::ModuleOp"> { let summary = "This pass removes any permutation (i.e., SWAP gates) it encounters and modifies the order of the qubits instead."; let description = [{ Elides permutations (i.e., SWAP gates) by removing them from the circuit and, instead, permuting the respective qubit values. @@ -62,6 +64,10 @@ def ElidePermutations : Pass<"elide-permutations", "mlir::ModuleOp"> { %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0 : !mqtopt.Qubit ctrl !mqtopt.Qubit ``` }]; + let dependentDialects = [ + "mlir::pdl::PDLDialect", + "mlir::pdl_interp::PDLInterpDialect", + ]; } def QuantumSinkPass : Pass<"quantum-sink", "mlir::ModuleOp"> { diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.h new file mode 100644 index 0000000000..9f2448d6fd --- /dev/null +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.h @@ -0,0 +1,22 @@ +/* + * 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 + */ + +#pragma once + +#include +#include +#include + +namespace mqt::ir::opt { + +#define GEN_PASS_DECL_SWAPRECONSTRUCTIONANDELISION +#include "SwapReconstructionAndElision.h.inc" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" +} // namespace mqt::ir::opt diff --git a/mlir/include/mlir/Dialect/MQTRef/IR/CMakeLists.txt b/mlir/include/mlir/Dialect/MQTRef/IR/CMakeLists.txt index b8314014e8..449e4e3c1e 100644 --- a/mlir/include/mlir/Dialect/MQTRef/IR/CMakeLists.txt +++ b/mlir/include/mlir/Dialect/MQTRef/IR/CMakeLists.txt @@ -6,7 +6,8 @@ # # Licensed under the MIT License -set(DIALECT_NAME "MQTREF") +set(DIALECT_NAME "MQTRef") +set(DIALECT_NAME_UPPER "MQTREF") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../../Common/IR/StdOps.td.inc ${CMAKE_CURRENT_BINARY_DIR}/MQTRefStdOps.td @ONLY) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index 5e86b17e08..d54d2c05aa 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -12,8 +12,19 @@ add_compile_options(-fexceptions) file(GLOB TRANSFORMS_SOURCES *.cpp) -add_mlir_library(MLIRMQTOptTransforms ${TRANSFORMS_SOURCES} LINK_LIBS ${LIBRARIES} DEPENDS - MLIRMQTOptTransformsIncGen) +add_mlir_pdll_library(SwapReconstructionAndElisionIncGen SwapReconstructionAndElision.pdll + SwapReconstructionAndElision.h.inc) + +add_mlir_library( + MLIRMQTOptTransforms + ${TRANSFORMS_SOURCES} + LINK_LIBS + ${LIBRARIES} + DEPENDS + MLIRMQTOptTransformsIncGen + SwapReconstructionAndElisionIncGen) + +target_include_directories(MLIRMQTOptTransforms PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) # collect header files file(GLOB_RECURSE TRANSFORMS_HEADERS_SOURCE diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutationsPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutationsPattern.cpp deleted file mode 100644 index 3ce19e31fc..0000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutationsPattern.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include - -namespace mqt::ir::opt { - -/** - * @brief This pattern attempts to remove SWAP gates by re-ordering qubits. - */ -struct ElidePermutationsPattern final : mlir::OpRewritePattern { - - explicit ElidePermutationsPattern(mlir::MLIRContext* context) - : OpRewritePattern(context) {} - - mlir::LogicalResult - matchAndRewrite(SWAPOp op, mlir::PatternRewriter& rewriter) const override { - if (op.isControlled()) { - return mlir::failure(); - } - - auto inQubits = op.getInQubits(); - assert(inQubits.size() == 2); - - rewriter.replaceAllOpUsesWith(op, {inQubits[1], inQubits[0]}); - - rewriter.eraseOp(op); - - return mlir::success(); - } -}; - -/** - * @brief Populates the given pattern set with the `ElidePermutationsPattern`. - * - * @param patterns The pattern set to populate. - */ -void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns) { - patterns.add(patterns.getContext()); -} - -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutations.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.cpp similarity index 74% rename from mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutations.cpp rename to mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.cpp index b5e2bb7013..b80ac13fce 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ElidePermutations.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.cpp @@ -8,8 +8,9 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include "mlir/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.h" +#include #include #include #include @@ -17,14 +18,14 @@ namespace mqt::ir::opt { -#define GEN_PASS_DEF_ELIDEPERMUTATIONS +#define GEN_PASS_DEF_SWAPRECONSTRUCTIONANDELISION #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" /** * @brief This pattern attempts to remove SWAP gates by re-ordering qubits. */ -struct ElidePermutations final - : impl::ElidePermutationsBase { +struct SwapReconstructionAndElision final + : impl::SwapReconstructionAndElisionBase { void runOnOperation() override { // Get the current operation being operated on. @@ -33,7 +34,7 @@ struct ElidePermutations final // Define the set of patterns to use. mlir::RewritePatternSet patterns(ctx); - populateElidePermutationsPatterns(patterns); + populateGeneratedPDLLPatterns(patterns); // Apply patterns in an iterative and greedy manner. if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.pdll b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.pdll new file mode 100644 index 0000000000..e3e3d736a9 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/SwapReconstructionAndElision.pdll @@ -0,0 +1,66 @@ +// 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/MQTOptStdOps.td" + +Constraint ExtractCNotResults(op: Op) -> (target: Value, control: Value) { + return (op.0, op.1); +} + +Constraint IsEmpty(valueRange: ValueRange) [{ + return mlir::success(valueRange.empty()); +}]; + +Constraint HasTwoElements(valueRange: ValueRange) [{ + return mlir::success(valueRange.size() == 2); +}]; + +Rewrite BuildXOp(op: Op, target: Value, posControls: ValueRange, negControls: ValueRange) -> Op [{ + auto qubitType = target.getType(); + auto outType = llvm::SmallVector{1, qubitType}; + auto outPosCtrlType = llvm::SmallVector{posControls.size(), qubitType}; + auto outNegCtrlType = llvm::SmallVector{negControls.size(), qubitType}; + + return rewriter.create(op->getLoc(), outType, outPosCtrlType, outNegCtrlType, mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + mlir::ValueRange{}, llvm::SmallVector{target}, posControls, mlir::ValueRange{}); +}]; + +Rewrite Self(op: Op) => op; + +Rewrite First(valueRange: ValueRange) -> Value [{ + return valueRange[0]; +}]; + +Rewrite Second(valueRange: ValueRange) -> Value [{ + return valueRange[1]; +}]; + +Pattern SwapReconstructionAndElisionPattern with recursion { + // match + let root = op(_: [ValueRange, IsEmpty], target: Value, control: Value, _: [ValueRange, IsEmpty]); + let rootOut = ExtractCNotResults(root); + let secondCNot = op(_: [ValueRange, IsEmpty], rootOut.control, rootOut.target, _: [ValueRange, IsEmpty]); + + // rewrite + rewrite root with { + replace secondCNot with (rootOut.control, rootOut.target); + replace root with op((), (control), (target), ()) {operandSegmentSizes = attr<"array">, resultSegmentSizes = attr<"array">}; + }; +} + +Pattern ElidePermutationsPattern with recursion { + // match + let root = op(params: ValueRange, targets: ValueRange, posCtrls: ValueRange, negCtrls: ValueRange); + IsEmpty(params); + HasTwoElements(targets); + IsEmpty(posCtrls); + IsEmpty(negCtrls); + + // rewrite + replace root with (Second(targets), First(targets)); +} diff --git a/mlir/test/Dialect/MQTOpt/Transforms/elide-permutations.mlir b/mlir/test/Dialect/MQTOpt/Transforms/elide-permutations.mlir deleted file mode 100644 index 81e8f25e91..0000000000 --- a/mlir/test/Dialect/MQTOpt/Transforms/elide-permutations.mlir +++ /dev/null @@ -1,110 +0,0 @@ -// 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 --elide-permutations | FileCheck %s - -// ----- -// This test checks that a single SWAP gate is removed correctly. - -module { - // CHECK-LABEL: func.func @testSingleElidePermutation - func.func @testSingleElidePermutation() { - // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - - // ========================== Check for operations that should be canceled ============================== - // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() - - // ========================== Check for operations that should not be canceled ============================== - // CHECK: %[[Q0_1:.*]] = mqtopt.x() %[[Q0_0]] - - // CHECK: mqtopt.deallocQubit %[[Q1_0]] - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - - %q0_0 = mqtopt.allocQubit - %q1_0 = mqtopt.allocQubit - - %q0_1, %q1_1 = mqtopt.swap() %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q1_2 = mqtopt.x() %q1_1 : !mqtopt.Qubit - - mqtopt.deallocQubit %q0_1 - mqtopt.deallocQubit %q1_2 - - return - } -} - - -// ----- -// This test checks that a controlled SWAP gate is not removed. - -module { - // CHECK-LABEL: func.func @testControlledSwapNoElidePermutation - func.func @testControlledSwapNoElidePermutation() { - // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - - // ========================== Check for operations that should not be canceled ============================== - // CHECK: %[[Q01_1:.*]]:2, %[[Q2_1:.*]] = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] - - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 - // CHECK: mqtopt.deallocQubit %[[Q2_1]] - - %q0_0 = mqtopt.allocQubit - %q1_0 = mqtopt.allocQubit - %q2_0 = mqtopt.allocQubit - - %q0_1, %q1_1, %q2_1 = mqtopt.swap() %q0_0, %q1_0 ctrl %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - - mqtopt.deallocQubit %q0_1 - mqtopt.deallocQubit %q1_1 - mqtopt.deallocQubit %q2_1 - - return - } -} - - -// ----- -// This test checks that all removable SWAP gates are removed. - -module { - // CHECK-LABEL: func.func @testMultiSwapElidePermutation - func.func @testMultiSwapElidePermutation() { - // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - - // ========================== Check for operations that should not be canceled ============================== - // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() - - // ========================== Check for operations that should not be canceled ============================== - // CHECK: %[[Q12_1:.*]]:2, %[[Q0_1:.*]] = mqtopt.swap() %[[Q1_0]], %[[Q2_0]] ctrl %[[Q0_0]] - - // CHECK: mqtopt.deallocQubit %[[Q12_1]]#0 - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q12_1]]#1 - - %q0_0 = mqtopt.allocQubit - %q1_0 = mqtopt.allocQubit - %q2_0 = mqtopt.allocQubit - - %q0_1, %q1_1 = mqtopt.swap() %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q1_2, %q2_1 = mqtopt.swap() %q1_1, %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit - %q0_2, %q1_3, %q2_2 = mqtopt.swap() %q0_1, %q1_2 ctrl %q2_1 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_4, %q2_3 = mqtopt.swap() %q1_3, %q2_2 : !mqtopt.Qubit, !mqtopt.Qubit - - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_4 - mqtopt.deallocQubit %q2_3 - - return - } -} diff --git a/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction-and-elision.mlir b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction-and-elision.mlir new file mode 100644 index 0000000000..bb5b286599 --- /dev/null +++ b/mlir/test/Dialect/MQTOpt/Transforms/swap-reconstruction-and-elision.mlir @@ -0,0 +1,257 @@ +// 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-and-elision | FileCheck %s + +// ----- +// This test checks that a single SWAP gate is removed correctly. + +module { + // CHECK-LABEL: func.func @testSingleElidePermutation + func.func @testSingleElidePermutation() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should be canceled ============================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + + // ========================== Check for operations that should not be canceled ============================== + // CHECK: %[[Q0_1:.*]] = mqtopt.x() %[[Q0_0]] + + // CHECK: mqtopt.deallocQubit %[[Q1_0]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.swap() %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit + %q1_2 = mqtopt.x() %q1_1 : !mqtopt.Qubit + + mqtopt.deallocQubit %q0_1 + mqtopt.deallocQubit %q1_2 + + return + } +} + + +// ----- +// This test checks that a controlled SWAP gate is not removed. + +module { + // CHECK-LABEL: func.func @testControlledSwapNoElidePermutation + func.func @testControlledSwapNoElidePermutation() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should not be canceled ============================== + // CHECK: %[[Q01_1:.*]]:2, %[[Q2_1:.*]] = mqtopt.swap() %[[Q0_0]], %[[Q1_0]] ctrl %[[Q2_0]] + + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q01_1]]#1 + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1, %q1_1, %q2_1 = mqtopt.swap() %q0_0, %q1_0 ctrl %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_1 + mqtopt.deallocQubit %q1_1 + mqtopt.deallocQubit %q2_1 + + return + } +} + + +// ----- +// This test checks that all removable SWAP gates are removed. + +module { + // CHECK-LABEL: func.func @testMultiSwapElidePermutation + func.func @testMultiSwapElidePermutation() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // ========================== Check for operations that should not be canceled ============================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + + // ========================== Check for operations that should not be canceled ============================== + // CHECK: %[[Q12_1:.*]]:2, %[[Q0_1:.*]] = mqtopt.swap() %[[Q1_0]], %[[Q2_0]] ctrl %[[Q0_0]] + + // CHECK: mqtopt.deallocQubit %[[Q12_1]]#0 + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q12_1]]#1 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.swap() %q0_0, %q1_0 : !mqtopt.Qubit, !mqtopt.Qubit + %q1_2, %q2_1 = mqtopt.swap() %q1_1, %q2_0 : !mqtopt.Qubit, !mqtopt.Qubit + %q0_2, %q1_3, %q2_2 = mqtopt.swap() %q0_1, %q1_2 ctrl %q2_1 : !mqtopt.Qubit, !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_4, %q2_3 = mqtopt.swap() %q1_3, %q2_2 : !mqtopt.Qubit, !mqtopt.Qubit + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_4 + mqtopt.deallocQubit %q2_3 + + return + } +} + + +// ----- +// 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: %[[Q1_1:.*]], %[[Q0_1:.*]] = mqtopt.x() %[[Q1_0]] ctrl %[[Q0_0]] + + // ========================== Check for operations that should be canceled ============================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x() + + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + + %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 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 for operations that should not be inserted =========================== + // CHECK-NOT: %[[ANY:.*]] = mqtopt.swap() + + // CHECK: mqtopt.deallocQubit %[[Q02_2]]#0 + // CHECK: mqtopt.deallocQubit %[[Q1_2]] + // 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 + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_2 + mqtopt.deallocQubit %q2_1 + + 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 controlled CNOT gates with differing controls are merged into a controlled SWAP gate. + +module { + // CHECK-LABEL: func.func @testNoControlledSwapReconstruction + 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:.*]], %[[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 %[[Q12_3]]#0 + // CHECK: mqtopt.deallocQubit %[[Q12_3]]#1 + // 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 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 + mqtopt.deallocQubit %q2_2 + mqtopt.deallocQubit %q3_3 + + return + } +}