From 789654daad76546cf2a557010079e6bfd1a96c3f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 26 Mar 2026 16:52:22 +0100 Subject: [PATCH 001/131] :test_tube: Added tests for Hadamard lifting --- .../Dialect/QCO/Transforms/CMakeLists.txt | 1 + .../Transforms/Optimization/CMakeLists.txt | 22 + .../test_qco_hadamard_lifting.cpp | 503 ++++++++++++++++++ 3 files changed, 526 insertions(+) create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt index 30ddc4dc38..77d17e3e0b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(Mapping) +add_subdirectory(Optimization) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt new file mode 100644 index 0000000000..a2612c752c --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +set(target_name mqt-core-mlir-unittest-optimizations) +add_executable(${target_name} test_qco_hadamard_lifting.cpp) + +target_link_libraries( + ${target_name} + PRIVATE GTest::gtest_main + MLIRParser + MLIRQCOProgramBuilder + MLIRSupportMQT + MLIRQTensorDialect) + +mqt_mlir_configure_unittest_target(${target_name}) + +gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) \ No newline at end of file diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp new file mode 100644 index 0000000000..4280c29b47 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +// #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Support/IRVerification.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +class QCOHadamardLiftingTest : public ::testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + OwningOpRef module; + OwningOpRef reference; + + QCOHadamardLiftingTest() + : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + // Register all necessary dialects + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + } + + /** + * @brief Adds the hadamardLiftingPass to the current context and runs it. + */ + static LogicalResult runHadamardLiftingPass(ModuleOp module) { + PassManager pm(module.getContext()); + // pm.addPass(qco::createLiftHadamardGates()); + return pm.run(module); + } +}; + +} // namespace + +// ################################################## +// # Raise Hadamard over one Pauli gate Tests +// ################################################## + +/** + * @brief Test: Hadamards should be lifted over one Pauli gate. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.x(q[0]); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.z(q[1]); + q[1] = programBuilder.h(q[1]); + q[2] = programBuilder.y(q[2]); + q[2] = programBuilder.h(q[2]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[1] = referenceBuilder.x(qRef[1]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[2] = referenceBuilder.y(qRef[2]); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Pauli gates should not be lifted over Hadamards. + */ +TEST_F(QCOHadamardLiftingTest, doNotLiftPauliOverHadamardGate) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.x(q[0]); + q[1] = programBuilder.h(q[1]); + q[1] = programBuilder.z(q[1]); + q[2] = programBuilder.h(q[2]); + q[2] = programBuilder.y(q[2]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.x(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[1] = referenceBuilder.z(qRef[1]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[2] = referenceBuilder.y(qRef[2]); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks if Hadamard gates can be lifted over multiple Pauli gate. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.x(q[0]); + q[0] = programBuilder.z(q[0]); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.x(q[1]); + q[1] = programBuilder.y(q[1]); + q[1] = programBuilder.z(q[1]); + q[1] = programBuilder.h(q[1]); + q[2] = programBuilder.x(q[2]); + q[2] = programBuilder.s(q[2]); + q[2] = programBuilder.x(q[2]); + q[2] = programBuilder.y(q[2]); + q[2] = programBuilder.h(q[2]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.x(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[1] = referenceBuilder.z(qRef[1]); + qRef[1] = referenceBuilder.y(qRef[1]); + qRef[1] = referenceBuilder.x(qRef[1]); + qRef[2] = referenceBuilder.x(qRef[2]); + qRef[2] = referenceBuilder.s(qRef[2]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[2] = referenceBuilder.z(qRef[2]); + qRef[2] = referenceBuilder.y(qRef[2]); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks if Hadamard gates are lifted over preceding and not over + * succeeding Pauli gates. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.x(q[0]); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.x(q[0]); + q[1] = programBuilder.x(q[1]); + q[1] = programBuilder.z(q[1]); + q[1] = programBuilder.h(q[1]); + q[1] = programBuilder.z(q[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.x(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[1] = referenceBuilder.z(qRef[1]); + qRef[1] = referenceBuilder.x(qRef[1]); + qRef[1] = referenceBuilder.z(qRef[1]); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks if hadamard gates are lifted if they are controlled by + * the same qubit as the lifted gate is. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.x(q[0]); + auto qubit_pair = programBuilder.dcx(q[1], q[0]); + auto qubitPairRange = programBuilder.ctrl( + {qubit_pair.first}, {qubit_pair.second}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + programBuilder.dcx(qubitPairRange.first[0], qubitPairRange.second[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.x(qRef[0]); + auto qubitPairRangeRef = + referenceBuilder.ctrl({qRef[1]}, {qRef[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.first[0], + qubitPairRangeRef.second[0]); + referenceBuilder.dcx(qubitPairRef.first, qubitPairRef.second); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate is not lifted if they are controlled + * by a different qubit than the one lifted gate is. + */ +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { + auto q = programBuilder.allocQubitRegister(3); + auto qubit_pair = programBuilder.dcx(q[1], q[0]); + auto qubitPairRange = programBuilder.ctrl( + {q[2]}, {qubit_pair.second}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + q[0] = programBuilder.z(qubitPairRange.second[0]); + programBuilder.ctrl({qubit_pair.first}, {q[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + auto qubitPairRef = referenceBuilder.dcx(qRef[1], qRef[0]); + auto qubitPairRangeRef = referenceBuilder.ctrl( + {qRef[2]}, {qubitPairRef.second}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + qRef[0] = referenceBuilder.z(qubitPairRangeRef.second[0]); + referenceBuilder.ctrl( + {qubitPairRef.first}, {qRef[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate is not lifted if there is another + * gate between the controls of the Pauli and the Hadamard gate. + */ +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { + auto q = programBuilder.allocQubitRegister(2); + auto qubitPairRange = + programBuilder.ctrl({q[1]}, {q[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.z(target[0])}; + }); + q[1] = programBuilder.s(qubitPairRange.first[0]); + programBuilder.ctrl( + {q[1]}, qubitPairRange.second, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + auto qubitPairRangeRef = + referenceBuilder.ctrl({qRef[1]}, {qRef[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.z(target[0])}; + }); + qRef[1] = referenceBuilder.s(qubitPairRangeRef.first[0]); + referenceBuilder.ctrl( + {qRef[1]}, qubitPairRangeRef.second[0], [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate is not lifted if they do not share + * all controls with the Pauli gate. + */ +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { + auto q = programBuilder.allocQubitRegister(3); + auto qubitPairRange = + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.z(target[0])}; + }); + programBuilder.ctrl({qubitPairRange.first[0]}, qubitPairRange.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{ + programBuilder.h(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + auto qubitPairRangeRef = referenceBuilder.ctrl( + {qRef[1], qRef[2]}, {qRef[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.z(target[0])}; + }); + referenceBuilder.ctrl({qubitPairRangeRef.first[0]}, qubitPairRangeRef.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{ + referenceBuilder.h(target[0])}; + }); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate can be lifted over a controlled + * Pauli Z gate even if the targets are at different places. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { + auto q = programBuilder.allocQubitRegister(3); + auto qubitPairRange = + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.z(target[0])}; + }); + programBuilder.ctrl({qubitPairRange.first[1], qubitPairRange.second[0]}, + {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{ + programBuilder.h(target[0])}; + }); + // TODO: nctrl + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + auto qubitPairRangeRef = referenceBuilder.ctrl( + {qRef[2], qRef[0]}, {qRef[1]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + referenceBuilder.ctrl(qubitPairRangeRef.first, qubitPairRangeRef.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{ + referenceBuilder.x(target[0])}; + }); + reference = referenceBuilder.finalize(); + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +// TODO: @testLiftHadamardOverPauliGateIfControlsFit + +/** + * @brief Test: Checks that a hadamard gate is lifted over a CNOT gate target if + * a measurement is following directly after it. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { + auto q = programBuilder.allocQubitRegister(2); + auto b = programBuilder.allocClassicalBitRegister(1); + q[0] = programBuilder.s(q[0]); + auto qubitPair = programBuilder.dcx(q[0], q[1]); + q[1] = programBuilder.h(qubitPair.second); + programBuilder.measure(q[1], b[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + auto bRef = referenceBuilder.allocClassicalBitRegister(1); + qRef[0] = referenceBuilder.s(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + auto qubitPairRef = referenceBuilder.dcx(qRef[1], qRef[0]); + referenceBuilder.h(qubitPairRef.first); + referenceBuilder.measure(qubitPairRef.second, bRef[0]); + reference = referenceBuilder.finalize(); + + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate is lifted over the target of a + * multiple controlled x gate if a measurement is following directly after it. + */ +TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { + auto q = programBuilder.allocQubitRegister(3); + auto b = programBuilder.allocClassicalBitRegister(1); + auto qubitPairRange = + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.x(target[0])}; + }); + q[1] = programBuilder.h(qubitPairRange.second[0]); + programBuilder.measure(q[1], b[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + auto bRef = referenceBuilder.allocClassicalBitRegister(1); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + auto qubitPairRangeRef = referenceBuilder.ctrl( + {qRef[1], qRef[2]}, {qRef[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.x(target[0])}; + }); + referenceBuilder.h(qubitPairRangeRef.first[0]); + referenceBuilder.measure(qubitPairRangeRef.second[0], bRef[0]); + reference = referenceBuilder.finalize(); + + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Checks that a hadamard gate is not lifted over a CNOT gate + * target if a measurement is not following directly after it. + */ +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { + auto q = programBuilder.allocQubitRegister(6); + auto b = programBuilder.allocClassicalBitRegister(3); + programBuilder.dcx(q[1], q[0]); + auto qubitPairOne = programBuilder.dcx(q[3], q[2]); + programBuilder.measure(qubitPairOne.first, b[0]); + auto qubitPairTwo = programBuilder.dcx(q[5], q[4]); + q[4] = programBuilder.h(qubitPairTwo.second); + q[5] = programBuilder.h(qubitPairTwo.first); + q[5] = programBuilder.s(q[5]); + programBuilder.measure(q[4], b[1]); + programBuilder.measure(q[5], b[2]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(6); + auto bRef = referenceBuilder.allocClassicalBitRegister(3); + referenceBuilder.dcx(qRef[1], qRef[0]); + auto qubitPairOneRef = referenceBuilder.dcx(qRef[3], qRef[2]); + referenceBuilder.measure(qubitPairOneRef.first, bRef[0]); + auto qubitPairTwoRef = referenceBuilder.dcx(qRef[5], qRef[4]); + qRef[4] = referenceBuilder.h(qubitPairTwoRef.second); + qRef[5] = referenceBuilder.h(qubitPairTwoRef.first); + qRef[5] = referenceBuilder.s(qRef[5]); + referenceBuilder.measure(qRef[4], bRef[1]); + referenceBuilder.measure(qRef[5], bRef[2]); + reference = referenceBuilder.finalize(); + + PassManager pmRef(module.get().getContext()); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} From e05e2fed320bd7d1d1fdb4151d229ea3cfa1c610 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 27 Mar 2026 09:41:16 +0100 Subject: [PATCH 002/131] :test_tube: Added test cases for controlled Pauli Z lifting --- .../test_qco_hadamard_lifting.cpp | 53 ++++++++++++++++--- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 4280c29b47..37bd597b4e 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -364,12 +364,31 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.z(target[0])}; }); - programBuilder.ctrl({qubitPairRange.first[1], qubitPairRange.second[0]}, - {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { + qubitPairRange = programBuilder.ctrl( + {qubitPairRange.first[1], qubitPairRange.second[0]}, + {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + auto qubitPairRangeOne = programBuilder.ctrl( + qubitPairRange.second, {qubitPairRange.first[0]}, + [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.z(target[0])}; + }); + qubitPairRange = programBuilder.ctrl( + {qubitPairRange.first[1], qubitPairRangeOne.second[0]}, + qubitPairRangeOne.first, [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.h(target[0])}; + }); + qubitPairRange = programBuilder.ctrl( + qubitPairRange.first, qubitPairRange.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{programBuilder.z(target[0])}; + }); + programBuilder.ctrl(qubitPairRange.second, {qubitPairRange.first[0]}, + [&](mlir::ValueRange target) { return llvm::SmallVector{ - programBuilder.h(target[0])}; + programBuilder.z(target[0])}; }); - // TODO: nctrl module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -377,10 +396,30 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { {qRef[2], qRef[0]}, {qRef[1]}, [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.h(target[0])}; }); - referenceBuilder.ctrl(qubitPairRangeRef.first, qubitPairRangeRef.second, + qubitPairRangeRef = referenceBuilder.ctrl( + qubitPairRangeRef.first, qubitPairRangeRef.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.x(target[0])}; + }); + auto qubitPairRangeOneRef = referenceBuilder.ctrl( + qubitPairRangeRef.second, {qubitPairRangeRef.first[0]}, + [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.z(target[0])}; + }); + qubitPairRangeRef = referenceBuilder.ctrl( + {qubitPairRangeRef.first[1], qubitPairRangeOneRef.second[0]}, + qubitPairRangeOneRef.first, [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.h(target[0])}; + }); + qubitPairRangeRef = referenceBuilder.ctrl( + qubitPairRangeRef.first, qubitPairRangeRef.second, + [&](mlir::ValueRange target) { + return llvm::SmallVector{referenceBuilder.z(target[0])}; + }); + referenceBuilder.ctrl(qubitPairRangeRef.second, {qubitPairRangeRef.first[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{ - referenceBuilder.x(target[0])}; + referenceBuilder.z(target[0])}; }); reference = referenceBuilder.finalize(); PassManager pmRef(module.get().getContext()); @@ -392,8 +431,6 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } -// TODO: @testLiftHadamardOverPauliGateIfControlsFit - /** * @brief Test: Checks that a hadamard gate is lifted over a CNOT gate target if * a measurement is following directly after it. From fa877c61455357a2f7107a9b39596d01ae8fb669 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 27 Mar 2026 15:54:01 +0100 Subject: [PATCH 003/131] :construction: Added framework for hadamard lifting pass --- CMakeLists.txt | 2 + .../mlir/Dialect/QCO/Transforms/Passes.td | 58 ++++++++ .../Optimization/HadamardLifting.cpp | 140 ++++++++++++++++++ .../Transforms/Optimization/CMakeLists.txt | 9 +- .../test_qco_hadamard_lifting.cpp | 25 ++-- 5 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 75e455919d..b9aba19734 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,8 @@ project( LANGUAGES C CXX DESCRIPTION "MQT Core - The Backbone of the Munich Quantum Toolkit") +list(APPEND CMAKE_PREFIX_PATH "/lib/llvm-22/lib/cmake/mlir" "/lib/llvm-22/lib/cmake/llvm") + if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 20) set(CMAKE_CXX_STANDARD 20 diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index bbb0c05865..d8baca7a1e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -73,4 +73,62 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { "The number of inserted SWAPs">]; } +//===----------------------------------------------------------------------===// +// Optimization Passes +//===----------------------------------------------------------------------===// + +def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect"]; + let summary = "This pass attempts to lift Hadamards as much as possible by flipping them with Pauli gates."; + let description = [{ + This pass lifts hadamards away from the measurements in order to apply lift-measurements more effectively. It uses + the following commutation rules: + ┌───┐ ┌───┐ ┌───┐ ┌───┐ + ─┤ X ├─┤ H ├─ => ─┤ H ├─┤ Z ├─ + └───┘ └───┘ └───┘ └───┘ + ┌───┐ ┌───┐ ┌───┐ ┌───┐ + ─┤ Z ├─┤ H ├─ => ─┤ H ├─┤ X ├─ + └───┘ └───┘ └───┘ └───┘ + ┌───┐ ┌───┐ ┌───┐ ┌───┐ + ─┤ Y ├─┤ H ├─ => ─┤ H ├─┤ Y ├─ + └───┘ └───┘ └───┘ └───┘ + Hadamard lifting is only applied to lift Hadamard gates further in front in the circuit, not the other way around. + + If the Hadamard and Pauli gates are controlled, they are only lifted if both gates are controlled by exactly the + same qubits. One exception to this are the controlled Pauli Z gates. For these gates, controls and targets are + interchangable. Therefore, tranformation routines as follows are possible and applied: + ┌───┐ + ┤ Z ├──■── ──■────■── ──■────■── + └─┬─┘┌─┴─┐ => ┌─┴─┐┌─┴─┐ => ┌─┴─┐┌─┴─┐ + ──■──┤ H ├ ┤ Z ├┤ H ├ ┤ H ├┤ X ├ + └───┘ └───┘└───┘ └───┘└───┘ + The commutation of controlled gates is only done if there are no gates applied between the two controlled gates to + any of the qubits used on the controlled gates. + + In order to minimize the reduce multi-qubit gates, a third transformation routine is applied. Using the commutation + rule + ┌───┐┌───┐┌───┐ + ──■── ┤ H ├┤ X ├┤ H ├ + ┌─┴─┐ => │───│└─┬─┘│───│ + ┤ X ├ ┤ H ├──■──┤ H ├ + └───┘ └───┘ └───┘ + the following transformation is applied to circuits pattern, where a Hadamard gate follows a the target gate of a + CNOT, which is followed by a measurement: + ┌───┐┌───┐┌───┐ + ──■───────────── ┤ H ├┤ X ├┤ H ├── + ┌─┴─┐┌───┐┌──────┐ => │───│└─┬─┘│───┘──┐ + ┤ X ├┤ H ├┤ Meas │ ┤ H ├──■──┤ Meas │ + └───┘└───┘└──────┘ └───┘ └──────┘ + + Afterwards, the measurement lifting routine could transform the CNOT into a classically controlled Pauli X. + + }]; + let statistics = [Statistic<"numPauliZTargetChange", "num-pauli-z-target-change", + "The number of times the target qubit of controlled Pauli Z gates was changed.">, + Statistic<"numHadamardLiftings", "num-hadamard-liftings", + "The number of times Hadamard gates were lifted in front of Pauli gates.">, + Statistic<"numMeasurementHadamardCNOTLiftings", "num-measurement-hadamard-cnot-liftings", + "The number of times the target and controls of a CNOT were lifted to put a measurement directly after a control qubit.">]; +} + #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp new file mode 100644 index 0000000000..2c36120c23 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace mlir::qco { + +#define GEN_PASS_DEF_HADAMARDLIFTING +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +namespace { + +/** + * @brief This pattern changes the target of a controlled Pauli Z gate if a + * controlled hadamard gate is it successor. + * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can + * commute the gates and change Pauli Z to X. This is only possible if Hadamard + * and Pauli act on the same qubit as target. If the target of the Pauli gate is + * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to + * the Hadamard's. This is done in this pattern. + */ +struct AdaptCtrldPauliZToLiftingPattern final + : mlir::OpInterfaceRewritePattern { + + explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + /** + * @brief Changes the target of a controlled Pauli Z gate if a + * controlled hadamard gate is it successor. + * + * @param op The operation to match (only Pauli gates trigger the rewrite) + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise + */ + mlir::LogicalResult + matchAndRewrite(UnitaryOpInterface op, + mlir::PatternRewriter& rewriter) const override { + return failure(); + } +}; + +/** + * @brief This pattern is responsible for lifting Hadamard gates above Pauli + * gates. + */ +struct LiftHadamardsAbovePauliGatesPattern final + : OpInterfaceRewritePattern { + explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + /** + * @brief Lifts Hadamard gates in front of Pauli gates. + * + * @param op The operation to match (only Pauli gates trigger the rewrite) + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise + */ + LogicalResult matchAndRewrite(UnitaryOpInterface op, + PatternRewriter& rewriter) const override { + return failure(); + } +}; + +/** + * @brief This pattern remove an H gate between a CNOT and a measurement. + * + * If there is a Hadamard gate between the target qubit of a CNOT and a + * measurement, we flip the CNOT and apply a hadamard gate to the incoming and + * outcoming qubits. As H * H = id, the measurement is then the direct successor + * of a CNOT ctrl, which is beneficial for the qubit reuse routine. + * The procedure also works if there are additional ctrls. Only the target + * and ctrl involved in the transformation get hadamard gates assigned. + * For now, the involved ctrl to be flipped with the target is chosen randomly. + */ +struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { + + explicit LiftHadamardAboveCNOTPattern(mlir::MLIRContext* context) + : OpRewritePattern(context) {} + + /** + * @brief This pattern remove an H gate between a CNOT and a measurement. + * + * @param op The operation to match (only uncontrolled Hadamard gates trigger + * the rewrite) + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise + */ + mlir::LogicalResult + matchAndRewrite(MeasureOp op, + mlir::PatternRewriter& rewriter) const override { + return mlir::failure(); + } +}; + +/** + * @brief Pass raises Hadamard gates above controlled and uncontrolled Pauli + * gates. + */ +struct HadamardLifting final : impl::HadamardLiftingBase { + using HadamardLiftingBase::HadamardLiftingBase; + +protected: + void runOnOperation() override { + auto op = getOperation(); + auto* ctx = &getContext(); + + // Define the set of patterns to use. + RewritePatternSet patterns(ctx); + patterns.add(patterns.getContext()); + patterns.add(patterns.getContext()); + patterns.add(patterns.getContext()); + + // Apply patterns in an iterative and greedy manner. + if (failed(applyPatternsGreedily(op, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace + +} // namespace mlir::qco \ No newline at end of file diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt index a2612c752c..6b0c310232 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt @@ -14,8 +14,15 @@ target_link_libraries( PRIVATE GTest::gtest_main MLIRParser MLIRQCOProgramBuilder + MLIRQCOTransforms MLIRSupportMQT - MLIRQTensorDialect) + MLIRQTensorDialect + MLIRQCOUtils + MLIRParser + MLIRIR + MLIRPass + MLIRSupport + LLVMSupport) mqt_mlir_configure_unittest_target(${target_name}) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 37bd597b4e..a64cf7beb3 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -11,7 +11,7 @@ #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -// #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Support/IRVerification.h" #include @@ -26,9 +26,6 @@ #include #include -#include -#include -#include namespace { @@ -62,7 +59,7 @@ class QCOHadamardLiftingTest : public ::testing::Test { */ static LogicalResult runHadamardLiftingPass(ModuleOp module) { PassManager pm(module.getContext()); - // pm.addPass(qco::createLiftHadamardGates()); + pm.addPass(qco::createHadamardLifting()); return pm.run(module); } }; @@ -70,7 +67,7 @@ class QCOHadamardLiftingTest : public ::testing::Test { } // namespace // ################################################## -// # Raise Hadamard over one Pauli gate Tests +// # Raise Hadamard over uncontrolled Pauli gate Tests // ################################################## /** @@ -208,6 +205,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +// ################################################## +// # Raise Hadamard over controlled Pauli gate Tests +// ################################################## + /** * @brief Test: Checks if hadamard gates are lifted if they are controlled by * the same qubit as the lifted gate is. @@ -281,7 +282,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { } /** - * @brief Test: Checks that a hadamard gate is not lifted if there is another + * @brief Test: Checks that a Hadamard gate is not lifted if there is another * gate between the controls of the Pauli and the Hadamard gate. */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { @@ -431,8 +432,12 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +// ################################################## +// # Raise Hadamard over CNOT gates Tests +// ################################################## + /** - * @brief Test: Checks that a hadamard gate is lifted over a CNOT gate target if + * @brief Test: Checks that a Hadamard gate is lifted over a CNOT gate target if * a measurement is following directly after it. */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { @@ -464,7 +469,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { } /** - * @brief Test: Checks that a hadamard gate is lifted over the target of a + * @brief Test: Checks that a Hadamard gate is lifted over the target of a * multiple controlled x gate if a measurement is following directly after it. */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { @@ -500,7 +505,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { } /** - * @brief Test: Checks that a hadamard gate is not lifted over a CNOT gate + * @brief Test: Checks that a Hadamard gate is not lifted over a CNOT gate * target if a measurement is not following directly after it. */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { From d784d01499cc2a4a2a2b148173d14b50d9619180 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 30 Mar 2026 14:01:31 +0200 Subject: [PATCH 004/131] :construction: Started exchange of Hadamard and Pauli --- .../Optimization/HadamardLifting.cpp | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 2c36120c23..3b3ceec003 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -66,6 +66,102 @@ struct LiftHadamardsAbovePauliGatesPattern final explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) : OpInterfaceRewritePattern(context) {} + /** + * @brief This method checks if two ranges contain of exactly the same + * elements. + * + * This method checks if two ranges contain of exactly the same elements. + * + * @param range1 The first range. + * @param range2 The second range. + */ + static bool containRangesOfSameElements(const std::vector& range1, + const std::vector& range2) { + bool result = true; + result &= range1.size() == range2.size(); + for (auto element : range1) { + result &= + std::find(range2.begin(), range2.end(), element) != range2.end(); + } + return result; + } + + /** + * @brief This method checks if two gates are connected by exactly the same + * target and ctrl qubits. + * + * This method checks if the output target/ctrl qubits of the first gate are + * exactly the input target/ctrl qubits of the second gate. There must be no + * qubit that is only used by one of the gates. + * + * @param firstGate The first unitary gate. + * @param secondGate The second unitary gate. + */ + static bool + areGatesConnectedExactlyBySameQubits(UnitaryOpInterface firstGate, + UnitaryOpInterface secondGate) { + if (firstGate.getNumTargets() != secondGate.getNumTargets() || + firstGate.getNumControls() != secondGate.getNumControls()) { + return false; + } + std::vector targetOutputsFirstGate; + std::vector controlOutputsFirstGate; + std::vector targetInputsSecondGate; + std::vector controlInputsSecondGate; + for (size_t i = 0; i < firstGate.getNumTargets(); i++) { + targetOutputsFirstGate.push_back(firstGate.getOutputTarget(i)); + targetInputsSecondGate.push_back(secondGate.getInputTarget(i)); + } + for (size_t i = 0; i < firstGate.getNumControls(); i++) { + targetOutputsFirstGate.push_back(firstGate.getOutputControl(i)); + targetInputsSecondGate.push_back(secondGate.getInputControl(i)); + } + + bool result = true; + result &= containRangesOfSameElements(targetOutputsFirstGate, + targetInputsSecondGate); + result &= containRangesOfSameElements(controlOutputsFirstGate, + controlInputsSecondGate); + return result; + } + + /** + * @brief This method swaps a gate with is succeeding hadamard gate, if + * applicable. + * + * This method swaps a gate with its suceeding hadamard gate. This is only + * done if there is a simple commutation rule to do so. + * Currently implemented: + * - X - H - = - H - Z - + * - Y - H - = - H - Y - + * - Z - H - = - H - X - + * + * @param gate The unitary gate. + * @param hadamardGate The hadamard gate. + * @param rewriter The used rewriter. + */ + static mlir::LogicalResult + swapGateWithHadamard(UnitaryOpInterface gate, UnitaryOpInterface hadamardGate, + mlir::PatternRewriter& rewriter) { + const auto gateName = gate->getName().stripDialect().str(); + + if (gateName == "x" || gateName == "y" || gateName == "z") { + // if (gateName == "x") { + // rewriter.replaceOpWithNewOp(hadamardGate, + // hadamardGate.getInputQubit(0)); + // } else if (gateName == "z") { + // rewriter.replaceOpWithNewOp(hadamardGate, + // hadamardGate.getInputQubit(0)); + // } else { + // rewriter.replaceOpWithNewOp(hadamardGate, + // hadamardGate.getInputQubit(0)); + // } + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + return success(); + } + return failure(); + } + /** * @brief Lifts Hadamard gates in front of Pauli gates. * @@ -75,7 +171,29 @@ struct LiftHadamardsAbovePauliGatesPattern final */ LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { - return failure(); + // op needs to be a Pauli gate + std::string opName = op->getName().stripDialect().str(); + if (opName != "x" && opName != "y" && opName != "z") { + return failure(); + } + + // op needs to be in front of a hadamard gate + const auto& users = op->getUsers(); + if (users.empty()) { + return failure(); + } + auto user = *users.begin(); + if (user->getName().stripDialect().str() != "h") { + return failure(); + } + + auto hadamardGate = mlir::dyn_cast(user); + + if (!areGatesConnectedExactlyBySameQubits(op, hadamardGate)) { + return failure(); + } + + return swapGateWithHadamard(op, hadamardGate, rewriter); } }; From e1b6b378bcb56e342ded4799bf924d2eab0d7c16 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 11:51:37 +0200 Subject: [PATCH 005/131] :construction: Lifting uncontrolled Hadamard gates above Pauli --- .../Optimization/HadamardLifting.cpp | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 3b3ceec003..0941c2c03e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "../../../../../../include/mqt-core/ir/operations/OpType.hpp" #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" @@ -146,17 +147,24 @@ struct LiftHadamardsAbovePauliGatesPattern final const auto gateName = gate->getName().stripDialect().str(); if (gateName == "x" || gateName == "y" || gateName == "z") { - // if (gateName == "x") { - // rewriter.replaceOpWithNewOp(hadamardGate, - // hadamardGate.getInputQubit(0)); - // } else if (gateName == "z") { - // rewriter.replaceOpWithNewOp(hadamardGate, - // hadamardGate.getInputQubit(0)); - // } else { - // rewriter.replaceOpWithNewOp(hadamardGate, - // hadamardGate.getInputQubit(0)); - // } - rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + auto newHadamardGate = rewriter.replaceOpWithNewOp( + gate, gate.getOutputQubit(0).getType(), gate.getInputQubit(0)); + if (gateName == "x") { + auto newPauliGate = rewriter.replaceOpWithNewOp( + hadamardGate, hadamardGate.getOutputQubit(0).getType(), + hadamardGate.getInputQubit(0)); + rewriter.moveOpBefore(newHadamardGate, newPauliGate); + } else if (gateName == "z") { + auto newPauliGate = rewriter.replaceOpWithNewOp( + hadamardGate, hadamardGate.getOutputQubit(0).getType(), + hadamardGate.getInputQubit(0)); + rewriter.moveOpBefore(newHadamardGate, newPauliGate); + } else { + auto newPauliGate = rewriter.replaceOpWithNewOp( + hadamardGate, hadamardGate.getOutputQubit(0).getType(), + hadamardGate.getInputQubit(0)); + rewriter.moveOpBefore(newHadamardGate, newPauliGate); + } return success(); } return failure(); From 9dc386e44143ba753f0c93202872e09dd5d00128 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 14:29:02 +0200 Subject: [PATCH 006/131] :white_check_mark: Added canonicalization to references --- .../test_qco_hadamard_lifting.cpp | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index a64cf7beb3..c369c438fb 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -13,6 +13,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Support/IRVerification.h" +#include "mlir/Support/Passes.h" #include #include @@ -21,8 +22,8 @@ #include #include #include +#include #include -#include #include #include @@ -59,7 +60,7 @@ class QCOHadamardLiftingTest : public ::testing::Test { */ static LogicalResult runHadamardLiftingPass(ModuleOp module) { PassManager pm(module.getContext()); - pm.addPass(qco::createHadamardLifting()); + pm.addPass(createHadamardLifting()); return pm.run(module); } }; @@ -91,10 +92,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -121,10 +122,10 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftPauliOverHadamardGate) { qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -163,10 +164,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { qRef[2] = referenceBuilder.z(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -196,10 +197,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { qRef[1] = referenceBuilder.x(qRef[1]); qRef[1] = referenceBuilder.z(qRef[1]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -234,10 +235,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { qubitPairRangeRef.second[0]); referenceBuilder.dcx(qubitPairRef.first, qubitPairRef.second); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -272,10 +273,10 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { return llvm::SmallVector{referenceBuilder.h(target[0])}; }); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -309,10 +310,10 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { return llvm::SmallVector{referenceBuilder.h(target[0])}; }); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -346,10 +347,10 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { referenceBuilder.h(target[0])}; }); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -423,10 +424,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { referenceBuilder.z(target[0])}; }); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -459,10 +460,9 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { referenceBuilder.measure(qubitPairRef.second, bRef[0]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -495,10 +495,9 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { referenceBuilder.measure(qubitPairRangeRef.second[0], bRef[0]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -535,10 +534,9 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { referenceBuilder.measure(qRef[5], bRef[2]); reference = referenceBuilder.finalize(); - PassManager pmRef(module.get().getContext()); - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(pmRef.run(reference.get()).succeeded()); + runCanonicalizationPasses(reference.get()); + EXPECT_TRUE(verify(*reference).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); From 5653b9a6850c61007622c25ea3ab139d75d3d3fa Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 15:28:37 +0200 Subject: [PATCH 007/131] :white_check_mark: Corrected gates --- .../test_qco_hadamard_lifting.cpp | 132 ++++++------------ 1 file changed, 40 insertions(+), 92 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index c369c438fb..057241f062 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -217,23 +217,16 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.x(q[0]); - auto qubit_pair = programBuilder.dcx(q[1], q[0]); - auto qubitPairRange = programBuilder.ctrl( - {qubit_pair.first}, {qubit_pair.second}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; - }); - programBuilder.dcx(qubitPairRange.first[0], qubitPairRange.second[0]); + auto qubitPair = programBuilder.cx(q[1], q[0]); + qubitPair = programBuilder.ch(qubitPair.first, qubitPair.second); + programBuilder.cx(qubitPair.first, qubitPair.second); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.x(qRef[0]); - auto qubitPairRangeRef = - referenceBuilder.ctrl({qRef[1]}, {qRef[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; - }); - auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.first[0], - qubitPairRangeRef.second[0]); - referenceBuilder.dcx(qubitPairRef.first, qubitPairRef.second); + auto qubitPairRef = referenceBuilder.ch(qRef[1], qRef[0]); + qubitPairRef = referenceBuilder.cz(qubitPairRef.first, qubitPairRef.second); + referenceBuilder.cx(qubitPairRef.first, qubitPairRef.second); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -250,28 +243,17 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { auto q = programBuilder.allocQubitRegister(3); - auto qubit_pair = programBuilder.dcx(q[1], q[0]); - auto qubitPairRange = programBuilder.ctrl( - {q[2]}, {qubit_pair.second}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; - }); - q[0] = programBuilder.z(qubitPairRange.second[0]); - programBuilder.ctrl({qubit_pair.first}, {q[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; - }); + auto qubitPair = programBuilder.cx(q[1], q[0]); + qubitPair = programBuilder.ch(q[2], qubitPair.second); + q[0] = programBuilder.z(qubitPair.second); + programBuilder.ch(qubitPair.first, q[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); - auto qubitPairRef = referenceBuilder.dcx(qRef[1], qRef[0]); - auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[2]}, {qubitPairRef.second}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; - }); - qRef[0] = referenceBuilder.z(qubitPairRangeRef.second[0]); - referenceBuilder.ctrl( - {qubitPairRef.first}, {qRef[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; - }); + auto qubitPairRef = referenceBuilder.cx(qRef[1], qRef[0]); + qubitPairRef = referenceBuilder.ch(qRef[2], qubitPairRef.second); + qRef[0] = referenceBuilder.z(qubitPairRef.second); + referenceBuilder.ch(qubitPairRef.first, qRef[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -288,27 +270,15 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { auto q = programBuilder.allocQubitRegister(2); - auto qubitPairRange = - programBuilder.ctrl({q[1]}, {q[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; - }); - q[1] = programBuilder.s(qubitPairRange.first[0]); - programBuilder.ctrl( - {q[1]}, qubitPairRange.second, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; - }); + auto qubitPair = programBuilder.cz(q[1], q[0]); + q[1] = programBuilder.s(qubitPair.first); + programBuilder.ch(q[1], qubitPair.second); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); - auto qubitPairRangeRef = - referenceBuilder.ctrl({qRef[1]}, {qRef[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.z(target[0])}; - }); - qRef[1] = referenceBuilder.s(qubitPairRangeRef.first[0]); - referenceBuilder.ctrl( - {qRef[1]}, qubitPairRangeRef.second[0], [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; - }); + auto qubitPairRef = referenceBuilder.cz(qRef[1], qRef[0]); + qRef[1] = referenceBuilder.s(qubitPairRef.first); + referenceBuilder.ch(qRef[1], qubitPairRef.second); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -329,11 +299,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.z(target[0])}; }); - programBuilder.ctrl({qubitPairRange.first[0]}, qubitPairRange.second, - [&](mlir::ValueRange target) { - return llvm::SmallVector{ - programBuilder.h(target[0])}; - }); + programBuilder.ch(qubitPairRange.first[0], qubitPairRange.second[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -341,11 +307,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { {qRef[1], qRef[2]}, {qRef[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.z(target[0])}; }); - referenceBuilder.ctrl({qubitPairRangeRef.first[0]}, qubitPairRangeRef.second, - [&](mlir::ValueRange target) { - return llvm::SmallVector{ - referenceBuilder.h(target[0])}; - }); + referenceBuilder.ch(qubitPairRangeRef.first[0], qubitPairRangeRef.second[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -371,14 +333,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.h(target[0])}; }); - auto qubitPairRangeOne = programBuilder.ctrl( - qubitPairRange.second, {qubitPairRange.first[0]}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; - }); + auto qubitPair = + programBuilder.cz(qubitPairRange.second[0], qubitPairRange.first[0]); qubitPairRange = programBuilder.ctrl( - {qubitPairRange.first[1], qubitPairRangeOne.second[0]}, - qubitPairRangeOne.first, [&](mlir::ValueRange target) { + {qubitPairRange.first[1], qubitPair.second}, {qubitPair.first}, + [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.h(target[0])}; }); qubitPairRange = programBuilder.ctrl( @@ -386,11 +345,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.z(target[0])}; }); - programBuilder.ctrl(qubitPairRange.second, {qubitPairRange.first[0]}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{ - programBuilder.z(target[0])}; - }); + programBuilder.cz(qubitPairRange.second[0], qubitPairRange.first[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -403,14 +358,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.x(target[0])}; }); - auto qubitPairRangeOneRef = referenceBuilder.ctrl( - qubitPairRangeRef.second, {qubitPairRangeRef.first[0]}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.z(target[0])}; - }); + auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.second[0], + qubitPairRangeRef.first[0]); qubitPairRangeRef = referenceBuilder.ctrl( - {qubitPairRangeRef.first[1], qubitPairRangeOneRef.second[0]}, - qubitPairRangeOneRef.first, [&](mlir::ValueRange target) { + {qubitPairRangeRef.first[1], qubitPairRef.second}, {qubitPairRef.first}, + [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.h(target[0])}; }); qubitPairRangeRef = referenceBuilder.ctrl( @@ -418,11 +370,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.z(target[0])}; }); - referenceBuilder.ctrl(qubitPairRangeRef.second, {qubitPairRangeRef.first[0]}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{ - referenceBuilder.z(target[0])}; - }); + referenceBuilder.cz(qubitPairRangeRef.second[0], qubitPairRangeRef.first[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -445,7 +393,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { auto q = programBuilder.allocQubitRegister(2); auto b = programBuilder.allocClassicalBitRegister(1); q[0] = programBuilder.s(q[0]); - auto qubitPair = programBuilder.dcx(q[0], q[1]); + auto qubitPair = programBuilder.cx(q[0], q[1]); q[1] = programBuilder.h(qubitPair.second); programBuilder.measure(q[1], b[0]); module = programBuilder.finalize(); @@ -455,7 +403,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { qRef[0] = referenceBuilder.s(qRef[0]); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); - auto qubitPairRef = referenceBuilder.dcx(qRef[1], qRef[0]); + auto qubitPairRef = referenceBuilder.cx(qRef[1], qRef[0]); referenceBuilder.h(qubitPairRef.first); referenceBuilder.measure(qubitPairRef.second, bRef[0]); reference = referenceBuilder.finalize(); @@ -510,10 +458,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { auto q = programBuilder.allocQubitRegister(6); auto b = programBuilder.allocClassicalBitRegister(3); - programBuilder.dcx(q[1], q[0]); - auto qubitPairOne = programBuilder.dcx(q[3], q[2]); + programBuilder.cx(q[1], q[0]); + auto qubitPairOne = programBuilder.cx(q[3], q[2]); programBuilder.measure(qubitPairOne.first, b[0]); - auto qubitPairTwo = programBuilder.dcx(q[5], q[4]); + auto qubitPairTwo = programBuilder.cx(q[5], q[4]); q[4] = programBuilder.h(qubitPairTwo.second); q[5] = programBuilder.h(qubitPairTwo.first); q[5] = programBuilder.s(q[5]); @@ -523,10 +471,10 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { auto qRef = referenceBuilder.allocQubitRegister(6); auto bRef = referenceBuilder.allocClassicalBitRegister(3); - referenceBuilder.dcx(qRef[1], qRef[0]); - auto qubitPairOneRef = referenceBuilder.dcx(qRef[3], qRef[2]); + referenceBuilder.cx(qRef[1], qRef[0]); + auto qubitPairOneRef = referenceBuilder.cx(qRef[3], qRef[2]); referenceBuilder.measure(qubitPairOneRef.first, bRef[0]); - auto qubitPairTwoRef = referenceBuilder.dcx(qRef[5], qRef[4]); + auto qubitPairTwoRef = referenceBuilder.cx(qRef[5], qRef[4]); qRef[4] = referenceBuilder.h(qubitPairTwoRef.second); qRef[5] = referenceBuilder.h(qubitPairTwoRef.first); qRef[5] = referenceBuilder.s(qRef[5]); From 67d3c7cc9c10971de2e60fb31dc08747a5bb4ee3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 12:18:12 +0200 Subject: [PATCH 008/131] :construction: Added handling of controlled gates --- .../Optimization/HadamardLifting.cpp | 217 ++++++++++++------ .../test_qco_hadamard_lifting.cpp | 8 +- 2 files changed, 149 insertions(+), 76 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 0941c2c03e..c7a0809fe7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -29,43 +29,21 @@ namespace mlir::qco { namespace { /** - * @brief This pattern changes the target of a controlled Pauli Z gate if a - * controlled hadamard gate is it successor. - * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can - * commute the gates and change Pauli Z to X. This is only possible if Hadamard - * and Pauli act on the same qubit as target. If the target of the Pauli gate is - * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to - * the Hadamard's. This is done in this pattern. + * @brief Lifts controlled Hadamard gates in front of controlled Pauli gates. + * + * This pattern lifts controlled Hadamard gates in front of controlled Pauli + * gates. This can be done if the output target of the Pauli gate is the input + * target of the Hadamard gate. Also, all control output qubits need to be the + * input control qubits of the Hadamard gates. If the Pauli gate is a Z gate, + * the target and control qubits can be changed upfront in order to be able to + * lift it. */ -struct AdaptCtrldPauliZToLiftingPattern final - : mlir::OpInterfaceRewritePattern { - - explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) - : OpInterfaceRewritePattern(context) {} - - /** - * @brief Changes the target of a controlled Pauli Z gate if a - * controlled hadamard gate is it successor. - * - * @param op The operation to match (only Pauli gates trigger the rewrite) - * @param rewriter Pattern rewriter for applying transformations - * @return success() if circuit was changed, failure() otherwise - */ - mlir::LogicalResult - matchAndRewrite(UnitaryOpInterface op, - mlir::PatternRewriter& rewriter) const override { - return failure(); - } -}; +struct LiftCtrldHadamardsAboveCtrldPauliGatesPattern final + : mlir::OpRewritePattern { -/** - * @brief This pattern is responsible for lifting Hadamard gates above Pauli - * gates. - */ -struct LiftHadamardsAbovePauliGatesPattern final - : OpInterfaceRewritePattern { - explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) - : OpInterfaceRewritePattern(context) {} + explicit LiftCtrldHadamardsAboveCtrldPauliGatesPattern( + mlir::MLIRContext* context) + : OpRewritePattern(context) {} /** * @brief This method checks if two ranges contain of exactly the same @@ -88,51 +66,134 @@ struct LiftHadamardsAbovePauliGatesPattern final } /** - * @brief This method checks if two gates are connected by exactly the same - * target and ctrl qubits. + * @brief This method swaps a controlled gate with is succeeding controlled + * Hadamard gate, if applicable. * - * This method checks if the output target/ctrl qubits of the first gate are - * exactly the input target/ctrl qubits of the second gate. There must be no - * qubit that is only used by one of the gates. + * This method swaps a controlled gate with its succeeding controlled + * Hadamard gate. This is only done if there is a simple commutation rule to + * do so. Currently implemented: + * - X - H - = - H - Z - + * - Y - H - = - H - Y - + * - Z - H - = - H - X - * - * @param firstGate The first unitary gate. - * @param secondGate The second unitary gate. + * @param ctrlGate The controlled unitary gate. + * @param ctrlHadamardGate The controlled hadamard gate. + * @param rewriter The used rewriter. + * @return success() if circuit was changed, failure() otherwise */ - static bool - areGatesConnectedExactlyBySameQubits(UnitaryOpInterface firstGate, - UnitaryOpInterface secondGate) { - if (firstGate.getNumTargets() != secondGate.getNumTargets() || - firstGate.getNumControls() != secondGate.getNumControls()) { - return false; + static mlir::LogicalResult + swapGateWithHadamardControlled(CtrlOp ctrlGate, CtrlOp ctrlHadamardGate, + mlir::PatternRewriter& rewriter) { + auto gate = ctrlGate.getBodyUnitary(); + auto hadamardGate = ctrlHadamardGate.getBodyUnitary(); + const auto gateName = gate->getName().stripDialect().str(); + + if (gateName == "x" || gateName == "y" || gateName == "z") { + rewriter.setInsertionPoint(gate); + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + if (gateName == "x") { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + } else if (gateName == "z") { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + } else { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + } + return success(); } - std::vector targetOutputsFirstGate; - std::vector controlOutputsFirstGate; - std::vector targetInputsSecondGate; - std::vector controlInputsSecondGate; - for (size_t i = 0; i < firstGate.getNumTargets(); i++) { - targetOutputsFirstGate.push_back(firstGate.getOutputTarget(i)); - targetInputsSecondGate.push_back(secondGate.getInputTarget(i)); + return failure(); + } + + /** + * This method checks whether the first and second controls are controlled by + * the same qubits. + * + * @param firstCtrl The first (preceding) controlled gate. + * @param secondCtrl The second (succeeding) controlled gate. + * @return true if the controls are controlled by the same qubits. + */ + static bool areControlsControlledBySameQubits(CtrlOp firstCtrl, + CtrlOp secondCtrl) { + std::vector controlOutputsFirstGate( + firstCtrl.getControlsOut().begin(), firstCtrl.getControlsOut().end()); + std::vector controlInputsSecondGate( + secondCtrl.getControlsIn().begin(), secondCtrl.getControlsIn().end()); + return containRangesOfSameElements(controlOutputsFirstGate, + controlInputsSecondGate); + } + + /** + * @brief Lifts controlled Hadamard gates in front of controlled Pauli gates. + * + * This pattern lifts controlled Hadamard gates in front of controlled Pauli + * gates. This can be done if the output target of the Pauli gate is the input + * target of the Hadamard gate. Also, all control output qubits need to be the + * input control qubits of the Hadamard gates. If the Pauli gate is a Z gate, + * the target and control qubits can be changed upfront in order to be able to + * lift it. + * + * @param op The controlled operation to match (only controlled Pauli gates + * trigger the rewrite) + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise + */ + mlir::LogicalResult + matchAndRewrite(CtrlOp op, mlir::PatternRewriter& rewriter) const override { + // op needs to be a controlled Pauli gate + std::string opName = op.getBodyUnitary()->getName().stripDialect().str(); + if (opName != "x" && opName != "y" && opName != "z") { + return failure(); } - for (size_t i = 0; i < firstGate.getNumControls(); i++) { - targetOutputsFirstGate.push_back(firstGate.getOutputControl(i)); - targetInputsSecondGate.push_back(secondGate.getInputControl(i)); + + // op needs to be in front of a controlled Hadamard gate + const auto& users = op->getUsers(); + if (users.empty()) { + return failure(); + } + auto user = *users.begin(); + auto userName = user->getName().stripDialect().str(); + if (userName != "ctrl") { + return failure(); + } + auto ctrldUser = mlir::dyn_cast(user); + if (ctrldUser.getBodyUnitary()->getName().stripDialect().str() != "h") { + return failure(); } - bool result = true; - result &= containRangesOfSameElements(targetOutputsFirstGate, - targetInputsSecondGate); - result &= containRangesOfSameElements(controlOutputsFirstGate, - controlInputsSecondGate); - return result; + if (op.getNumTargets() != 1 || ctrldUser.getNumTargets() != 1 || + op.getOutputTarget(0) != ctrldUser.getInputTarget(0)) { + return failure(); + } + + if (!areControlsControlledBySameQubits(op, ctrldUser)) { + return failure(); + } + + return swapGateWithHadamardControlled(op, ctrldUser, rewriter); } +}; + +/** + * @brief This pattern is responsible for lifting uncontrolled Hadamard gates + * above uncontrolled Pauli gates. + */ +struct LiftHadamardsAbovePauliGatesPattern final + : OpInterfaceRewritePattern { + explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) + : OpInterfaceRewritePattern(context) {} /** - * @brief This method swaps a gate with is succeeding hadamard gate, if + * @brief This method swaps a gate with is succeeding Hadamard gate, if * applicable. * - * This method swaps a gate with its suceeding hadamard gate. This is only - * done if there is a simple commutation rule to do so. - * Currently implemented: + * This method swaps an uncontrolled gate with its succeeding uncontrolled + * Hadamard gate. This is only done if there is a simple commutation rule to + * do so. Currently implemented: * - X - H - = - H - Z - * - Y - H - = - H - Y - * - Z - H - = - H - X - @@ -142,8 +203,9 @@ struct LiftHadamardsAbovePauliGatesPattern final * @param rewriter The used rewriter. */ static mlir::LogicalResult - swapGateWithHadamard(UnitaryOpInterface gate, UnitaryOpInterface hadamardGate, - mlir::PatternRewriter& rewriter) { + swapGateWithHadamardUncontrolled(UnitaryOpInterface gate, + UnitaryOpInterface hadamardGate, + mlir::PatternRewriter& rewriter) { const auto gateName = gate->getName().stripDialect().str(); if (gateName == "x" || gateName == "y" || gateName == "z") { @@ -171,7 +233,8 @@ struct LiftHadamardsAbovePauliGatesPattern final } /** - * @brief Lifts Hadamard gates in front of Pauli gates. + * @brief Lifts uncontrolled Hadamard gates in front of uncontrolled Pauli + * gates. * * @param op The operation to match (only Pauli gates trigger the rewrite) * @param rewriter Pattern rewriter for applying transformations @@ -191,17 +254,20 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } auto user = *users.begin(); - if (user->getName().stripDialect().str() != "h") { + auto userName = user->getName().stripDialect().str(); + if (userName != "h") { return failure(); } auto hadamardGate = mlir::dyn_cast(user); - if (!areGatesConnectedExactlyBySameQubits(op, hadamardGate)) { + if (op.getNumControls() > 0 || hadamardGate.getNumControls() > 0 || + op.getNumTargets() != 1 || hadamardGate.getNumTargets() != 1 || + op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { return failure(); } - return swapGateWithHadamard(op, hadamardGate, rewriter); + return swapGateWithHadamardUncontrolled(op, hadamardGate, rewriter); } }; @@ -250,7 +316,8 @@ struct HadamardLifting final : impl::HadamardLiftingBase { // Define the set of patterns to use. RewritePatternSet patterns(ctx); - patterns.add(patterns.getContext()); + patterns.add( + patterns.getContext()); patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 057241f062..aae85027ba 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +// #include "mlir/Compiler/CompilerPipeline.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" @@ -211,7 +212,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { // ################################################## /** - * @brief Test: Checks if hadamard gates are lifted if they are controlled by + * @brief Test: Checks if Hadamard gates are lifted if they are controlled by * the same qubit as the lifted gate is. */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { @@ -233,6 +234,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); + // auto qco = captureIR(module.get()); + // std::cout << qco; + // qco = captureIR(reference.get()); + // std::cout << qco; + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } From 95e36049b998a980e2a53c6b9a65d80680568e91 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 13:28:06 +0200 Subject: [PATCH 009/131] :recycle: Refactored handling of controlled gates --- .../Optimization/HadamardLifting.cpp | 251 +++++++----------- 1 file changed, 99 insertions(+), 152 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index c7a0809fe7..6361608572 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -29,21 +29,51 @@ namespace mlir::qco { namespace { /** - * @brief Lifts controlled Hadamard gates in front of controlled Pauli gates. - * - * This pattern lifts controlled Hadamard gates in front of controlled Pauli - * gates. This can be done if the output target of the Pauli gate is the input - * target of the Hadamard gate. Also, all control output qubits need to be the - * input control qubits of the Hadamard gates. If the Pauli gate is a Z gate, - * the target and control qubits can be changed upfront in order to be able to - * lift it. + * @brief This pattern changes the target of a controlled Pauli Z gate if a + * controlled hadamard gate is it successor. + * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can + * commute the gates and change Pauli Z to X. This is only possible if Hadamard + * and Pauli act on the same qubit as target. If the target of the Pauli gate is + * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to + * the Hadamard's. This is done in this pattern. */ -struct LiftCtrldHadamardsAboveCtrldPauliGatesPattern final - : mlir::OpRewritePattern { +struct AdaptCtrldPauliZToLiftingPattern final + : mlir::OpInterfaceRewritePattern { - explicit LiftCtrldHadamardsAboveCtrldPauliGatesPattern( - mlir::MLIRContext* context) - : OpRewritePattern(context) {} + explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + /** + * @brief Changes the target of a controlled Pauli Z gate if a + * controlled hadamard gate is it successor. + * + * @param op The operation to match (only Pauli gates trigger the rewrite) + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise + */ + mlir::LogicalResult + matchAndRewrite(UnitaryOpInterface op, + mlir::PatternRewriter& rewriter) const override { + return failure(); + } +}; + +/** + * @brief This pattern is responsible for lifting Hadamard gates above Pauli + * gates. + * + * This pattern swaps a Pauli gate with a Hadamard gate. This is done using the + * commutation rules of Pauli and Hadamard gates, which are: + * - X - H - = - H - Z - + * - Y - H - = - H - Y - + * - Z - H - = - H - X - + * This is applied to uncontrolled gates and controlled ones, if the controls + * are applied to the same qubits for both gates. + */ +struct LiftHadamardsAbovePauliGatesPattern final + : OpInterfaceRewritePattern { + explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) + : OpInterfaceRewritePattern(context) {} /** * @brief This method checks if two ranges contain of exactly the same @@ -65,50 +95,6 @@ struct LiftCtrldHadamardsAboveCtrldPauliGatesPattern final return result; } - /** - * @brief This method swaps a controlled gate with is succeeding controlled - * Hadamard gate, if applicable. - * - * This method swaps a controlled gate with its succeeding controlled - * Hadamard gate. This is only done if there is a simple commutation rule to - * do so. Currently implemented: - * - X - H - = - H - Z - - * - Y - H - = - H - Y - - * - Z - H - = - H - X - - * - * @param ctrlGate The controlled unitary gate. - * @param ctrlHadamardGate The controlled hadamard gate. - * @param rewriter The used rewriter. - * @return success() if circuit was changed, failure() otherwise - */ - static mlir::LogicalResult - swapGateWithHadamardControlled(CtrlOp ctrlGate, CtrlOp ctrlHadamardGate, - mlir::PatternRewriter& rewriter) { - auto gate = ctrlGate.getBodyUnitary(); - auto hadamardGate = ctrlHadamardGate.getBodyUnitary(); - const auto gateName = gate->getName().stripDialect().str(); - - if (gateName == "x" || gateName == "y" || gateName == "z") { - rewriter.setInsertionPoint(gate); - rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); - if (gateName == "x") { - rewriter.setInsertionPoint(hadamardGate); - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - } else if (gateName == "z") { - rewriter.setInsertionPoint(hadamardGate); - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - } else { - rewriter.setInsertionPoint(hadamardGate); - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - } - return success(); - } - return failure(); - } - /** * This method checks whether the first and second controls are controlled by * the same qubits. @@ -128,113 +114,70 @@ struct LiftCtrldHadamardsAboveCtrldPauliGatesPattern final } /** - * @brief Lifts controlled Hadamard gates in front of controlled Pauli gates. + * @brief This method swaps a Pauli gate with a Hadamard gate. * - * This pattern lifts controlled Hadamard gates in front of controlled Pauli - * gates. This can be done if the output target of the Pauli gate is the input - * target of the Hadamard gate. Also, all control output qubits need to be the - * input control qubits of the Hadamard gates. If the Pauli gate is a Z gate, - * the target and control qubits can be changed upfront in order to be able to - * lift it. + * This method swaps a Pauli gate with a Hadamard gate. This is done using the + * commutation rules of Pauli and Hadamard gates, which are: + * - X - H - = - H - Z - + * - Y - H - = - H - Y - + * - Z - H - = - H - X - * - * @param op The controlled operation to match (only controlled Pauli gates - * trigger the rewrite) - * @param rewriter Pattern rewriter for applying transformations + * @param gate The Pauli gate. + * @param hadamardGate The Hadamard gate. + * @param rewriter The used rewriter. * @return success() if circuit was changed, failure() otherwise */ - mlir::LogicalResult - matchAndRewrite(CtrlOp op, mlir::PatternRewriter& rewriter) const override { - // op needs to be a controlled Pauli gate - std::string opName = op.getBodyUnitary()->getName().stripDialect().str(); - if (opName != "x" && opName != "y" && opName != "z") { - return failure(); - } - - // op needs to be in front of a controlled Hadamard gate - const auto& users = op->getUsers(); - if (users.empty()) { - return failure(); - } - auto user = *users.begin(); - auto userName = user->getName().stripDialect().str(); - if (userName != "ctrl") { - return failure(); - } - auto ctrldUser = mlir::dyn_cast(user); - if (ctrldUser.getBodyUnitary()->getName().stripDialect().str() != "h") { - return failure(); - } - - if (op.getNumTargets() != 1 || ctrldUser.getNumTargets() != 1 || - op.getOutputTarget(0) != ctrldUser.getInputTarget(0)) { + static mlir::LogicalResult + swapPauliWithHadamard(UnitaryOpInterface gate, + UnitaryOpInterface hadamardGate, + mlir::PatternRewriter& rewriter) { + const auto gateName = gate->getName().stripDialect().str(); + const auto hadamardName = hadamardGate->getName().stripDialect().str(); + if (hadamardName != "h" || + (gateName != "x" && gateName != "y" && gateName != "z")) { return failure(); } - - if (!areControlsControlledBySameQubits(op, ctrldUser)) { - return failure(); + rewriter.setInsertionPoint(gate); + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + if (gateName == "x") { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + } else if (gateName == "z") { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + } else { + rewriter.setInsertionPoint(hadamardGate); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); } - - return swapGateWithHadamardControlled(op, ctrldUser, rewriter); + return success(); } -}; - -/** - * @brief This pattern is responsible for lifting uncontrolled Hadamard gates - * above uncontrolled Pauli gates. - */ -struct LiftHadamardsAbovePauliGatesPattern final - : OpInterfaceRewritePattern { - explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) - : OpInterfaceRewritePattern(context) {} /** - * @brief This method swaps a gate with is succeeding Hadamard gate, if - * applicable. - * - * This method swaps an uncontrolled gate with its succeeding uncontrolled - * Hadamard gate. This is only done if there is a simple commutation rule to - * do so. Currently implemented: - * - X - H - = - H - Z - - * - Y - H - = - H - Y - - * - Z - H - = - H - X - + * @brief Swaps controlled Hadamard and Pauli gate if they follow after each + * other and are operated on by the same qubits. * - * @param gate The unitary gate. - * @param hadamardGate The hadamard gate. - * @param rewriter The used rewriter. + * @param firstGate First controlled gate, needs to be a Pauli gate. + * @param secondGate Second controlled gate, needs to be a Hadamard gate. + * @param rewriter Pattern rewriter for applying transformations + * @return success() if circuit was changed, failure() otherwise */ - static mlir::LogicalResult - swapGateWithHadamardUncontrolled(UnitaryOpInterface gate, - UnitaryOpInterface hadamardGate, - mlir::PatternRewriter& rewriter) { - const auto gateName = gate->getName().stripDialect().str(); - - if (gateName == "x" || gateName == "y" || gateName == "z") { - auto newHadamardGate = rewriter.replaceOpWithNewOp( - gate, gate.getOutputQubit(0).getType(), gate.getInputQubit(0)); - if (gateName == "x") { - auto newPauliGate = rewriter.replaceOpWithNewOp( - hadamardGate, hadamardGate.getOutputQubit(0).getType(), - hadamardGate.getInputQubit(0)); - rewriter.moveOpBefore(newHadamardGate, newPauliGate); - } else if (gateName == "z") { - auto newPauliGate = rewriter.replaceOpWithNewOp( - hadamardGate, hadamardGate.getOutputQubit(0).getType(), - hadamardGate.getInputQubit(0)); - rewriter.moveOpBefore(newHadamardGate, newPauliGate); - } else { - auto newPauliGate = rewriter.replaceOpWithNewOp( - hadamardGate, hadamardGate.getOutputQubit(0).getType(), - hadamardGate.getInputQubit(0)); - rewriter.moveOpBefore(newHadamardGate, newPauliGate); - } - return success(); + static LogicalResult handleTwoSucceedingControls(CtrlOp firstGate, + CtrlOp secondGate, + PatternRewriter& rewriter) { + if (firstGate.getNumTargets() != 1 || secondGate.getNumTargets() != 1 || + firstGate.getOutputTarget(0) != secondGate.getInputTarget(0) || + !areControlsControlledBySameQubits(firstGate, secondGate)) { + return failure(); } - return failure(); + return swapPauliWithHadamard(firstGate.getBodyUnitary(), + secondGate.getBodyUnitary(), rewriter); } /** - * @brief Lifts uncontrolled Hadamard gates in front of uncontrolled Pauli - * gates. + * @brief Lifts Hadamard gates in front of Pauli gates. * * @param op The operation to match (only Pauli gates trigger the rewrite) * @param rewriter Pattern rewriter for applying transformations @@ -244,7 +187,7 @@ struct LiftHadamardsAbovePauliGatesPattern final PatternRewriter& rewriter) const override { // op needs to be a Pauli gate std::string opName = op->getName().stripDialect().str(); - if (opName != "x" && opName != "y" && opName != "z") { + if (opName != "x" && opName != "y" && opName != "z" && opName != "ctrl") { return failure(); } @@ -256,6 +199,11 @@ struct LiftHadamardsAbovePauliGatesPattern final auto user = *users.begin(); auto userName = user->getName().stripDialect().str(); if (userName != "h") { + if (opName == "ctrl" && userName == "ctrl") { + return handleTwoSucceedingControls(mlir::dyn_cast(*op), + mlir::dyn_cast(user), + rewriter); + } return failure(); } @@ -267,7 +215,7 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } - return swapGateWithHadamardUncontrolled(op, hadamardGate, rewriter); + return swapPauliWithHadamard(op, hadamardGate, rewriter); } }; @@ -316,8 +264,7 @@ struct HadamardLifting final : impl::HadamardLiftingBase { // Define the set of patterns to use. RewritePatternSet patterns(ctx); - patterns.add( - patterns.getContext()); + patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); From e01a8b9f60c354f0ff87c5bc9324ba006d4917a4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 16:06:01 +0200 Subject: [PATCH 010/131] :construction: Added handling of controlled Pauli Z gates --- .../Optimization/HadamardLifting.cpp | 181 +++++++++++++++--- .../test_qco_hadamard_lifting.cpp | 24 +-- 2 files changed, 167 insertions(+), 38 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 6361608572..fb2d5a4cc5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -28,6 +28,25 @@ namespace mlir::qco { namespace { +/** + * @brief This method checks if two ranges contain of exactly the same + * elements. + * + * This method checks if two ranges contain of exactly the same elements. + * + * @param range1 The first range. + * @param range2 The second range. + */ +bool containRangesOfSameElements(const std::vector& range1, + const std::vector& range2) { + bool result = true; + result &= range1.size() == range2.size(); + for (auto element : range1) { + result &= std::find(range2.begin(), range2.end(), element) != range2.end(); + } + return result; +} + /** * @brief This pattern changes the target of a controlled Pauli Z gate if a * controlled hadamard gate is it successor. @@ -37,11 +56,90 @@ namespace { * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to * the Hadamard's. This is done in this pattern. */ -struct AdaptCtrldPauliZToLiftingPattern final - : mlir::OpInterfaceRewritePattern { +struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) - : OpInterfaceRewritePattern(context) {} + : OpRewritePattern(context) {} + + /** + * @brief This method checks if two gates are connected by the same qubits. + * + * This method checks if the output qubits of the first gate are exactly the + * input qubits of the second gate. There must be no qubit that is only used + * by one of the gates. The qubits may have different tasks (e.g. bein target + * in the first gate but ctrl in the second). + * + * @param firstGate The first unitary gate. + * @param secondGate The second unitary gate. + */ + static bool areGatesConnectedBySameQubits(UnitaryOpInterface firstGate, + UnitaryOpInterface secondGate) { + auto inQubits = secondGate.getInputQubits(); + auto outQubits = firstGate.getOutputQubits(); + + bool result = true; + result &= inQubits.size() == outQubits.size(); + for (auto element : inQubits) { + result &= std::find(outQubits.begin(), outQubits.end(), element) != + outQubits.end(); + } + return result; + } + + /** + * @brief Checks if the target qubit of gate 1 is part of the ctrl qubits of + * gate 2 and vice versa. + * + * This method checks if the output target qubit of gate 1 is used as control + * qubit of gate 2. Additionally, it checks if the input target of gate 2 is + * an output control of gate 2. Returns true if that is the case. + * Must only be used on gates that have a single target qubit. + * + * @param gate1 First gate, predecessor of gate2. + * @param gate2 Second gate, successor of gate1. + * @return True if target qubit of gate1 is ctrl in gate2 and vice versa. + * False otherwise. + */ + static bool areTargetsControlsAtTheOtherGates(CtrlOp gate1, CtrlOp gate2) { + Value targetQubitGate2 = gate2.getInputTarget(0); + Value targetQubitGate1 = gate1.getOutputTarget(0); + auto inCtrlGate2 = gate2.getControlsIn(); + auto outCtrlGate1 = gate1.getControlsOut(); + + return std::find(inCtrlGate2.begin(), inCtrlGate2.end(), + targetQubitGate1) != inCtrlGate2.end() && + std::find(outCtrlGate1.begin(), outCtrlGate1.end(), + targetQubitGate2) != outCtrlGate1.end(); + } + + /** + * @brief This method exchanges the position of two qubits acting on the same + * gate. + * + * This method exchanges two qubits acting on the same gate. E.g. if qubit 1 + * is a target qubit and qubit 2 a control qubit, that is exchanged. + * + * + * @param gate The gate both qubit1 and qubit2 belong to. + * @param qubit1 First qubit, exchanged with second. + * @param qubit2 Second qubit, exchanged with first. + * @param temporary Qubit that is not used on the respective gate. Used as + * temporary variable. + * @param rewriter The rewriter. + */ + static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, Value qubit1, + Value qubit2, Value temporary, + PatternRewriter& rewriter) { + rewriter.replaceUsesWithIf( + qubit1, temporary, + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + rewriter.replaceUsesWithIf(qubit2, qubit1, [&](mlir::OpOperand& operand) { + return operand.getOwner() == gate; + }); + rewriter.replaceUsesWithIf( + temporary, qubit2, + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + } /** * @brief Changes the target of a controlled Pauli Z gate if a @@ -51,10 +149,59 @@ struct AdaptCtrldPauliZToLiftingPattern final * @param rewriter Pattern rewriter for applying transformations * @return success() if circuit was changed, failure() otherwise */ - mlir::LogicalResult - matchAndRewrite(UnitaryOpInterface op, - mlir::PatternRewriter& rewriter) const override { - return failure(); + LogicalResult matchAndRewrite(CtrlOp op, + PatternRewriter& rewriter) const override { + // op needs to be a Pauli Z gate and controlled + std::string opName = op.getBodyUnitary()->getName().stripDialect().str(); + if (op.getNumTargets() != 1 || opName != "z") { + return failure(); + } + + // op needs to be in front of a controlled hadamard gate + const auto& users = op->getUsers(); + if (users.empty()) { + return failure(); + } + auto user = *users.begin(); + if (user->getName().stripDialect().str() != "ctrl") { + return failure(); + } + auto hadamardGate = mlir::dyn_cast(user); + if (hadamardGate.getNumTargets() != 1 || + hadamardGate.getBodyUnitary()->getName().stripDialect().str() != "h") { + return failure(); + } + + std::vector outputsOp(op.getOutputQubits().begin(), + op.getOutputQubits().end()); + std::vector inputsH(hadamardGate.getInputQubits().begin(), + hadamardGate.getInputQubits().end()); + if (!containRangesOfSameElements(outputsOp, inputsH)) { + return failure(); + } + + // If the target qubit of H is a ctrl in Z and vice versa, we can move Z's + // target to H's target + if (!areTargetsControlsAtTheOtherGates(op, hadamardGate)) { + return failure(); + } + + // Put the Z target to the same qubit as the hadamard target is + Value originalTargetQubitZ = op.getInputTarget(0); + Value targetQubitHadamard = hadamardGate.getInputTarget(0); + Value newTargetQubitZ = op.getInputForOutput(targetQubitHadamard); + Value temporary = hadamardGate.getOutputTarget(0); + + exchangeTwoQubitsAtGate(op, originalTargetQubitZ, newTargetQubitZ, + temporary, rewriter); + + Value newTargetQubitH = op.getOutputForInput(newTargetQubitZ); + temporary = op.getInputTarget(0); + + exchangeTwoQubitsAtGate(hadamardGate, targetQubitHadamard, newTargetQubitH, + temporary, rewriter); + + return success(); } }; @@ -75,26 +222,6 @@ struct LiftHadamardsAbovePauliGatesPattern final explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) : OpInterfaceRewritePattern(context) {} - /** - * @brief This method checks if two ranges contain of exactly the same - * elements. - * - * This method checks if two ranges contain of exactly the same elements. - * - * @param range1 The first range. - * @param range2 The second range. - */ - static bool containRangesOfSameElements(const std::vector& range1, - const std::vector& range2) { - bool result = true; - result &= range1.size() == range2.size(); - for (auto element : range1) { - result &= - std::find(range2.begin(), range2.end(), element) != range2.end(); - } - return result; - } - /** * This method checks whether the first and second controls are controlled by * the same qubits. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index aae85027ba..2d1e70c81a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -234,11 +234,6 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); - // auto qco = captureIR(module.get()); - // std::cout << qco; - // qco = captureIR(reference.get()); - // std::cout << qco; - EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } @@ -330,17 +325,18 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.s(q[0]); auto qubitPairRange = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.z(target[0])}; }); qubitPairRange = programBuilder.ctrl( - {qubitPairRange.first[1], qubitPairRange.second[0]}, + {qubitPairRange.second[0], qubitPairRange.first[1]}, {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { return llvm::SmallVector{programBuilder.h(target[0])}; }); - auto qubitPair = - programBuilder.cz(qubitPairRange.second[0], qubitPairRange.first[0]); + q[0] = programBuilder.s(qubitPairRange.first[0]); + auto qubitPair = programBuilder.cz(qubitPairRange.second[0], q[0]); qubitPairRange = programBuilder.ctrl( {qubitPairRange.first[1], qubitPair.second}, {qubitPair.first}, [&](mlir::ValueRange target) { @@ -355,8 +351,9 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.s(qRef[0]); auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[2], qRef[0]}, {qRef[1]}, [&](mlir::ValueRange target) { + {qRef[0], qRef[2]}, {qRef[1]}, [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.h(target[0])}; }); qubitPairRangeRef = referenceBuilder.ctrl( @@ -364,8 +361,8 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.x(target[0])}; }); - auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.second[0], - qubitPairRangeRef.first[0]); + qRef[0] = referenceBuilder.s(qubitPairRangeRef.first[0]); + auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.second[0], qRef[0]); qubitPairRangeRef = referenceBuilder.ctrl( {qubitPairRangeRef.first[1], qubitPairRef.second}, {qubitPairRef.first}, [&](mlir::ValueRange target) { @@ -383,6 +380,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); + // auto qco = captureIR(module.get()); + // std::cout << qco; + // qco = captureIR(reference.get()); + // std::cout << qco; + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } From 798c5146e31b2b14f3359de9b4c11844fa263cdc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 16:29:10 +0200 Subject: [PATCH 011/131] :recycle: Refactored handling of controlled Pauli Z gates --- .../Optimization/HadamardLifting.cpp | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index fb2d5a4cc5..17a6add2f6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -61,31 +61,6 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) : OpRewritePattern(context) {} - /** - * @brief This method checks if two gates are connected by the same qubits. - * - * This method checks if the output qubits of the first gate are exactly the - * input qubits of the second gate. There must be no qubit that is only used - * by one of the gates. The qubits may have different tasks (e.g. bein target - * in the first gate but ctrl in the second). - * - * @param firstGate The first unitary gate. - * @param secondGate The second unitary gate. - */ - static bool areGatesConnectedBySameQubits(UnitaryOpInterface firstGate, - UnitaryOpInterface secondGate) { - auto inQubits = secondGate.getInputQubits(); - auto outQubits = firstGate.getOutputQubits(); - - bool result = true; - result &= inQubits.size() == outQubits.size(); - for (auto element : inQubits) { - result &= std::find(outQubits.begin(), outQubits.end(), element) != - outQubits.end(); - } - return result; - } - /** * @brief Checks if the target qubit of gate 1 is part of the ctrl qubits of * gate 2 and vice versa. @@ -123,13 +98,13 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { * @param gate The gate both qubit1 and qubit2 belong to. * @param qubit1 First qubit, exchanged with second. * @param qubit2 Second qubit, exchanged with first. - * @param temporary Qubit that is not used on the respective gate. Used as - * temporary variable. * @param rewriter The rewriter. */ static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, Value qubit1, - Value qubit2, Value temporary, - PatternRewriter& rewriter) { + Value qubit2, PatternRewriter& rewriter) { + auto temporary = + rewriter.create(gate.getLoc(), gate.getInputTarget(0)) + .getResult(); rewriter.replaceUsesWithIf( qubit1, temporary, [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); @@ -187,19 +162,17 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { } // Put the Z target to the same qubit as the hadamard target is - Value originalTargetQubitZ = op.getInputTarget(0); - Value targetQubitHadamard = hadamardGate.getInputTarget(0); - Value newTargetQubitZ = op.getInputForOutput(targetQubitHadamard); - Value temporary = hadamardGate.getOutputTarget(0); + Value originalInputTargetQubitZ = op.getInputTarget(0); + Value targetInputQubitHadamard = hadamardGate.getInputTarget(0); + Value newTargetInputQubitZ = op.getInputForOutput(targetInputQubitHadamard); - exchangeTwoQubitsAtGate(op, originalTargetQubitZ, newTargetQubitZ, - temporary, rewriter); + exchangeTwoQubitsAtGate(op, originalInputTargetQubitZ, newTargetInputQubitZ, + rewriter); - Value newTargetQubitH = op.getOutputForInput(newTargetQubitZ); - temporary = op.getInputTarget(0); + Value newTargetInputQubitH = op.getOutputForInput(newTargetInputQubitZ); - exchangeTwoQubitsAtGate(hadamardGate, targetQubitHadamard, newTargetQubitH, - temporary, rewriter); + exchangeTwoQubitsAtGate(hadamardGate, targetInputQubitHadamard, + newTargetInputQubitH, rewriter); return success(); } From baf731c0aefae6952f477cc80360912eb4fdf4be Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 17:32:12 +0200 Subject: [PATCH 012/131] :construction: Added CNOT Hadamard handling --- .../Optimization/HadamardLifting.cpp | 158 +++++++++++++++++- .../test_qco_hadamard_lifting.cpp | 22 +-- 2 files changed, 168 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 17a6add2f6..ae652849e3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -335,6 +335,121 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { explicit LiftHadamardAboveCNOTPattern(mlir::MLIRContext* context) : OpRewritePattern(context) {} + /** + * @brief This method swaps two qubits on a gate. + * + * This method swaps two qubits on a gate. Input and output are exchanged. + * + * @param gate The gate that the qubits belong to. + * @param inputQubit1 The input qubit of the qubit to be exchanged with 2. + * @param inputQubit2 The input qubit of the qubit to be exchanged with 1. + * @param succeedingOp1 The operation succeeding gate on the corresponding + * output of inputQubit1. + * @param succeedingOp2 The operation succeeding gate on the corresponding + * output of inputQubit2. + * @param rewriter The used rewriter. + */ + static void swapQubits(UnitaryOpInterface gate, mlir::Value inputQubit1, + mlir::Value inputQubit2, + mlir::Operation* succeedingOp1, + mlir::Operation* succeedingOp2, + mlir::PatternRewriter& rewriter) { + mlir::Value outputQubit1 = gate.getOutputForInput(inputQubit1); + mlir::Value outputQubit2 = gate.getOutputForInput(inputQubit2); + auto temporary = + rewriter.create(gate.getLoc(), gate.getInputTarget(0)) + .getResult(); + + rewriter.replaceUsesWithIf(outputQubit1, temporary, + [&](mlir::OpOperand& operand) { + return operand.getOwner() == gate || + operand.getOwner() == succeedingOp1 || + operand.getOwner() == succeedingOp2; + }); + rewriter.replaceUsesWithIf(outputQubit2, outputQubit1, + [&](mlir::OpOperand& operand) { + return operand.getOwner() == gate || + operand.getOwner() == succeedingOp1 || + operand.getOwner() == succeedingOp2; + }); + rewriter.replaceUsesWithIf(temporary, outputQubit2, + [&](mlir::OpOperand& operand) { + return operand.getOwner() == gate || + operand.getOwner() == succeedingOp1 || + operand.getOwner() == succeedingOp2; + }); + + rewriter.replaceUsesWithIf( + inputQubit1, temporary, + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + rewriter.replaceUsesWithIf( + inputQubit2, inputQubit1, + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + rewriter.replaceUsesWithIf( + temporary, inputQubit2, + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + } + + /** + * @brief This method adds hadamrad gates before a given gate. + * + * @param gate The gate before which hadamard gates should be applied. + * @param inputQubits The input qubits of gate before which hadamard gates + * should be applied. + * @param rewriter The used rewriter. + * @returns One of the created hadamard gates. + */ + static HOp addHadamardGatesBeforeGate(UnitaryOpInterface gate, + std::vector inputQubits, + mlir::PatternRewriter& rewriter) { + HOp newHOP; + for (mlir::Value inputQubit : inputQubits) { + + std::vector inQubits{inputQubit}; + std::vector outQubits{inputQubit.getType()}; + + newHOP = rewriter.create(gate->getLoc(), inQubits); + + rewriter.moveOpBefore(newHOP, gate); + + rewriter.replaceUsesWithIf( + inputQubit, newHOP.getOutputTarget(0), + [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + } + return newHOP; + } + + /** + * @brief This method adds Hadamard gates after a given gate. + * + * @param gate The gate after which Hadamard gates should be applied. + * @param outputQubits The output qubits of gate after which Hadamard gates + * should be applied. + * @param rewriter The used rewriter. + * @returns One of the created hadamard gates. + */ + static HOp addHadamardGatesAfterGate(UnitaryOpInterface gate, + std::vector outputQubits, + mlir::PatternRewriter& rewriter) { + HOp newHOp; + for (mlir::Value outputQubit : outputQubits) { + + std::vector inQubit{outputQubit}; + std::vector outQubit{outputQubit.getType()}; + + newHOp = rewriter.create(gate->getLoc(), inQubit); + + rewriter.moveOpAfter(newHOp, gate); + + rewriter.replaceUsesWithIf( + newHOp.getInputTarget(0), newHOp.getOutputTarget(0), + [&](mlir::OpOperand& operand) { + return operand.getOwner() != gate && operand.getOwner() != newHOp; + }); + } + return newHOp; + } + /** * @brief This pattern remove an H gate between a CNOT and a measurement. * @@ -346,7 +461,48 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { mlir::LogicalResult matchAndRewrite(MeasureOp op, mlir::PatternRewriter& rewriter) const override { - return mlir::failure(); + // A Hadamard gate needs to be in front of the measurement + const auto qubitInMeasurement = op.getQubitIn(); + auto* predecessor = qubitInMeasurement.getDefiningOp(); + auto hadamardGate = mlir::dyn_cast(predecessor); + if (!hadamardGate || hadamardGate.getNumTargets() != 1 || + hadamardGate->getName().stripDialect().str() != "h") { + return failure(); + } + + // The Hadamard gate must be successor of the target of a CNOT + auto inQubitHadamard = hadamardGate.getInputQubit(0); + predecessor = inQubitHadamard.getDefiningOp(); + auto cnotGate = mlir::dyn_cast(predecessor); + if (!cnotGate || cnotGate.getNumTargets() != 1 || + cnotGate.getBodyUnitary()->getName().stripDialect().str() != "x" || + cnotGate.getOutputTarget(0) != inQubitHadamard) { + return failure(); + } + + // Remove the Hadamard gate + for (auto outQubit : hadamardGate.getOutputQubits()) { + rewriter.replaceAllUsesWith(outQubit, + hadamardGate.getInputForOutput(outQubit)); + } + rewriter.eraseOp(hadamardGate); + + // Add Hadamard gates to the other in and output gates of cnot + std::vector relevantInputQubitsForHadamard{ + cnotGate.getInputTarget(0), cnotGate.getInputControl(0)}; + HOp newHOPBefore = addHadamardGatesBeforeGate( + cnotGate, relevantInputQubitsForHadamard, rewriter); + + std::vector relevantOutputQubitsForHadamard{ + cnotGate.getOutputForInput(cnotGate.getInputControl(0))}; + HOp newHOPAfterCtrl = addHadamardGatesAfterGate( + cnotGate, relevantOutputQubitsForHadamard, rewriter); + + // Flip CNOT targets and ctrl + swapQubits(cnotGate, cnotGate.getInputControl(0), + cnotGate.getInputTarget(0), op, newHOPAfterCtrl, rewriter); + + return success(); } }; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 2d1e70c81a..47062bace8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -380,11 +380,6 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); - // auto qco = captureIR(module.get()); - // std::cout << qco; - // qco = captureIR(reference.get()); - // std::cout << qco; - EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } @@ -409,11 +404,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { auto qRef = referenceBuilder.allocQubitRegister(2); auto bRef = referenceBuilder.allocClassicalBitRegister(1); qRef[0] = referenceBuilder.s(qRef[0]); - qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); + qRef[0] = referenceBuilder.h(qRef[0]); auto qubitPairRef = referenceBuilder.cx(qRef[1], qRef[0]); - referenceBuilder.h(qubitPairRef.first); - referenceBuilder.measure(qubitPairRef.second, bRef[0]); + referenceBuilder.h(qubitPairRef.second); + referenceBuilder.measure(qubitPairRef.first, bRef[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -444,11 +439,11 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[1], qRef[2]}, {qRef[0]}, [&](mlir::ValueRange target) { + {qRef[0], qRef[2]}, {qRef[1]}, [&](mlir::ValueRange target) { return llvm::SmallVector{referenceBuilder.x(target[0])}; }); - referenceBuilder.h(qubitPairRangeRef.first[0]); - referenceBuilder.measure(qubitPairRangeRef.second[0], bRef[0]); + referenceBuilder.h(qubitPairRangeRef.second[0]); + referenceBuilder.measure(qubitPairRangeRef.first[0], bRef[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -494,6 +489,11 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); + // auto qco = captureIR(module.get()); + // std::cout << qco; + // qco = captureIR(reference.get()); + // std::cout << qco; + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } From a43b15890b0939df1ee2a94744e3931a7c89f9e0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 08:55:48 +0200 Subject: [PATCH 013/131] =?UTF-8?q?=E2=9C=85=20Corrected=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimization/test_qco_hadamard_lifting.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 47062bace8..4d2a87931c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -8,10 +8,8 @@ * Licensed under the MIT License */ -// #include "mlir/Compiler/CompilerPipeline.h" #include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCODialect.h" -#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Support/IRVerification.h" #include "mlir/Support/Passes.h" @@ -34,7 +32,7 @@ namespace { using namespace mlir; using namespace mlir::qco; -class QCOHadamardLiftingTest : public ::testing::Test { +class QCOHadamardLiftingTest : public testing::Test { protected: MLIRContext context; QCOProgramBuilder programBuilder; @@ -467,7 +465,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { auto qubitPairTwo = programBuilder.cx(q[5], q[4]); q[4] = programBuilder.h(qubitPairTwo.second); q[5] = programBuilder.h(qubitPairTwo.first); - q[5] = programBuilder.s(q[5]); + q[4] = programBuilder.s(q[4]); programBuilder.measure(q[4], b[1]); programBuilder.measure(q[5], b[2]); module = programBuilder.finalize(); @@ -480,7 +478,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { auto qubitPairTwoRef = referenceBuilder.cx(qRef[5], qRef[4]); qRef[4] = referenceBuilder.h(qubitPairTwoRef.second); qRef[5] = referenceBuilder.h(qubitPairTwoRef.first); - qRef[5] = referenceBuilder.s(qRef[5]); + qRef[4] = referenceBuilder.s(qRef[4]); referenceBuilder.measure(qRef[4], bRef[1]); referenceBuilder.measure(qRef[5], bRef[2]); reference = referenceBuilder.finalize(); @@ -489,11 +487,6 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { runCanonicalizationPasses(reference.get()); EXPECT_TRUE(verify(*reference).succeeded()); - // auto qco = captureIR(module.get()); - // std::cout << qco; - // qco = captureIR(reference.get()); - // std::cout << qco; - EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } From 7fd4f7569b941d99bcd80a709fcab698821b35da Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 09:40:37 +0200 Subject: [PATCH 014/131] :rotating_light: Fixed linter warnings --- .../Optimization/HadamardLifting.cpp | 119 +++++++++-------- .../test_qco_hadamard_lifting.cpp | 120 +++++++++--------- 2 files changed, 119 insertions(+), 120 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index ae652849e3..bb786abad5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -8,12 +8,9 @@ * Licensed under the MIT License */ -#include "../../../../../../include/mqt-core/ir/operations/OpType.hpp" #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" -#include -#include #include #include #include @@ -42,7 +39,7 @@ bool containRangesOfSameElements(const std::vector& range1, bool result = true; result &= range1.size() == range2.size(); for (auto element : range1) { - result &= std::find(range2.begin(), range2.end(), element) != range2.end(); + result &= std::ranges::find(range2, element) != range2.end(); } return result; } @@ -56,9 +53,9 @@ bool containRangesOfSameElements(const std::vector& range1, * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to * the Hadamard's. This is done in this pattern. */ -struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { +struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { - explicit AdaptCtrldPauliZToLiftingPattern(mlir::MLIRContext* context) + explicit AdaptCtrldPauliZToLiftingPattern(MLIRContext* context) : OpRewritePattern(context) {} /** @@ -76,10 +73,10 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { * False otherwise. */ static bool areTargetsControlsAtTheOtherGates(CtrlOp gate1, CtrlOp gate2) { - Value targetQubitGate2 = gate2.getInputTarget(0); - Value targetQubitGate1 = gate1.getOutputTarget(0); - auto inCtrlGate2 = gate2.getControlsIn(); - auto outCtrlGate1 = gate1.getControlsOut(); + const Value targetQubitGate2 = gate2.getInputTarget(0); + const Value targetQubitGate1 = gate1.getOutputTarget(0); + const auto inCtrlGate2 = gate2.getControlsIn(); + const auto outCtrlGate1 = gate1.getControlsOut(); return std::find(inCtrlGate2.begin(), inCtrlGate2.end(), targetQubitGate1) != inCtrlGate2.end() && @@ -100,20 +97,21 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { * @param qubit2 Second qubit, exchanged with first. * @param rewriter The rewriter. */ - static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, Value qubit1, - Value qubit2, PatternRewriter& rewriter) { + static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, + const Value qubit1, const Value qubit2, + PatternRewriter& rewriter) { auto temporary = rewriter.create(gate.getLoc(), gate.getInputTarget(0)) .getResult(); rewriter.replaceUsesWithIf( qubit1, temporary, - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); - rewriter.replaceUsesWithIf(qubit2, qubit1, [&](mlir::OpOperand& operand) { + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); + rewriter.replaceUsesWithIf(qubit2, qubit1, [&](const OpOperand& operand) { return operand.getOwner() == gate; }); rewriter.replaceUsesWithIf( temporary, qubit2, - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); } /** @@ -147,10 +145,10 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { return failure(); } - std::vector outputsOp(op.getOutputQubits().begin(), - op.getOutputQubits().end()); - std::vector inputsH(hadamardGate.getInputQubits().begin(), - hadamardGate.getInputQubits().end()); + const std::vector outputsOp(op.getOutputQubits().begin(), + op.getOutputQubits().end()); + const std::vector inputsH(hadamardGate.getInputQubits().begin(), + hadamardGate.getInputQubits().end()); if (!containRangesOfSameElements(outputsOp, inputsH)) { return failure(); } @@ -162,14 +160,16 @@ struct AdaptCtrldPauliZToLiftingPattern final : mlir::OpRewritePattern { } // Put the Z target to the same qubit as the hadamard target is - Value originalInputTargetQubitZ = op.getInputTarget(0); - Value targetInputQubitHadamard = hadamardGate.getInputTarget(0); - Value newTargetInputQubitZ = op.getInputForOutput(targetInputQubitHadamard); + const Value originalInputTargetQubitZ = op.getInputTarget(0); + const Value targetInputQubitHadamard = hadamardGate.getInputTarget(0); + const Value newTargetInputQubitZ = + op.getInputForOutput(targetInputQubitHadamard); exchangeTwoQubitsAtGate(op, originalInputTargetQubitZ, newTargetInputQubitZ, rewriter); - Value newTargetInputQubitH = op.getOutputForInput(newTargetInputQubitZ); + const Value newTargetInputQubitH = + op.getOutputForInput(newTargetInputQubitZ); exchangeTwoQubitsAtGate(hadamardGate, targetInputQubitHadamard, newTargetInputQubitH, rewriter); @@ -205,9 +205,9 @@ struct LiftHadamardsAbovePauliGatesPattern final */ static bool areControlsControlledBySameQubits(CtrlOp firstCtrl, CtrlOp secondCtrl) { - std::vector controlOutputsFirstGate( + const std::vector controlOutputsFirstGate( firstCtrl.getControlsOut().begin(), firstCtrl.getControlsOut().end()); - std::vector controlInputsSecondGate( + const std::vector controlInputsSecondGate( secondCtrl.getControlsIn().begin(), secondCtrl.getControlsIn().end()); return containRangesOfSameElements(controlOutputsFirstGate, controlInputsSecondGate); @@ -330,9 +330,9 @@ struct LiftHadamardsAbovePauliGatesPattern final * and ctrl involved in the transformation get hadamard gates assigned. * For now, the involved ctrl to be flipped with the target is chosen randomly. */ -struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { +struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { - explicit LiftHadamardAboveCNOTPattern(mlir::MLIRContext* context) + explicit LiftHadamardAboveCNOTPattern(MLIRContext* context) : OpRewritePattern(context) {} /** @@ -349,31 +349,31 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { * output of inputQubit2. * @param rewriter The used rewriter. */ - static void swapQubits(UnitaryOpInterface gate, mlir::Value inputQubit1, - mlir::Value inputQubit2, - mlir::Operation* succeedingOp1, - mlir::Operation* succeedingOp2, - mlir::PatternRewriter& rewriter) { - mlir::Value outputQubit1 = gate.getOutputForInput(inputQubit1); - mlir::Value outputQubit2 = gate.getOutputForInput(inputQubit2); + static void swapQubits(UnitaryOpInterface gate, const Value inputQubit1, + const Value inputQubit2, + const Operation* succeedingOp1, + const Operation* succeedingOp2, + PatternRewriter& rewriter) { + const Value outputQubit1 = gate.getOutputForInput(inputQubit1); + const Value outputQubit2 = gate.getOutputForInput(inputQubit2); auto temporary = rewriter.create(gate.getLoc(), gate.getInputTarget(0)) .getResult(); rewriter.replaceUsesWithIf(outputQubit1, temporary, - [&](mlir::OpOperand& operand) { + [&](const OpOperand& operand) { return operand.getOwner() == gate || operand.getOwner() == succeedingOp1 || operand.getOwner() == succeedingOp2; }); rewriter.replaceUsesWithIf(outputQubit2, outputQubit1, - [&](mlir::OpOperand& operand) { + [&](const OpOperand& operand) { return operand.getOwner() == gate || operand.getOwner() == succeedingOp1 || operand.getOwner() == succeedingOp2; }); rewriter.replaceUsesWithIf(temporary, outputQubit2, - [&](mlir::OpOperand& operand) { + [&](const OpOperand& operand) { return operand.getOwner() == gate || operand.getOwner() == succeedingOp1 || operand.getOwner() == succeedingOp2; @@ -381,13 +381,13 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { rewriter.replaceUsesWithIf( inputQubit1, temporary, - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); rewriter.replaceUsesWithIf( inputQubit2, inputQubit1, - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); rewriter.replaceUsesWithIf( temporary, inputQubit2, - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); } /** @@ -400,13 +400,13 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { * @returns One of the created hadamard gates. */ static HOp addHadamardGatesBeforeGate(UnitaryOpInterface gate, - std::vector inputQubits, - mlir::PatternRewriter& rewriter) { + std::vector inputQubits, + PatternRewriter& rewriter) { HOp newHOP; - for (mlir::Value inputQubit : inputQubits) { + for (Value inputQubit : inputQubits) { - std::vector inQubits{inputQubit}; - std::vector outQubits{inputQubit.getType()}; + std::vector inQubits{inputQubit}; + std::vector outQubits{inputQubit.getType()}; newHOP = rewriter.create(gate->getLoc(), inQubits); @@ -414,7 +414,7 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { rewriter.replaceUsesWithIf( inputQubit, newHOP.getOutputTarget(0), - [&](mlir::OpOperand& operand) { return operand.getOwner() == gate; }); + [&](const OpOperand& operand) { return operand.getOwner() == gate; }); } return newHOP; } @@ -429,13 +429,13 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { * @returns One of the created hadamard gates. */ static HOp addHadamardGatesAfterGate(UnitaryOpInterface gate, - std::vector outputQubits, - mlir::PatternRewriter& rewriter) { + const std::vector& outputQubits, + PatternRewriter& rewriter) { HOp newHOp; - for (mlir::Value outputQubit : outputQubits) { + for (Value outputQubit : outputQubits) { - std::vector inQubit{outputQubit}; - std::vector outQubit{outputQubit.getType()}; + std::vector inQubit{outputQubit}; + std::vector outQubit{outputQubit.getType()}; newHOp = rewriter.create(gate->getLoc(), inQubit); @@ -443,7 +443,7 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { rewriter.replaceUsesWithIf( newHOp.getInputTarget(0), newHOp.getOutputTarget(0), - [&](mlir::OpOperand& operand) { + [&](const OpOperand& operand) { return operand.getOwner() != gate && operand.getOwner() != newHOp; }); } @@ -458,9 +458,8 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { * @param rewriter Pattern rewriter for applying transformations * @return success() if circuit was changed, failure() otherwise */ - mlir::LogicalResult - matchAndRewrite(MeasureOp op, - mlir::PatternRewriter& rewriter) const override { + LogicalResult matchAndRewrite(MeasureOp op, + PatternRewriter& rewriter) const override { // A Hadamard gate needs to be in front of the measurement const auto qubitInMeasurement = op.getQubitIn(); auto* predecessor = qubitInMeasurement.getDefiningOp(); @@ -488,12 +487,12 @@ struct LiftHadamardAboveCNOTPattern final : mlir::OpRewritePattern { rewriter.eraseOp(hadamardGate); // Add Hadamard gates to the other in and output gates of cnot - std::vector relevantInputQubitsForHadamard{ + const std::vector relevantInputQubitsForHadamard{ cnotGate.getInputTarget(0), cnotGate.getInputControl(0)}; - HOp newHOPBefore = addHadamardGatesBeforeGate( - cnotGate, relevantInputQubitsForHadamard, rewriter); + addHadamardGatesBeforeGate(cnotGate, relevantInputQubitsForHadamard, + rewriter); - std::vector relevantOutputQubitsForHadamard{ + const std::vector relevantOutputQubitsForHadamard{ cnotGate.getOutputForInput(cnotGate.getInputControl(0))}; HOp newHOPAfterCtrl = addHadamardGatesAfterGate( cnotGate, relevantOutputQubitsForHadamard, rewriter); @@ -515,7 +514,7 @@ struct HadamardLifting final : impl::HadamardLiftingBase { protected: void runOnOperation() override { - auto op = getOperation(); + const auto op = getOperation(); auto* ctx = &getContext(); // Define the set of patterns to use. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 4d2a87931c..133ec0e4f4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -269,15 +269,15 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { auto q = programBuilder.allocQubitRegister(2); - auto qubitPair = programBuilder.cz(q[1], q[0]); - q[1] = programBuilder.s(qubitPair.first); - programBuilder.ch(q[1], qubitPair.second); + auto [q1, q2] = programBuilder.cz(q[1], q[0]); + q[1] = programBuilder.s(q1); + programBuilder.ch(q[1], q2); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); - auto qubitPairRef = referenceBuilder.cz(qRef[1], qRef[0]); - qRef[1] = referenceBuilder.s(qubitPairRef.first); - referenceBuilder.ch(qRef[1], qubitPairRef.second); + auto [q1Ref, q2Ref] = referenceBuilder.cz(qRef[1], qRef[0]); + qRef[1] = referenceBuilder.s(q1Ref); + referenceBuilder.ch(qRef[1], q2Ref); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -294,19 +294,19 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { auto q = programBuilder.allocQubitRegister(3); - auto qubitPairRange = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; + auto [q12, q0] = + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { + return SmallVector{programBuilder.z(target[0])}; }); - programBuilder.ch(qubitPairRange.first[0], qubitPairRange.second[0]); + programBuilder.ch(q12[0], q0[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); - auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[1], qRef[2]}, {qRef[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.z(target[0])}; + auto [q12Ref, q0Ref] = referenceBuilder.ctrl( + {qRef[1], qRef[2]}, {qRef[0]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.z(target[0])}; }); - referenceBuilder.ch(qubitPairRangeRef.first[0], qubitPairRangeRef.second[0]); + referenceBuilder.ch(q12Ref[0], q0Ref[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -325,25 +325,25 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { auto q = programBuilder.allocQubitRegister(3); q[0] = programBuilder.s(q[0]); auto qubitPairRange = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { + return SmallVector{programBuilder.z(target[0])}; }); qubitPairRange = programBuilder.ctrl( {qubitPairRange.second[0], qubitPairRange.first[1]}, - {qubitPairRange.first[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; + {qubitPairRange.first[0]}, [&](const ValueRange target) { + return SmallVector{programBuilder.h(target[0])}; }); q[0] = programBuilder.s(qubitPairRange.first[0]); auto qubitPair = programBuilder.cz(qubitPairRange.second[0], q[0]); qubitPairRange = programBuilder.ctrl( {qubitPairRange.first[1], qubitPair.second}, {qubitPair.first}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.h(target[0])}; + [&](const ValueRange target) { + return SmallVector{programBuilder.h(target[0])}; }); qubitPairRange = programBuilder.ctrl( qubitPairRange.first, qubitPairRange.second, - [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; + [&](const ValueRange target) { + return SmallVector{programBuilder.z(target[0])}; }); programBuilder.cz(qubitPairRange.second[0], qubitPairRange.first[0]); module = programBuilder.finalize(); @@ -351,25 +351,25 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { auto qRef = referenceBuilder.allocQubitRegister(3); qRef[0] = referenceBuilder.s(qRef[0]); auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[0], qRef[2]}, {qRef[1]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; + {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.h(target[0])}; }); qubitPairRangeRef = referenceBuilder.ctrl( qubitPairRangeRef.first, qubitPairRangeRef.second, - [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.x(target[0])}; + [&](const ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; }); qRef[0] = referenceBuilder.s(qubitPairRangeRef.first[0]); auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.second[0], qRef[0]); qubitPairRangeRef = referenceBuilder.ctrl( {qubitPairRangeRef.first[1], qubitPairRef.second}, {qubitPairRef.first}, - [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.h(target[0])}; + [&](const ValueRange target) { + return SmallVector{referenceBuilder.h(target[0])}; }); qubitPairRangeRef = referenceBuilder.ctrl( qubitPairRangeRef.first, qubitPairRangeRef.second, - [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.z(target[0])}; + [&](const ValueRange target) { + return SmallVector{referenceBuilder.z(target[0])}; }); referenceBuilder.cz(qubitPairRangeRef.second[0], qubitPairRangeRef.first[0]); reference = referenceBuilder.finalize(); @@ -392,21 +392,21 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { auto q = programBuilder.allocQubitRegister(2); - auto b = programBuilder.allocClassicalBitRegister(1); + const auto b = programBuilder.allocClassicalBitRegister(1); q[0] = programBuilder.s(q[0]); - auto qubitPair = programBuilder.cx(q[0], q[1]); - q[1] = programBuilder.h(qubitPair.second); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + q[1] = programBuilder.h(q1); programBuilder.measure(q[1], b[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); - auto bRef = referenceBuilder.allocClassicalBitRegister(1); + const auto bRef = referenceBuilder.allocClassicalBitRegister(1); qRef[0] = referenceBuilder.s(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); qRef[0] = referenceBuilder.h(qRef[0]); - auto qubitPairRef = referenceBuilder.cx(qRef[1], qRef[0]); - referenceBuilder.h(qubitPairRef.second); - referenceBuilder.measure(qubitPairRef.first, bRef[0]); + auto [q1Ref, q0Ref] = referenceBuilder.cx(qRef[1], qRef[0]); + referenceBuilder.h(q0Ref); + referenceBuilder.measure(q1Ref, bRef[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -423,25 +423,25 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { auto q = programBuilder.allocQubitRegister(3); - auto b = programBuilder.allocClassicalBitRegister(1); - auto qubitPairRange = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{programBuilder.x(target[0])}; + const auto b = programBuilder.allocClassicalBitRegister(1); + auto [q12, q0] = + programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; }); - q[1] = programBuilder.h(qubitPairRange.second[0]); + q[1] = programBuilder.h(q0[0]); programBuilder.measure(q[1], b[0]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); - auto bRef = referenceBuilder.allocClassicalBitRegister(1); + const auto bRef = referenceBuilder.allocClassicalBitRegister(1); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); - auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[0], qRef[2]}, {qRef[1]}, [&](mlir::ValueRange target) { - return llvm::SmallVector{referenceBuilder.x(target[0])}; + auto [q02Ref, q1Ref] = referenceBuilder.ctrl( + {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; }); - referenceBuilder.h(qubitPairRangeRef.second[0]); - referenceBuilder.measure(qubitPairRangeRef.first[0], bRef[0]); + referenceBuilder.h(q1Ref[0]); + referenceBuilder.measure(q02Ref[0], bRef[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -458,26 +458,26 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { auto q = programBuilder.allocQubitRegister(6); - auto b = programBuilder.allocClassicalBitRegister(3); + const auto b = programBuilder.allocClassicalBitRegister(3); programBuilder.cx(q[1], q[0]); - auto qubitPairOne = programBuilder.cx(q[3], q[2]); - programBuilder.measure(qubitPairOne.first, b[0]); - auto qubitPairTwo = programBuilder.cx(q[5], q[4]); - q[4] = programBuilder.h(qubitPairTwo.second); - q[5] = programBuilder.h(qubitPairTwo.first); + auto [q3, q2] = programBuilder.cx(q[3], q[2]); + programBuilder.measure(q3, b[0]); + auto [q5, q4] = programBuilder.cx(q[5], q[4]); + q[4] = programBuilder.h(q4); + q[5] = programBuilder.h(q5); q[4] = programBuilder.s(q[4]); programBuilder.measure(q[4], b[1]); programBuilder.measure(q[5], b[2]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(6); - auto bRef = referenceBuilder.allocClassicalBitRegister(3); + const auto bRef = referenceBuilder.allocClassicalBitRegister(3); referenceBuilder.cx(qRef[1], qRef[0]); - auto qubitPairOneRef = referenceBuilder.cx(qRef[3], qRef[2]); - referenceBuilder.measure(qubitPairOneRef.first, bRef[0]); - auto qubitPairTwoRef = referenceBuilder.cx(qRef[5], qRef[4]); - qRef[4] = referenceBuilder.h(qubitPairTwoRef.second); - qRef[5] = referenceBuilder.h(qubitPairTwoRef.first); + auto [q3Ref, q2Ref] = referenceBuilder.cx(qRef[3], qRef[2]); + referenceBuilder.measure(q3Ref, bRef[0]); + auto [q5Ref, q4Ref] = referenceBuilder.cx(qRef[5], qRef[4]); + qRef[4] = referenceBuilder.h(q4Ref); + qRef[5] = referenceBuilder.h(q5Ref); qRef[4] = referenceBuilder.s(qRef[4]); referenceBuilder.measure(qRef[4], bRef[1]); referenceBuilder.measure(qRef[5], bRef[2]); From 18382b33488b07f039205ca053674ff6c105b946 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 11:53:43 +0200 Subject: [PATCH 015/131] :rotating_light: Fixed linter warnings --- .../Optimization/HadamardLifting.cpp | 66 +++++++++---------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index bb786abad5..80a297740a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -100,8 +100,8 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, const Value qubit1, const Value qubit2, PatternRewriter& rewriter) { - auto temporary = - rewriter.create(gate.getLoc(), gate.getInputTarget(0)) + const auto temporary = + IdOp::create(rewriter, gate.getLoc(), gate.getInputTarget(0)) .getResult(); rewriter.replaceUsesWithIf( qubit1, temporary, @@ -135,7 +135,7 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { if (users.empty()) { return failure(); } - auto user = *users.begin(); + const auto user = *users.begin(); if (user->getName().stripDialect().str() != "ctrl") { return failure(); } @@ -147,8 +147,8 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { const std::vector outputsOp(op.getOutputQubits().begin(), op.getOutputQubits().end()); - const std::vector inputsH(hadamardGate.getInputQubits().begin(), - hadamardGate.getInputQubits().end()); + const std::vector inputsH(hadamardGate.getInputQubits().begin(), + hadamardGate.getInputQubits().end()); if (!containRangesOfSameElements(outputsOp, inputsH)) { return failure(); } @@ -207,7 +207,7 @@ struct LiftHadamardsAbovePauliGatesPattern final CtrlOp secondCtrl) { const std::vector controlOutputsFirstGate( firstCtrl.getControlsOut().begin(), firstCtrl.getControlsOut().end()); - const std::vector controlInputsSecondGate( + const std::vector controlInputsSecondGate( secondCtrl.getControlsIn().begin(), secondCtrl.getControlsIn().end()); return containRangesOfSameElements(controlOutputsFirstGate, controlInputsSecondGate); @@ -227,10 +227,9 @@ struct LiftHadamardsAbovePauliGatesPattern final * @param rewriter The used rewriter. * @return success() if circuit was changed, failure() otherwise */ - static mlir::LogicalResult - swapPauliWithHadamard(UnitaryOpInterface gate, - UnitaryOpInterface hadamardGate, - mlir::PatternRewriter& rewriter) { + static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, + UnitaryOpInterface hadamardGate, + PatternRewriter& rewriter) { const auto gateName = gate->getName().stripDialect().str(); const auto hadamardName = hadamardGate->getName().stripDialect().str(); if (hadamardName != "h" || @@ -286,7 +285,7 @@ struct LiftHadamardsAbovePauliGatesPattern final LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { // op needs to be a Pauli gate - std::string opName = op->getName().stripDialect().str(); + const std::string opName = op->getName().stripDialect().str(); if (opName != "x" && opName != "y" && opName != "z" && opName != "ctrl") { return failure(); } @@ -296,8 +295,8 @@ struct LiftHadamardsAbovePauliGatesPattern final if (users.empty()) { return failure(); } - auto user = *users.begin(); - auto userName = user->getName().stripDialect().str(); + const auto user = *users.begin(); + const auto userName = user->getName().stripDialect().str(); if (userName != "h") { if (opName == "ctrl" && userName == "ctrl") { return handleTwoSucceedingControls(mlir::dyn_cast(*op), @@ -356,8 +355,8 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { PatternRewriter& rewriter) { const Value outputQubit1 = gate.getOutputForInput(inputQubit1); const Value outputQubit2 = gate.getOutputForInput(inputQubit2); - auto temporary = - rewriter.create(gate.getLoc(), gate.getInputTarget(0)) + const auto temporary = + IdOp::create(rewriter, gate.getLoc(), gate.getInputTarget(0)) .getResult(); rewriter.replaceUsesWithIf(outputQubit1, temporary, @@ -399,24 +398,23 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param rewriter The used rewriter. * @returns One of the created hadamard gates. */ - static HOp addHadamardGatesBeforeGate(UnitaryOpInterface gate, - std::vector inputQubits, + static HOp addHadamardGatesBeforeGate(const UnitaryOpInterface gate, + const std::vector& inputQubits, PatternRewriter& rewriter) { - HOp newHOP; - for (Value inputQubit : inputQubits) { + HOp newHOp; + for (const Value inputQubit : inputQubits) { - std::vector inQubits{inputQubit}; - std::vector outQubits{inputQubit.getType()}; + std::vector inQubits{inputQubit}; - newHOP = rewriter.create(gate->getLoc(), inQubits); + newHOp = HOp::create(rewriter, gate->getLoc(), inQubits); - rewriter.moveOpBefore(newHOP, gate); + rewriter.moveOpBefore(newHOp, gate); rewriter.replaceUsesWithIf( - inputQubit, newHOP.getOutputTarget(0), + inputQubit, newHOp.getOutputTarget(0), [&](const OpOperand& operand) { return operand.getOwner() == gate; }); } - return newHOP; + return newHOp; } /** @@ -428,16 +426,16 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param rewriter The used rewriter. * @returns One of the created hadamard gates. */ - static HOp addHadamardGatesAfterGate(UnitaryOpInterface gate, + static HOp addHadamardGatesAfterGate(const UnitaryOpInterface gate, const std::vector& outputQubits, PatternRewriter& rewriter) { HOp newHOp; for (Value outputQubit : outputQubits) { - std::vector inQubit{outputQubit}; - std::vector outQubit{outputQubit.getType()}; + std::vector inQubit{outputQubit}; + std::vector outQubit{outputQubit.getType()}; - newHOp = rewriter.create(gate->getLoc(), inQubit); + newHOp = HOp::create(rewriter, gate->getLoc(), inQubit); rewriter.moveOpAfter(newHOp, gate); @@ -470,7 +468,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } // The Hadamard gate must be successor of the target of a CNOT - auto inQubitHadamard = hadamardGate.getInputQubit(0); + const auto inQubitHadamard = hadamardGate.getInputQubit(0); predecessor = inQubitHadamard.getDefiningOp(); auto cnotGate = mlir::dyn_cast(predecessor); if (!cnotGate || cnotGate.getNumTargets() != 1 || @@ -480,21 +478,21 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } // Remove the Hadamard gate - for (auto outQubit : hadamardGate.getOutputQubits()) { + for (const auto outQubit : hadamardGate.getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, hadamardGate.getInputForOutput(outQubit)); } rewriter.eraseOp(hadamardGate); // Add Hadamard gates to the other in and output gates of cnot - const std::vector relevantInputQubitsForHadamard{ + const std::vector relevantInputQubitsForHadamard{ cnotGate.getInputTarget(0), cnotGate.getInputControl(0)}; addHadamardGatesBeforeGate(cnotGate, relevantInputQubitsForHadamard, rewriter); - const std::vector relevantOutputQubitsForHadamard{ + const std::vector relevantOutputQubitsForHadamard{ cnotGate.getOutputForInput(cnotGate.getInputControl(0))}; - HOp newHOPAfterCtrl = addHadamardGatesAfterGate( + const HOp newHOPAfterCtrl = addHadamardGatesAfterGate( cnotGate, relevantOutputQubitsForHadamard, rewriter); // Flip CNOT targets and ctrl From 7a8981126793c78f43f1df972e5db729e4021d0c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:00:22 +0200 Subject: [PATCH 016/131] :fire: Removed statistics --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index d1949b3a59..a4afc64af3 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -122,12 +122,6 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { Afterwards, the measurement lifting routine could transform the CNOT into a classically controlled Pauli X. }]; - let statistics = [Statistic<"numPauliZTargetChange", "num-pauli-z-target-change", - "The number of times the target qubit of controlled Pauli Z gates was changed.">, - Statistic<"numHadamardLiftings", "num-hadamard-liftings", - "The number of times Hadamard gates were lifted in front of Pauli gates.">, - Statistic<"numMeasurementHadamardCNOTLiftings", "num-measurement-hadamard-cnot-liftings", - "The number of times the target and controls of a CNOT were lifted to put a measurement directly after a control qubit.">]; } #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD From cc5f15cca01a16bfee460e920537b72a7e452428 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:28:56 +0200 Subject: [PATCH 017/131] :construction: Added infrastructure to use hadamard lifting --- CMakeLists.txt | 2 -- mlir/include/mlir/Compiler/CompilerPipeline.h | 3 +++ mlir/lib/Compiler/CMakeLists.txt | 1 + mlir/lib/Compiler/CompilerPipeline.cpp | 8 ++++-- mlir/tools/mqt-cc/mqt-cc.cpp | 6 +++++ .../Compiler/test_compiler_pipeline.cpp | 27 ++++++++++++++++++- 6 files changed, 42 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bec69ba0fe..4c1a0eee53 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,6 @@ project( LANGUAGES C CXX DESCRIPTION "MQT Core - The Backbone of the Munich Quantum Toolkit") -list(APPEND CMAKE_PREFIX_PATH "/lib/llvm-22/lib/cmake/mlir" "/lib/llvm-22/lib/cmake/llvm") - if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 20) set(CMAKE_CXX_STANDARD 20 diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index ec7473a8cf..bbbd991423 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -40,6 +40,9 @@ struct QuantumCompilerConfig { /// Print IR after each stage bool printIRAfterAllStages = false; + + /// Enable Hadamard lifting + bool hadamardLifting = false; }; /** diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index 66afb3515a..2aeeae1576 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -20,6 +20,7 @@ add_mlir_library( MLIRQCToQCO MLIRQCOToQC MLIRQCToQIR + MLIRQCOTransforms MQT::MLIRSupport) mqt_mlir_target_use_project_options(MQTCompilerPipeline) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 119e343755..c2880649e0 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -13,6 +13,7 @@ #include "mlir/Conversion/QCOToQC/QCOToQC.h" #include "mlir/Conversion/QCToQCO/QCToQCO.h" #include "mlir/Conversion/QCToQIR/QCToQIR.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Support/PrettyPrinting.h" #include @@ -142,8 +143,11 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } } // Stage 5: Optimization passes - // TODO: Add optimization passes - if (failed(runStage([&](PassManager& pm) { addCleanupPasses(pm); }))) { + if (failed(runStage([&](PassManager& pm) { + if (config_.hadamardLifting) { + pm.addPass(mlir::qco::createHadamardLifting()); + } + }))) { return failure(); } if (record != nullptr && config_.recordIntermediates) { diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 76848b6594..f7eb25796d 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -71,6 +71,11 @@ static cl::opt cl::desc("Print IR after each compiler stage"), cl::init(false)); +static cl::opt + hadamardLifting("hadamard-lifting", + cl::desc("Apply Hadamard lifting during optimization"), + cl::init(false)); + /** * @brief Load and parse a .qasm file */ @@ -165,6 +170,7 @@ int main(int argc, char** argv) { config.enableTiming = enableTiming; config.enableStatistics = enableStatistics; config.printIRAfterAllStages = printIRAfterAllStages; + config.hadamardLifting = hadamardLifting; // Run the compilation pipeline CompilationRecord record; diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 184bb7d677..c5588e7055 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -114,9 +114,11 @@ class CompilerPipelineTest } static void runPipeline(const mlir::ModuleOp module, const bool convertToQIR, + const bool hadamardLifting, mlir::CompilationRecord& record) { mlir::QuantumCompilerConfig config; config.convertToQIR = convertToQIR; + config.hadamardLifting = hadamardLifting; config.recordIntermediates = true; config.printIRAfterAllStages = true; @@ -160,7 +162,7 @@ TEST_P(CompilerPipelineTest, EndToEndPipeline) { EXPECT_TRUE(mlir::verify(*module).succeeded()); mlir::CompilationRecord record; - runPipeline(module.get(), testCase.convertToQIR, record); + runPipeline(module.get(), testCase.convertToQIR, false, record); ASSERT_TRUE(testCase.qcReferenceBuilder); auto qcReference = buildQCReference(testCase.qcReferenceBuilder); @@ -186,6 +188,29 @@ TEST_P(CompilerPipelineTest, EndToEndPipeline) { } } +/** + * @brief Test: Hadamard lifting pass is invoked during the optimization stage + * + * We run the pipeline with enabled Hadamard lifting and check whether the + * outputs differ, i.e. that the pipeline ran and changed the IR. + * Correctness of the pass is tested in a dedicated test. + */ +TEST_F(CompilerPipelineTest, HadamardLiftingPass) { + auto module = mlir::qc::QCProgramBuilder::build( + context.get(), [&](mlir::qc::QCProgramBuilder& b) { + auto q = b.allocQubit(); + b.x(q); + b.h(q); + }); + ASSERT_TRUE(module); + + mlir::CompilationRecord record; + runPipeline(module.get(), false, true, record); + + // The outputs must differ, proving the pass ran and transformed the IR + EXPECT_NE(record.afterQCOCanon, record.afterOptimization); +} + INSTANTIATE_TEST_SUITE_P( QuantumComputationPipelineProgramsTest, CompilerPipelineTest, testing::Values( From a682420b881e21066390c5c15bddf5fa7e64783e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:29:46 +0200 Subject: [PATCH 018/131] :construction: Added infrastructure to use hadamard lifting --- mlir/tools/mqt-cc/mqt-cc.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index f7eb25796d..9da4d442c4 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -73,8 +73,8 @@ static cl::opt static cl::opt hadamardLifting("hadamard-lifting", - cl::desc("Apply Hadamard lifting during optimization"), - cl::init(false)); + cl::desc("Apply Hadamard lifting during optimization"), + cl::init(false)); /** * @brief Load and parse a .qasm file From 2c298bf55f076aaacf2a9339eac75fe372080146 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:47:41 +0000 Subject: [PATCH 019/131] =?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/Dialect/QCO/Transforms/Passes.td | 5 ++-- mlir/lib/Compiler/CMakeLists.txt | 2 +- .../Optimization/HadamardLifting.cpp | 2 +- .../Transforms/Optimization/CMakeLists.txt | 28 +++++++++---------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index a4afc64af3..b17f7ec266 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,7 +78,8 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to lift Hadamards as much as possible by flipping them with Pauli gates."; + let summary = "This pass attempts to lift Hadamards as much as possible by " + "flipping them with Pauli gates."; let description = [{ This pass lifts hadamards away from the measurements in order to apply lift-measurements more effectively. It uses the following commutation rules: @@ -95,7 +96,7 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { If the Hadamard and Pauli gates are controlled, they are only lifted if both gates are controlled by exactly the same qubits. One exception to this are the controlled Pauli Z gates. For these gates, controls and targets are - interchangable. Therefore, tranformation routines as follows are possible and applied: + interchangeable. Therefore, transformation routines as follows are possible and applied: ┌───┐ ┤ Z ├──■── ──■────■── ──■────■── └─┬─┘┌─┴─┐ => ┌─┴─┐┌─┴─┐ => ┌─┴─┐┌─┴─┐ diff --git a/mlir/lib/Compiler/CMakeLists.txt b/mlir/lib/Compiler/CMakeLists.txt index 2aeeae1576..1dc45532fa 100644 --- a/mlir/lib/Compiler/CMakeLists.txt +++ b/mlir/lib/Compiler/CMakeLists.txt @@ -20,7 +20,7 @@ add_mlir_library( MLIRQCToQCO MLIRQCOToQC MLIRQCToQIR - MLIRQCOTransforms + MLIRQCOTransforms MQT::MLIRSupport) mqt_mlir_target_use_project_options(MQTCompilerPipeline) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 80a297740a..30069ef001 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -530,4 +530,4 @@ struct HadamardLifting final : impl::HadamardLiftingBase { } // namespace -} // namespace mlir::qco \ No newline at end of file +} // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt index 6b0c310232..a5c3f9dcd6 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt @@ -10,20 +10,20 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable(${target_name} test_qco_hadamard_lifting.cpp) target_link_libraries( - ${target_name} - PRIVATE GTest::gtest_main - MLIRParser - MLIRQCOProgramBuilder - MLIRQCOTransforms - MLIRSupportMQT - MLIRQTensorDialect - MLIRQCOUtils - MLIRParser - MLIRIR - MLIRPass - MLIRSupport - LLVMSupport) + ${target_name} + PRIVATE GTest::gtest_main + MLIRParser + MLIRQCOProgramBuilder + MLIRQCOTransforms + MLIRSupportMQT + MLIRQTensorDialect + MLIRQCOUtils + MLIRParser + MLIRIR + MLIRPass + MLIRSupport + LLVMSupport) mqt_mlir_configure_unittest_target(${target_name}) -gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) \ No newline at end of file +gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) From e3da07f8aa2fa1984cd54f47286c88636ae7ef1f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:52:13 +0200 Subject: [PATCH 020/131] :memo: Added information to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c3ef4d7b..e59f49d01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add optimization routine Hadamard lifting ([#1605]) ([**@lirem101**]) - ✨ Add Sampler and Estimator Primitives to the QDMI-Qiskit Interface ([#1507]) ([**@marcelwa**]) - ✨ Add conversions between Jeff and QCO ([#1479], [#1548], [#1565]) ([**@denialhaag**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588]) ([**@MatthiasReumann**]) @@ -335,6 +336,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1605]: https://github.com/munich-quantum-toolkit/core/pull/1605 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 [#1593]: https://github.com/munich-quantum-toolkit/core/pull/1593 From b3bf96634ffab6a13247605e7e450e010a9ce56c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:53:27 +0200 Subject: [PATCH 021/131] :pencil2: Changed Preceeding to preceding --- .../QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 133ec0e4f4..e483ba4c30 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -176,7 +176,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { * @brief Test: Checks if Hadamard gates are lifted over preceding and not over * succeeding Pauli gates. */ -TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPreceedingPauliGate) { +TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPrecedingPauliGate) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.x(q[0]); q[0] = programBuilder.h(q[0]); From e9c66b348fdea523c2aa25c45a3c05ffb7959e60 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 11:01:05 +0200 Subject: [PATCH 022/131] :rotating_light: Fixed linter warnings --- .../Optimization/HadamardLifting.cpp | 23 ++++++++++++------- .../test_qco_hadamard_lifting.cpp | 2 ++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 30069ef001..ada405cb1a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -13,10 +13,15 @@ #include #include +#include #include +#include #include +#include +#include #include +#include namespace mlir::qco { @@ -34,8 +39,8 @@ namespace { * @param range1 The first range. * @param range2 The second range. */ -bool containRangesOfSameElements(const std::vector& range1, - const std::vector& range2) { +static bool containRangesOfSameElements(const std::vector& range1, + const std::vector& range2) { bool result = true; result &= range1.size() == range2.size(); for (auto element : range1) { @@ -77,11 +82,13 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { const Value targetQubitGate1 = gate1.getOutputTarget(0); const auto inCtrlGate2 = gate2.getControlsIn(); const auto outCtrlGate1 = gate1.getControlsOut(); + std::vector inCtrlGate2Vec(inCtrlGate2.begin(), inCtrlGate2.end()); + std::vector outCtrlGate1Vec(outCtrlGate1.begin(), outCtrlGate1.end()); - return std::find(inCtrlGate2.begin(), inCtrlGate2.end(), - targetQubitGate1) != inCtrlGate2.end() && - std::find(outCtrlGate1.begin(), outCtrlGate1.end(), - targetQubitGate2) != outCtrlGate1.end(); + return std::ranges::find(inCtrlGate2Vec, targetQubitGate1) != + inCtrlGate2Vec.end() && + std::ranges::find(outCtrlGate1Vec, targetQubitGate2) != + outCtrlGate1Vec.end(); } /** @@ -135,7 +142,7 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { if (users.empty()) { return failure(); } - const auto user = *users.begin(); + auto* const user = *users.begin(); if (user->getName().stripDialect().str() != "ctrl") { return failure(); } @@ -295,7 +302,7 @@ struct LiftHadamardsAbovePauliGatesPattern final if (users.empty()) { return failure(); } - const auto user = *users.begin(); + auto* const user = *users.begin(); const auto userName = user->getName().stripDialect().str(); if (userName != "h") { if (opName == "ctrl" && userName == "ctrl") { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index e483ba4c30..af31f5e290 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -19,10 +19,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include From 13c046ddd98d4ff7938b02fd4c26bc02f6ab7853 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 12:30:08 +0200 Subject: [PATCH 023/131] :rotating_light: Optimized imports --- .../QCO/Transforms/Optimization/HadamardLifting.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index ada405cb1a..567b0b3516 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -10,15 +10,16 @@ #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include #include #include #include -#include #include #include -#include +#include #include #include #include @@ -28,8 +29,6 @@ namespace mlir::qco { #define GEN_PASS_DEF_HADAMARDLIFTING #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -namespace { - /** * @brief This method checks if two ranges contain of exactly the same * elements. @@ -39,8 +38,8 @@ namespace { * @param range1 The first range. * @param range2 The second range. */ -static bool containRangesOfSameElements(const std::vector& range1, - const std::vector& range2) { +bool containRangesOfSameElements(const std::vector& range1, + const std::vector& range2) { bool result = true; result &= range1.size() == range2.size(); for (auto element : range1) { @@ -49,6 +48,8 @@ static bool containRangesOfSameElements(const std::vector& range1, return result; } +namespace { + /** * @brief This pattern changes the target of a controlled Pauli Z gate if a * controlled hadamard gate is it successor. From db8491e6ef49aca36a6c8840f41b8c60283fe0a5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 12:41:56 +0200 Subject: [PATCH 024/131] :rotating_light: Changed dyn_cast to llvm --- .../Transforms/Optimization/HadamardLifting.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 567b0b3516..334ce9e4a1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -38,8 +38,8 @@ namespace mlir::qco { * @param range1 The first range. * @param range2 The second range. */ -bool containRangesOfSameElements(const std::vector& range1, - const std::vector& range2) { +static bool containRangesOfSameElements(const std::vector& range1, + const std::vector& range2) { bool result = true; result &= range1.size() == range2.size(); for (auto element : range1) { @@ -147,7 +147,7 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { if (user->getName().stripDialect().str() != "ctrl") { return failure(); } - auto hadamardGate = mlir::dyn_cast(user); + auto hadamardGate = llvm::dyn_cast(user); if (hadamardGate.getNumTargets() != 1 || hadamardGate.getBodyUnitary()->getName().stripDialect().str() != "h") { return failure(); @@ -307,14 +307,14 @@ struct LiftHadamardsAbovePauliGatesPattern final const auto userName = user->getName().stripDialect().str(); if (userName != "h") { if (opName == "ctrl" && userName == "ctrl") { - return handleTwoSucceedingControls(mlir::dyn_cast(*op), - mlir::dyn_cast(user), + return handleTwoSucceedingControls(llvm::dyn_cast(*op), + llvm::dyn_cast(user), rewriter); } return failure(); } - auto hadamardGate = mlir::dyn_cast(user); + auto hadamardGate = llvm::dyn_cast(user); if (op.getNumControls() > 0 || hadamardGate.getNumControls() > 0 || op.getNumTargets() != 1 || hadamardGate.getNumTargets() != 1 || @@ -469,7 +469,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { // A Hadamard gate needs to be in front of the measurement const auto qubitInMeasurement = op.getQubitIn(); auto* predecessor = qubitInMeasurement.getDefiningOp(); - auto hadamardGate = mlir::dyn_cast(predecessor); + auto hadamardGate = llvm::dyn_cast(predecessor); if (!hadamardGate || hadamardGate.getNumTargets() != 1 || hadamardGate->getName().stripDialect().str() != "h") { return failure(); @@ -478,7 +478,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { // The Hadamard gate must be successor of the target of a CNOT const auto inQubitHadamard = hadamardGate.getInputQubit(0); predecessor = inQubitHadamard.getDefiningOp(); - auto cnotGate = mlir::dyn_cast(predecessor); + auto cnotGate = llvm::dyn_cast(predecessor); if (!cnotGate || cnotGate.getNumTargets() != 1 || cnotGate.getBodyUnitary()->getName().stripDialect().str() != "x" || cnotGate.getOutputTarget(0) != inQubitHadamard) { From fb70183b7a75fde0b544a20d0db7180db35007fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 05:38:34 +0000 Subject: [PATCH 025/131] =?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 --- CHANGELOG.md | 1 - mlir/lib/Conversion/QCOToQC/CMakeLists.txt | 2 +- mlir/lib/Conversion/QCToQCO/CMakeLists.txt | 2 +- mlir/lib/Dialect/QC/Builder/CMakeLists.txt | 2 +- mlir/lib/Dialect/QC/Transforms/CMakeLists.txt | 44 +++++++++--------- mlir/lib/Dialect/QIR/Builder/CMakeLists.txt | 2 +- .../lib/Dialect/QIR/Transforms/CMakeLists.txt | 46 +++++++++---------- .../Dialect/QTensor/Transforms/CMakeLists.txt | 44 +++++++++--------- mlir/lib/Support/CMakeLists.txt | 10 ++-- .../Dialect/QTensor/IR/CMakeLists.txt | 2 +- 10 files changed, 77 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cca6df6d9..4c33372457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -344,7 +344,6 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1588]: https://github.com/munich-quantum-toolkit/core/pull/1588 [#1583]: https://github.com/munich-quantum-toolkit/core/pull/1583 [#1581]: https://github.com/munich-quantum-toolkit/core/pull/1581 - [#1580]: https://github.com/munich-quantum-toolkit/core/pull/1580 [#1573]: https://github.com/munich-quantum-toolkit/core/pull/1573 [#1572]: https://github.com/munich-quantum-toolkit/core/pull/1572 diff --git a/mlir/lib/Conversion/QCOToQC/CMakeLists.txt b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt index 5b33c286bc..b3a1b1cf18 100644 --- a/mlir/lib/Conversion/QCOToQC/CMakeLists.txt +++ b/mlir/lib/Conversion/QCOToQC/CMakeLists.txt @@ -19,7 +19,7 @@ add_mlir_conversion_library( MLIRQTensorDialect MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect + MLIRMemRefDialect MLIRTransforms MLIRFuncTransforms) diff --git a/mlir/lib/Conversion/QCToQCO/CMakeLists.txt b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt index eaf430f600..c448b8f765 100644 --- a/mlir/lib/Conversion/QCToQCO/CMakeLists.txt +++ b/mlir/lib/Conversion/QCToQCO/CMakeLists.txt @@ -19,7 +19,7 @@ add_mlir_conversion_library( MLIRQTensorDialect MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect + MLIRMemRefDialect MLIRTransforms MLIRFuncTransforms) diff --git a/mlir/lib/Dialect/QC/Builder/CMakeLists.txt b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt index e120f1f211..d5f7f51caa 100644 --- a/mlir/lib/Dialect/QC/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Builder/CMakeLists.txt @@ -13,7 +13,7 @@ add_mlir_library( PUBLIC MLIRArithDialect MLIRFuncDialect - MLIRMemRefDialect + MLIRMemRefDialect MLIRQCDialect) mqt_mlir_target_use_project_options(MLIRQCProgramBuilder) diff --git a/mlir/lib/Dialect/QC/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QC/Transforms/CMakeLists.txt index e847b4ebba..e64d323c01 100644 --- a/mlir/lib/Dialect/QC/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QC/Transforms/CMakeLists.txt @@ -9,32 +9,32 @@ file(GLOB_RECURSE PASSES_SOURCES *.cpp) add_mlir_library( - MLIRQCTransforms - ${PASSES_SOURCES} - LINK_LIBS - PRIVATE - MLIRQCDialect - DEPENDS - MLIRQCTransformsIncGen) + MLIRQCTransforms + ${PASSES_SOURCES} + LINK_LIBS + PRIVATE + MLIRQCDialect + DEPENDS + MLIRQCTransformsIncGen) # collect header files file(GLOB_RECURSE PASSES_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Transforms/*.h) + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QC/Transforms/*.h) file(GLOB_RECURSE PASSES_HEADERS_BUILD - ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QC/Transforms/*.inc) + ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QC/Transforms/*.inc) # add public headers using file sets target_sources( - MLIRQCTransforms - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_SOURCE_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_SOURCE} - FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_BUILD_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_BUILD}) + MLIRQCTransforms + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt index fef6fce594..78e8e29c9e 100644 --- a/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt +++ b/mlir/lib/Dialect/QIR/Builder/CMakeLists.txt @@ -12,7 +12,7 @@ add_mlir_library( LINK_LIBS PUBLIC MLIRLLVMDialect - MLIRMemRefDialect + MLIRMemRefDialect MLIRQIRUtils MLIRIR MLIRSupport) diff --git a/mlir/lib/Dialect/QIR/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QIR/Transforms/CMakeLists.txt index 3e60d10560..95afa87e3f 100644 --- a/mlir/lib/Dialect/QIR/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QIR/Transforms/CMakeLists.txt @@ -9,35 +9,35 @@ file(GLOB_RECURSE PASSES_SOURCES *.cpp) add_mlir_library( - MLIRQIRTransforms - ${PASSES_SOURCES} - LINK_LIBS - PRIVATE - MLIRLLVMDialect - MLIRQIRUtils - DEPENDS - MLIRQIRTransformsIncGen) + MLIRQIRTransforms + ${PASSES_SOURCES} + LINK_LIBS + PRIVATE + MLIRLLVMDialect + MLIRQIRUtils + DEPENDS + MLIRQIRTransformsIncGen) mqt_mlir_target_use_project_options(MLIRQIRTransforms) # collect header files file(GLOB_RECURSE PASSES_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Transforms/*.h) + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QIR/Transforms/*.h) file(GLOB_RECURSE PASSES_HEADERS_BUILD - ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QIR/Transforms/*.inc) + ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QIR/Transforms/*.inc) # add public headers using file sets target_sources( - MLIRQIRTransforms - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_SOURCE_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_SOURCE} - FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_BUILD_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_BUILD}) + MLIRQIRTransforms + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_BUILD}) diff --git a/mlir/lib/Dialect/QTensor/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QTensor/Transforms/CMakeLists.txt index 6d05ff37c4..2c110797bd 100644 --- a/mlir/lib/Dialect/QTensor/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QTensor/Transforms/CMakeLists.txt @@ -9,32 +9,32 @@ file(GLOB_RECURSE PASSES_SOURCES *.cpp) add_mlir_library( - MLIRQTensorTransforms - ${PASSES_SOURCES} - LINK_LIBS - PRIVATE - MLIRQTensorDialect - DEPENDS - MLIRQTensorTransformsIncGen) + MLIRQTensorTransforms + ${PASSES_SOURCES} + LINK_LIBS + PRIVATE + MLIRQTensorDialect + DEPENDS + MLIRQTensorTransformsIncGen) # collect header files file(GLOB_RECURSE PASSES_HEADERS_SOURCE - ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Transforms/*.h) + ${MQT_MLIR_SOURCE_INCLUDE_DIR}/mlir/Dialect/QTensor/Transforms/*.h) file(GLOB_RECURSE PASSES_HEADERS_BUILD - ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Transforms/*.inc) + ${MQT_MLIR_BUILD_INCLUDE_DIR}/mlir/Dialect/QTensor/Transforms/*.inc) # add public headers using file sets target_sources( - MLIRQTensorTransforms - PUBLIC FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_SOURCE_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_SOURCE} - FILE_SET - HEADERS - BASE_DIRS - ${MQT_MLIR_BUILD_INCLUDE_DIR} - FILES - ${PASSES_HEADERS_BUILD}) + MLIRQTensorTransforms + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_SOURCE_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_SOURCE} + FILE_SET + HEADERS + BASE_DIRS + ${MQT_MLIR_BUILD_INCLUDE_DIR} + FILES + ${PASSES_HEADERS_BUILD}) diff --git a/mlir/lib/Support/CMakeLists.txt b/mlir/lib/Support/CMakeLists.txt index 4cdeb51372..462d791329 100644 --- a/mlir/lib/Support/CMakeLists.txt +++ b/mlir/lib/Support/CMakeLists.txt @@ -23,11 +23,11 @@ add_mlir_library( MLIRTransformUtils MLIRLLVMDialect MLIRFuncDialect - MLIRArithDialect - MLIRQCTransforms - MLIRQIRTransforms - MLIRQTensorTransforms - MLIRQTensorDialect) + MLIRArithDialect + MLIRQCTransforms + MLIRQIRTransforms + MLIRQTensorTransforms + MLIRQTensorDialect) mqt_mlir_target_use_project_options(MLIRSupportMQT) diff --git a/mlir/unittests/Dialect/QTensor/IR/CMakeLists.txt b/mlir/unittests/Dialect/QTensor/IR/CMakeLists.txt index 0f2728f46a..b7e2227682 100644 --- a/mlir/unittests/Dialect/QTensor/IR/CMakeLists.txt +++ b/mlir/unittests/Dialect/QTensor/IR/CMakeLists.txt @@ -9,7 +9,7 @@ set(qtensor_ir_target mqt-core-mlir-unittest-qtensor-ir) add_executable(${qtensor_ir_target} test_qtensor_ir.cpp) target_link_libraries(${qtensor_ir_target} PRIVATE GTest::gtest_main MLIRParser MLIRSupportMQT - MLIRQCOProgramBuilder MLIRQCOPrograms) + MLIRQCOProgramBuilder MLIRQCOPrograms) mqt_mlir_configure_unittest_target(${qtensor_ir_target}) gtest_discover_tests(${qtensor_ir_target} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) From d785bdb6dcc615f1579e29d8ffd8ee84e7acbb13 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 08:26:39 +0200 Subject: [PATCH 026/131] :construction: Fixed branching for optimization passes --- mlir/lib/Compiler/CompilerPipeline.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 108c99da43..bd01f31483 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -142,14 +142,16 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, pm.addPass(qco::createHadamardLifting()); } populateQCOCleanupPipeline(pm); - }))) - if (record != nullptr && config_.recordIntermediates) { - record->afterOptimization = captureIR(module); - if (config_.printIRAfterAllStages) { - prettyPrintStage(module, "Optimization Passes", ++currentStage, - totalStages); - } + }))) { + return failure(); + } + if (record != nullptr && config_.recordIntermediates) { + record->afterOptimization = captureIR(module); + if (config_.printIRAfterAllStages) { + prettyPrintStage(module, "Optimization Passes", ++currentStage, + totalStages); } + } // Stage 6: QCO cleanup if (failed( runStage([&](PassManager& pm) { populateQCOCleanupPipeline(pm); }))) { From cc97cfd4e10a8b3b73436e2350138a4942511200 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:17:34 +0200 Subject: [PATCH 027/131] :memo: Updated Hadamard lifting documentation and fixed typos --- .../mlir/Dialect/QCO/Transforms/Passes.td | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index b17f7ec266..5b5e973011 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,11 +78,13 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to lift Hadamards as much as possible by " - "flipping them with Pauli gates."; + let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by flipping them " + "with Pauli gates. Additionally, it can change target and control qubits from Pauli-Z gates to make " + "Hadamard lifting applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement " + "directly after a control."; let description = [{ - This pass lifts hadamards away from the measurements in order to apply lift-measurements more effectively. It uses - the following commutation rules: + This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. It + uses the following commutation rules: ┌───┐ ┌───┐ ┌───┐ ┌───┐ ─┤ X ├─┤ H ├─ => ─┤ H ├─┤ Z ├─ └───┘ └───┘ └───┘ └───┘ @@ -95,7 +97,7 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { Hadamard lifting is only applied to lift Hadamard gates further in front in the circuit, not the other way around. If the Hadamard and Pauli gates are controlled, they are only lifted if both gates are controlled by exactly the - same qubits. One exception to this are the controlled Pauli Z gates. For these gates, controls and targets are + same qubits. One exception to this is the controlled Pauli Z gate. For these gates, controls and targets are interchangeable. Therefore, transformation routines as follows are possible and applied: ┌───┐ ┤ Z ├──■── ──■────■── ──■────■── @@ -105,22 +107,21 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { The commutation of controlled gates is only done if there are no gates applied between the two controlled gates to any of the qubits used on the controlled gates. - In order to minimize the reduce multi-qubit gates, a third transformation routine is applied. Using the commutation - rule + In order to reduce multi-qubit gates, a third transformation routine is applied. Using the commutation rule ┌───┐┌───┐┌───┐ ──■── ┤ H ├┤ X ├┤ H ├ ┌─┴─┐ => │───│└─┬─┘│───│ ┤ X ├ ┤ H ├──■──┤ H ├ └───┘ └───┘ └───┘ - the following transformation is applied to circuits pattern, where a Hadamard gate follows a the target gate of a - CNOT, which is followed by a measurement: + the following transformation is applied to circuits where a Hadamard gate follows a target gate of a controlled + Pauli X gate, which is followed by a measurement: ┌───┐┌───┐┌───┐ ──■───────────── ┤ H ├┤ X ├┤ H ├── ┌─┴─┐┌───┐┌──────┐ => │───│└─┬─┘│───┘──┐ ┤ X ├┤ H ├┤ Meas │ ┤ H ├──■──┤ Meas │ └───┘└───┘└──────┘ └───┘ └──────┘ - Afterwards, the measurement lifting routine could transform the CNOT into a classically controlled Pauli X. + Afterward the measurement lifting routine could transform the CNOT into a classically controlled Pauli X. }]; } From 26ca2edf938d41603460be2a9f01dccf334e51a4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:19:35 +0200 Subject: [PATCH 028/131] :construction: Removed double MLIRParser in CMakeLists --- .../unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt index a5c3f9dcd6..5d6043aaad 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt @@ -18,7 +18,6 @@ target_link_libraries( MLIRSupportMQT MLIRQTensorDialect MLIRQCOUtils - MLIRParser MLIRIR MLIRPass MLIRSupport From a15d2c135f1feefa32e2f6e978f70d6d7b753801 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:20:58 +0200 Subject: [PATCH 029/131] :white_check_mark: Apply only canonicalizer to reference code --- .../test_qco_hadamard_lifting.cpp | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 9fb3dd445e..0bb183f036 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -94,7 +95,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -125,7 +126,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftPauliOverHadamardGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -168,7 +169,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -202,7 +203,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPrecedingPauliGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -234,7 +235,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -262,7 +263,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -288,7 +289,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -318,7 +319,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -384,7 +385,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -420,7 +421,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -456,7 +457,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( @@ -496,7 +497,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); PassManager pm(reference->getContext()); - pm.addPass(createHadamardLifting()); + pm.addPass(createCanonicalizerPass()); EXPECT_TRUE(pm.run(*reference).succeeded()); EXPECT_TRUE( From 233da3f4c0277ac2b919ad9984492493f11ef391 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:21:50 +0200 Subject: [PATCH 030/131] :recycle: use is_permutation --- .../QCO/Transforms/Optimization/HadamardLifting.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 334ce9e4a1..7ac218ea9f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -40,12 +40,8 @@ namespace mlir::qco { */ static bool containRangesOfSameElements(const std::vector& range1, const std::vector& range2) { - bool result = true; - result &= range1.size() == range2.size(); - for (auto element : range1) { - result &= std::ranges::find(range2, element) != range2.end(); - } - return result; + return range1.size() == range2.size() && + std::ranges::is_permutation(range1, range2); } namespace { From 6af2a96553f904c6282c6526cdb7948930d333c2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 13:02:37 +0200 Subject: [PATCH 031/131] :bug: Controlled Pauli Y cannot be lifted --- .../mlir/Dialect/QCO/Transforms/Passes.td | 7 +++-- .../Optimization/HadamardLifting.cpp | 16 ++++++++--- .../test_qco_hadamard_lifting.cpp | 28 +++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 5b5e973011..1858a91b60 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -97,8 +97,11 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { Hadamard lifting is only applied to lift Hadamard gates further in front in the circuit, not the other way around. If the Hadamard and Pauli gates are controlled, they are only lifted if both gates are controlled by exactly the - same qubits. One exception to this is the controlled Pauli Z gate. For these gates, controls and targets are - interchangeable. Therefore, transformation routines as follows are possible and applied: + same qubits and the Pauli gate is not a Pauli Y gate. In that case, the swap of the Pauli Y gate and the Hadamard + gate introduces a phase (YH = -HY) which leads to a non-global phase for controlled gates. + + In the case of the controlled Pauli Z gate, controls and targets are interchangeable. Therefore, transformation + routines as follows are possible and applied: ┌───┐ ┤ Z ├──■── ──■────■── ──■────■── └─┬─┘┌─┴─┐ => ┌─┴─┐┌─┴─┐ => ┌─┴─┐┌─┴─┐ diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 7ac218ea9f..d53c8a340b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -192,7 +192,10 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { * - Y - H - = - H - Y - * - Z - H - = - H - X - * This is applied to uncontrolled gates and controlled ones, if the controls - * are applied to the same qubits for both gates. + * are applied to the same qubits for both gates and the Pauli gates are X or Y. + * In case of Pauli Y, the routine is only applied to uncontrolled gates, as + * HY = -YH, which leads to a relative phase if the Hadamard and Pauli gate are + * controlled. */ struct LiftHadamardsAbovePauliGatesPattern final : OpInterfaceRewritePattern { @@ -225,19 +228,24 @@ struct LiftHadamardsAbovePauliGatesPattern final * - X - H - = - H - Z - * - Y - H - = - H - Y - * - Z - H - = - H - X - + * A Pauli Y gate is only swapped with the Hadamard gate if they are + * uncontrolled. * * @param gate The Pauli gate. * @param hadamardGate The Hadamard gate. + * @param controlled Whether the gates are controlled or not. * @param rewriter The used rewriter. * @return success() if circuit was changed, failure() otherwise */ static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, UnitaryOpInterface hadamardGate, + const bool controlled, PatternRewriter& rewriter) { const auto gateName = gate->getName().stripDialect().str(); const auto hadamardName = hadamardGate->getName().stripDialect().str(); if (hadamardName != "h" || - (gateName != "x" && gateName != "y" && gateName != "z")) { + (gateName != "x" && gateName != "y" && gateName != "z") || + (gateName == "y" && controlled)) { return failure(); } rewriter.setInsertionPoint(gate); @@ -276,7 +284,7 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } return swapPauliWithHadamard(firstGate.getBodyUnitary(), - secondGate.getBodyUnitary(), rewriter); + secondGate.getBodyUnitary(), true, rewriter); } /** @@ -318,7 +326,7 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } - return swapPauliWithHadamard(op, hadamardGate, rewriter); + return swapPauliWithHadamard(op, hadamardGate, false, rewriter); } }; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 0bb183f036..973c8407a0 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -242,6 +242,34 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: Checks that Hadamard gates are not lifted if they are controlled + * and the Pauli gate is a Pauli Y gate. + */ +TEST_F(QCOHadamardLiftingTest, doNotliftHadamardOverPauliYGateIfControlled) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.y(q[0]); + auto qubitPair = programBuilder.cy(q[1], q[0]); + qubitPair = programBuilder.ch(qubitPair.first, qubitPair.second); + programBuilder.cy(qubitPair.first, qubitPair.second); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.y(qRef[0]); + auto qubitPairRef = referenceBuilder.cy(qRef[1], qRef[0]); + qubitPairRef = referenceBuilder.ch(qubitPairRef.first, qubitPairRef.second); + referenceBuilder.cy(qubitPairRef.first, qubitPairRef.second); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + PassManager pm(reference->getContext()); + pm.addPass(createCanonicalizerPass()); + EXPECT_TRUE(pm.run(*reference).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + /** * @brief Test: Checks that a hadamard gate is not lifted if they are controlled * by a different qubit than the one lifted gate is. From 664abadac5e8ef2e26b76fe97790548e5c321516 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:03:42 +0000 Subject: [PATCH 032/131] =?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/QCO/Transforms/Passes.td | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 1858a91b60..d690c3ef16 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,9 +78,12 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by flipping them " - "with Pauli gates. Additionally, it can change target and control qubits from Pauli-Z gates to make " - "Hadamard lifting applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement " + let summary = "This pass attempts to move Hadamard gates as far away from " + "measurements as possible by flipping them " + "with Pauli gates. Additionally, it can change target and " + "control qubits from Pauli-Z gates to make " + "Hadamard lifting applicable. It also lifts Hadamard gates " + "over CNOT gates if that moves a measurement " "directly after a control."; let description = [{ This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. It From cf245b1bc59480f60b4c8f0e6df85c11130d6968 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 14:20:31 +0200 Subject: [PATCH 033/131] :recycle: Refactored runCanonicalizerPass method --- .../test_qco_hadamard_lifting.cpp | 61 +++++++------------ 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 973c8407a0..4932af6f1d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -63,6 +63,15 @@ class QCOHadamardLiftingTest : public testing::Test { pm.addPass(createHadamardLifting()); return pm.run(module); } + + /** + * @brief Adds the canonicalizerPass to the current context and runs it. + */ + static LogicalResult runCanonicalizerPass(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createCanonicalizerPass()); + return pm.run(module); + } }; } // namespace @@ -94,9 +103,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -125,9 +132,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftPauliOverHadamardGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -168,9 +173,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -202,9 +205,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPrecedingPauliGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -234,9 +235,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -262,9 +261,7 @@ TEST_F(QCOHadamardLiftingTest, doNotliftHadamardOverPauliYGateIfControlled) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -290,9 +287,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -316,9 +311,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -346,9 +339,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -412,9 +403,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -448,9 +437,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -484,9 +471,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -524,9 +509,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - PassManager pm(reference->getContext()); - pm.addPass(createCanonicalizerPass()); - EXPECT_TRUE(pm.run(*reference).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); From 57159aecc9a92121aec268743e79dcb5147c0f85 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 14:45:41 +0200 Subject: [PATCH 034/131] :rotating_light: Optimized imports and test naming --- .../Transforms/Optimization/test_qco_hadamard_lifting.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 4932af6f1d..9f55573387 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -25,9 +25,6 @@ #include #include -#include -#include - namespace { using namespace mlir; @@ -245,7 +242,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { * @brief Test: Checks that Hadamard gates are not lifted if they are controlled * and the Pauli gate is a Pauli Y gate. */ -TEST_F(QCOHadamardLiftingTest, doNotliftHadamardOverPauliYGateIfControlled) { +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliYGateIfControlled) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.y(q[0]); auto qubitPair = programBuilder.cy(q[1], q[0]); From c966623fbb9ffdfebbe2b862def506898b9fd845 Mon Sep 17 00:00:00 2001 From: LiRem101 <63499678+LiRem101@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:04:26 +0200 Subject: [PATCH 035/131] :construction: Apply suggestions from code review Co-authored-by: Lukas Burgholzer Signed-off-by: LiRem101 <63499678+LiRem101@users.noreply.github.com> --- mlir/include/mlir/Compiler/CompilerPipeline.h | 2 +- .../mlir/Dialect/QCO/Transforms/Passes.td | 2 +- mlir/lib/Compiler/CompilerPipeline.cpp | 1 - .../Optimization/HadamardLifting.cpp | 24 ++++++++----------- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index ef64c7ebe9..88d1fde572 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -42,7 +42,7 @@ struct QuantumCompilerConfig { bool printIRAfterAllStages = false; /// Enable Hadamard lifting - bool hadamardLifting = false; + bool enableHadamardLifting = false; }; /** diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index d690c3ef16..4255fdc961 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -79,7 +79,7 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass attempts to move Hadamard gates as far away from " - "measurements as possible by flipping them " + "measurements as possible by commuting them " "with Pauli gates. Additionally, it can change target and " "control qubits from Pauli-Z gates to make " "Hadamard lifting applicable. It also lifts Hadamard gates " diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index bd01f31483..c856760811 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -141,7 +141,6 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (config_.hadamardLifting) { pm.addPass(qco::createHadamardLifting()); } - populateQCOCleanupPipeline(pm); }))) { return failure(); } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index d53c8a340b..52c2303b41 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -48,11 +48,11 @@ namespace { /** * @brief This pattern changes the target of a controlled Pauli Z gate if a - * controlled hadamard gate is it successor. + * controlled Hadamard gate is it successor. * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can * commute the gates and change Pauli Z to X. This is only possible if Hadamard * and Pauli act on the same qubit as target. If the target of the Pauli gate is - * a ctrl at the hadamard and vice versa, we can change the target of Pauli Z to + * a control at the hadamard and vice versa, we can change the target of Pauli Z to * the Hadamard's. This is done in this pattern. */ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { @@ -130,20 +130,16 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { PatternRewriter& rewriter) const override { // op needs to be a Pauli Z gate and controlled std::string opName = op.getBodyUnitary()->getName().stripDialect().str(); - if (op.getNumTargets() != 1 || opName != "z") { + if (opName != "z") { return failure(); } // op needs to be in front of a controlled hadamard gate - const auto& users = op->getUsers(); - if (users.empty()) { - return failure(); - } - auto* const user = *users.begin(); + auto* const user = *op->getUsers().begin(); if (user->getName().stripDialect().str() != "ctrl") { return failure(); } - auto hadamardGate = llvm::dyn_cast(user); + auto hadamardGate = llvm::cast(user); if (hadamardGate.getNumTargets() != 1 || hadamardGate.getBodyUnitary()->getName().stripDialect().str() != "h") { return failure(); @@ -192,7 +188,7 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { * - Y - H - = - H - Y - * - Z - H - = - H - X - * This is applied to uncontrolled gates and controlled ones, if the controls - * are applied to the same qubits for both gates and the Pauli gates are X or Y. + * are applied to the same qubits for both gates and the Pauli gates are X or Z. * In case of Pauli Y, the routine is only applied to uncontrolled gates, as * HY = -YH, which leads to a relative phase if the Hadamard and Pauli gate are * controlled. @@ -203,7 +199,7 @@ struct LiftHadamardsAbovePauliGatesPattern final : OpInterfaceRewritePattern(context) {} /** - * This method checks whether the first and second controls are controlled by + * This method checks whether the first and second operations are controlled by * the same qubits. * * @param firstCtrl The first (preceding) controlled gate. @@ -238,7 +234,7 @@ struct LiftHadamardsAbovePauliGatesPattern final * @return success() if circuit was changed, failure() otherwise */ static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, - UnitaryOpInterface hadamardGate, + HOp hadamardGate, const bool controlled, PatternRewriter& rewriter) { const auto gateName = gate->getName().stripDialect().str(); @@ -331,7 +327,7 @@ struct LiftHadamardsAbovePauliGatesPattern final }; /** - * @brief This pattern remove an H gate between a CNOT and a measurement. + * @brief This pattern removes an H gate between a CNOT and a measurement. * * If there is a Hadamard gate between the target qubit of a CNOT and a * measurement, we flip the CNOT and apply a hadamard gate to the incoming and @@ -339,7 +335,7 @@ struct LiftHadamardsAbovePauliGatesPattern final * of a CNOT ctrl, which is beneficial for the qubit reuse routine. * The procedure also works if there are additional ctrls. Only the target * and ctrl involved in the transformation get hadamard gates assigned. - * For now, the involved ctrl to be flipped with the target is chosen randomly. + * The involved ctrl to be flipped with the target is chosen randomly. */ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { From 8d32188472cef30cfd1160063c71fbe61b253fdc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Apr 2026 15:57:34 +0200 Subject: [PATCH 036/131] :construction: Adapated code to suggestions from code review --- mlir/lib/Compiler/CompilerPipeline.cpp | 2 +- .../Optimization/HadamardLifting.cpp | 28 ++++++++++--------- .../Compiler/test_compiler_pipeline.cpp | 4 +-- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index c856760811..0ed41b30af 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -138,7 +138,7 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, } // Stage 5: Optimization passes if (failed(runStage([&](PassManager& pm) { - if (config_.hadamardLifting) { + if (config_.enableHadamardLifting) { pm.addPass(qco::createHadamardLifting()); } }))) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 52c2303b41..f3399862ad 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -52,8 +52,8 @@ namespace { * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can * commute the gates and change Pauli Z to X. This is only possible if Hadamard * and Pauli act on the same qubit as target. If the target of the Pauli gate is - * a control at the hadamard and vice versa, we can change the target of Pauli Z to - * the Hadamard's. This is done in this pattern. + * a control at the hadamard and vice versa, we can change the target of Pauli Z + * to the Hadamard's. This is done in this pattern. */ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { @@ -199,8 +199,8 @@ struct LiftHadamardsAbovePauliGatesPattern final : OpInterfaceRewritePattern(context) {} /** - * This method checks whether the first and second operations are controlled by - * the same qubits. + * This method checks whether the first and second operations are controlled + * by the same qubits. * * @param firstCtrl The first (preceding) controlled gate. * @param secondCtrl The second (succeeding) controlled gate. @@ -238,9 +238,7 @@ struct LiftHadamardsAbovePauliGatesPattern final const bool controlled, PatternRewriter& rewriter) { const auto gateName = gate->getName().stripDialect().str(); - const auto hadamardName = hadamardGate->getName().stripDialect().str(); - if (hadamardName != "h" || - (gateName != "x" && gateName != "y" && gateName != "z") || + if ((gateName != "x" && gateName != "y" && gateName != "z") || (gateName == "y" && controlled)) { return failure(); } @@ -274,13 +272,16 @@ struct LiftHadamardsAbovePauliGatesPattern final static LogicalResult handleTwoSucceedingControls(CtrlOp firstGate, CtrlOp secondGate, PatternRewriter& rewriter) { - if (firstGate.getNumTargets() != 1 || secondGate.getNumTargets() != 1 || + auto hadamardGate = + llvm::dyn_cast(secondGate.getBodyUnitary().getOperation()); + if (!hadamardGate || firstGate.getNumTargets() != 1 || + secondGate.getNumTargets() != 1 || firstGate.getOutputTarget(0) != secondGate.getInputTarget(0) || !areControlsControlledBySameQubits(firstGate, secondGate)) { return failure(); } - return swapPauliWithHadamard(firstGate.getBodyUnitary(), - secondGate.getBodyUnitary(), true, rewriter); + return swapPauliWithHadamard(firstGate.getBodyUnitary(), hadamardGate, true, + rewriter); } /** @@ -314,10 +315,11 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } - auto hadamardGate = llvm::dyn_cast(user); + auto hadamardGate = llvm::dyn_cast(user); - if (op.getNumControls() > 0 || hadamardGate.getNumControls() > 0 || - op.getNumTargets() != 1 || hadamardGate.getNumTargets() != 1 || + if (!hadamardGate || op.getNumControls() > 0 || + hadamardGate.getNumControls() > 0 || op.getNumTargets() != 1 || + hadamardGate.getNumTargets() != 1 || op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { return failure(); } diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 9479cb0caa..c731d82400 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -117,11 +117,11 @@ class CompilerPipelineTest } static void runPipeline(const mlir::ModuleOp module, const bool convertToQIR, - const bool hadamardLifting, + const bool enableHadamardLifting, mlir::CompilationRecord& record) { mlir::QuantumCompilerConfig config; config.convertToQIR = convertToQIR; - config.hadamardLifting = hadamardLifting; + config.enableHadamardLifting = enableHadamardLifting; config.recordIntermediates = true; config.printIRAfterAllStages = true; From edc59a2b8c456924eb07228646ce0d3d5c7f54c0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Apr 2026 15:59:01 +0200 Subject: [PATCH 037/131] :memo: Adapted documentation according to review --- .../mlir/Dialect/QCO/Transforms/Passes.td | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 4255fdc961..8ac78fee0c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,56 +78,49 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to move Hadamard gates as far away from " - "measurements as possible by commuting them " - "with Pauli gates. Additionally, it can change target and " - "control qubits from Pauli-Z gates to make " - "Hadamard lifting applicable. It also lifts Hadamard gates " - "over CNOT gates if that moves a measurement " - "directly after a control."; + let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by commuting them " + "with Pauli gates. This is done to in order to apply measurement lifting more efficiently, which is a sub-routine of " + "qubit resuse. Additionally, it can change target and control qubits from Pauli-Z gates to make Hadamard lifting " + "applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement directly after a control."; let description = [{ - This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. It - uses the following commutation rules: + This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. + Measurement lifting is a subroutine of the qubit reuse routine. The goal is to measure qubits earlier in the + circuit to reuse them and to potentially remove some quantum gates. + + Measurement lifting commutes measurements with Pauli and phase gates. However, the routine stops if a Hadmard gate + is applied before the measurement takes place. Hadamard lifting attempts to move Hadamards further in front of the + circuit, in order to improve the results provided by measurement lifting. + + Hadamard lifting uses the following commutation rules: ┌───┐ ┌───┐ ┌───┐ ┌───┐ ─┤ X ├─┤ H ├─ => ─┤ H ├─┤ Z ├─ └───┘ └───┘ └───┘ └───┘ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ─┤ Z ├─┤ H ├─ => ─┤ H ├─┤ X ├─ └───┘ └───┘ └───┘ └───┘ - ┌───┐ ┌───┐ ┌───┐ ┌───┐ - ─┤ Y ├─┤ H ├─ => ─┤ H ├─┤ Y ├─ - └───┘ └───┘ └───┘ └───┘ - Hadamard lifting is only applied to lift Hadamard gates further in front in the circuit, not the other way around. - - If the Hadamard and Pauli gates are controlled, they are only lifted if both gates are controlled by exactly the - same qubits and the Pauli gate is not a Pauli Y gate. In that case, the swap of the Pauli Y gate and the Hadamard - gate introduces a phase (YH = -HY) which leads to a non-global phase for controlled gates. - - In the case of the controlled Pauli Z gate, controls and targets are interchangeable. Therefore, transformation - routines as follows are possible and applied: - ┌───┐ - ┤ Z ├──■── ──■────■── ──■────■── - └─┬─┘┌─┴─┐ => ┌─┴─┐┌─┴─┐ => ┌─┴─┐┌─┴─┐ - ──■──┤ H ├ ┤ Z ├┤ H ├ ┤ H ├┤ X ├ - └───┘ └───┘└───┘ └───┘└───┘ - The commutation of controlled gates is only done if there are no gates applied between the two controlled gates to - any of the qubits used on the controlled gates. - - In order to reduce multi-qubit gates, a third transformation routine is applied. Using the commutation rule + ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───────┐ + ─┤ Y ├─┤ H ├─ => ─┤ H ├─┤ Y ├─┤ G(pi) ├─ + └───┘ └───┘ └───┘ └───┘ └───────┘ + Hadamard lifting is only applied to commute Hadamard gates further in front in the circuit, not the other way + around. A global phase is added if the Hadamard gate is commuted with a Pauli-Y gate, as YH = -HY. + + The routine is only applied to non-controlled Pauli- and Hadamard gates. + + In order to reduce multi-qubit gates, a second transformation routine is applied. Using the commutation rule ┌───┐┌───┐┌───┐ ──■── ┤ H ├┤ X ├┤ H ├ ┌─┴─┐ => │───│└─┬─┘│───│ ┤ X ├ ┤ H ├──■──┤ H ├ └───┘ └───┘ └───┘ the following transformation is applied to circuits where a Hadamard gate follows a target gate of a controlled - Pauli X gate, which is followed by a measurement: + Pauli-X gate, which is followed by a measurement: ┌───┐┌───┐┌───┐ ──■───────────── ┤ H ├┤ X ├┤ H ├── ┌─┴─┐┌───┐┌──────┐ => │───│└─┬─┘│───┘──┐ ┤ X ├┤ H ├┤ Meas │ ┤ H ├──■──┤ Meas │ └───┘└───┘└──────┘ └───┘ └──────┘ - Afterward the measurement lifting routine could transform the CNOT into a classically controlled Pauli X. + Afterward the measurement lifting routine could transform the CNOT into a classically controlled Pauli-X. }]; } From cdfc8edf752c417f99aa52ec4834d7bbe080c55e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Apr 2026 16:32:47 +0200 Subject: [PATCH 038/131] :construction: Implemented changes suggested by review, removed handling of controlled gates --- .../Optimization/HadamardLifting.cpp | 223 ++---------------- .../test_qco_hadamard_lifting.cpp | 74 +----- 2 files changed, 19 insertions(+), 278 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index f3399862ad..5044a10aee 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -12,11 +12,11 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include #include #include #include #include -#include #include #include @@ -46,138 +46,6 @@ static bool containRangesOfSameElements(const std::vector& range1, namespace { -/** - * @brief This pattern changes the target of a controlled Pauli Z gate if a - * controlled Hadamard gate is it successor. - * If all out qubits of Pauli Z are equal to all in qubits of Hadamard, we can - * commute the gates and change Pauli Z to X. This is only possible if Hadamard - * and Pauli act on the same qubit as target. If the target of the Pauli gate is - * a control at the hadamard and vice versa, we can change the target of Pauli Z - * to the Hadamard's. This is done in this pattern. - */ -struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { - - explicit AdaptCtrldPauliZToLiftingPattern(MLIRContext* context) - : OpRewritePattern(context) {} - - /** - * @brief Checks if the target qubit of gate 1 is part of the ctrl qubits of - * gate 2 and vice versa. - * - * This method checks if the output target qubit of gate 1 is used as control - * qubit of gate 2. Additionally, it checks if the input target of gate 2 is - * an output control of gate 2. Returns true if that is the case. - * Must only be used on gates that have a single target qubit. - * - * @param gate1 First gate, predecessor of gate2. - * @param gate2 Second gate, successor of gate1. - * @return True if target qubit of gate1 is ctrl in gate2 and vice versa. - * False otherwise. - */ - static bool areTargetsControlsAtTheOtherGates(CtrlOp gate1, CtrlOp gate2) { - const Value targetQubitGate2 = gate2.getInputTarget(0); - const Value targetQubitGate1 = gate1.getOutputTarget(0); - const auto inCtrlGate2 = gate2.getControlsIn(); - const auto outCtrlGate1 = gate1.getControlsOut(); - std::vector inCtrlGate2Vec(inCtrlGate2.begin(), inCtrlGate2.end()); - std::vector outCtrlGate1Vec(outCtrlGate1.begin(), outCtrlGate1.end()); - - return std::ranges::find(inCtrlGate2Vec, targetQubitGate1) != - inCtrlGate2Vec.end() && - std::ranges::find(outCtrlGate1Vec, targetQubitGate2) != - outCtrlGate1Vec.end(); - } - - /** - * @brief This method exchanges the position of two qubits acting on the same - * gate. - * - * This method exchanges two qubits acting on the same gate. E.g. if qubit 1 - * is a target qubit and qubit 2 a control qubit, that is exchanged. - * - * - * @param gate The gate both qubit1 and qubit2 belong to. - * @param qubit1 First qubit, exchanged with second. - * @param qubit2 Second qubit, exchanged with first. - * @param rewriter The rewriter. - */ - static void exchangeTwoQubitsAtGate(UnitaryOpInterface gate, - const Value qubit1, const Value qubit2, - PatternRewriter& rewriter) { - const auto temporary = - IdOp::create(rewriter, gate.getLoc(), gate.getInputTarget(0)) - .getResult(); - rewriter.replaceUsesWithIf( - qubit1, temporary, - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); - rewriter.replaceUsesWithIf(qubit2, qubit1, [&](const OpOperand& operand) { - return operand.getOwner() == gate; - }); - rewriter.replaceUsesWithIf( - temporary, qubit2, - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); - } - - /** - * @brief Changes the target of a controlled Pauli Z gate if a - * controlled hadamard gate is it successor. - * - * @param op The operation to match (only Pauli gates trigger the rewrite) - * @param rewriter Pattern rewriter for applying transformations - * @return success() if circuit was changed, failure() otherwise - */ - LogicalResult matchAndRewrite(CtrlOp op, - PatternRewriter& rewriter) const override { - // op needs to be a Pauli Z gate and controlled - std::string opName = op.getBodyUnitary()->getName().stripDialect().str(); - if (opName != "z") { - return failure(); - } - - // op needs to be in front of a controlled hadamard gate - auto* const user = *op->getUsers().begin(); - if (user->getName().stripDialect().str() != "ctrl") { - return failure(); - } - auto hadamardGate = llvm::cast(user); - if (hadamardGate.getNumTargets() != 1 || - hadamardGate.getBodyUnitary()->getName().stripDialect().str() != "h") { - return failure(); - } - - const std::vector outputsOp(op.getOutputQubits().begin(), - op.getOutputQubits().end()); - const std::vector inputsH(hadamardGate.getInputQubits().begin(), - hadamardGate.getInputQubits().end()); - if (!containRangesOfSameElements(outputsOp, inputsH)) { - return failure(); - } - - // If the target qubit of H is a ctrl in Z and vice versa, we can move Z's - // target to H's target - if (!areTargetsControlsAtTheOtherGates(op, hadamardGate)) { - return failure(); - } - - // Put the Z target to the same qubit as the hadamard target is - const Value originalInputTargetQubitZ = op.getInputTarget(0); - const Value targetInputQubitHadamard = hadamardGate.getInputTarget(0); - const Value newTargetInputQubitZ = - op.getInputForOutput(targetInputQubitHadamard); - - exchangeTwoQubitsAtGate(op, originalInputTargetQubitZ, newTargetInputQubitZ, - rewriter); - - const Value newTargetInputQubitH = - op.getOutputForInput(newTargetInputQubitZ); - - exchangeTwoQubitsAtGate(hadamardGate, targetInputQubitHadamard, - newTargetInputQubitH, rewriter); - - return success(); - } -}; - /** * @brief This pattern is responsible for lifting Hadamard gates above Pauli * gates. @@ -185,70 +53,44 @@ struct AdaptCtrldPauliZToLiftingPattern final : OpRewritePattern { * This pattern swaps a Pauli gate with a Hadamard gate. This is done using the * commutation rules of Pauli and Hadamard gates, which are: * - X - H - = - H - Z - - * - Y - H - = - H - Y - + * - Y - H - = - H - Y - G(pi) - * - Z - H - = - H - X - - * This is applied to uncontrolled gates and controlled ones, if the controls - * are applied to the same qubits for both gates and the Pauli gates are X or Z. - * In case of Pauli Y, the routine is only applied to uncontrolled gates, as - * HY = -YH, which leads to a relative phase if the Hadamard and Pauli gate are - * controlled. + * This is applied to uncontrolled gates. + * In case of Pauli Y, a global phase is applied, as HY = -YH. */ struct LiftHadamardsAbovePauliGatesPattern final : OpInterfaceRewritePattern { explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) : OpInterfaceRewritePattern(context) {} - /** - * This method checks whether the first and second operations are controlled - * by the same qubits. - * - * @param firstCtrl The first (preceding) controlled gate. - * @param secondCtrl The second (succeeding) controlled gate. - * @return true if the controls are controlled by the same qubits. - */ - static bool areControlsControlledBySameQubits(CtrlOp firstCtrl, - CtrlOp secondCtrl) { - const std::vector controlOutputsFirstGate( - firstCtrl.getControlsOut().begin(), firstCtrl.getControlsOut().end()); - const std::vector controlInputsSecondGate( - secondCtrl.getControlsIn().begin(), secondCtrl.getControlsIn().end()); - return containRangesOfSameElements(controlOutputsFirstGate, - controlInputsSecondGate); - } - /** * @brief This method swaps a Pauli gate with a Hadamard gate. * * This method swaps a Pauli gate with a Hadamard gate. This is done using the * commutation rules of Pauli and Hadamard gates, which are: * - X - H - = - H - Z - - * - Y - H - = - H - Y - + * - Y - H - = - H - Y - gPhase(pi) - * - Z - H - = - H - X - - * A Pauli Y gate is only swapped with the Hadamard gate if they are - * uncontrolled. * * @param gate The Pauli gate. * @param hadamardGate The Hadamard gate. - * @param controlled Whether the gates are controlled or not. * @param rewriter The used rewriter. * @return success() if circuit was changed, failure() otherwise */ static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, HOp hadamardGate, - const bool controlled, PatternRewriter& rewriter) { - const auto gateName = gate->getName().stripDialect().str(); - if ((gateName != "x" && gateName != "y" && gateName != "z") || - (gateName == "y" && controlled)) { + auto op = gate.getOperation(); + if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { return failure(); } rewriter.setInsertionPoint(gate); rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); - if (gateName == "x") { + if (llvm::isa(op)) { rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); - } else if (gateName == "z") { + } else if (llvm::isa(op)) { rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); @@ -256,34 +98,11 @@ struct LiftHadamardsAbovePauliGatesPattern final rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); + // TODO: Add Gphase(pi) } return success(); } - /** - * @brief Swaps controlled Hadamard and Pauli gate if they follow after each - * other and are operated on by the same qubits. - * - * @param firstGate First controlled gate, needs to be a Pauli gate. - * @param secondGate Second controlled gate, needs to be a Hadamard gate. - * @param rewriter Pattern rewriter for applying transformations - * @return success() if circuit was changed, failure() otherwise - */ - static LogicalResult handleTwoSucceedingControls(CtrlOp firstGate, - CtrlOp secondGate, - PatternRewriter& rewriter) { - auto hadamardGate = - llvm::dyn_cast(secondGate.getBodyUnitary().getOperation()); - if (!hadamardGate || firstGate.getNumTargets() != 1 || - secondGate.getNumTargets() != 1 || - firstGate.getOutputTarget(0) != secondGate.getInputTarget(0) || - !areControlsControlledBySameQubits(firstGate, secondGate)) { - return failure(); - } - return swapPauliWithHadamard(firstGate.getBodyUnitary(), hadamardGate, true, - rewriter); - } - /** * @brief Lifts Hadamard gates in front of Pauli gates. * @@ -293,9 +112,8 @@ struct LiftHadamardsAbovePauliGatesPattern final */ LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { - // op needs to be a Pauli gate - const std::string opName = op->getName().stripDialect().str(); - if (opName != "x" && opName != "y" && opName != "z" && opName != "ctrl") { + // op needs to be an uncontrolled Pauli gate + if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { return failure(); } @@ -305,26 +123,14 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } auto* const user = *users.begin(); - const auto userName = user->getName().stripDialect().str(); - if (userName != "h") { - if (opName == "ctrl" && userName == "ctrl") { - return handleTwoSucceedingControls(llvm::dyn_cast(*op), - llvm::dyn_cast(user), - rewriter); - } - return failure(); - } - auto hadamardGate = llvm::dyn_cast(user); - if (!hadamardGate || op.getNumControls() > 0 || - hadamardGate.getNumControls() > 0 || op.getNumTargets() != 1 || - hadamardGate.getNumTargets() != 1 || + if (!hadamardGate || op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { return failure(); } - return swapPauliWithHadamard(op, hadamardGate, false, rewriter); + return swapPauliWithHadamard(op, hadamardGate, rewriter); } }; @@ -527,7 +333,6 @@ struct HadamardLifting final : impl::HadamardLiftingBase { // Define the set of patterns to use. RewritePatternSet patterns(ctx); - patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 9f55573387..178c6ef951 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -213,10 +213,10 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPrecedingPauliGate) { // ################################################## /** - * @brief Test: Checks if Hadamard gates are lifted if they are controlled by - * the same qubit as the lifted gate is. + * @brief Test: Checks if Hadamard gates are not lifted if they are controlled + * by the same qubit as the lifted gate is. */ -TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { +TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliGateIfControlled) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.x(q[0]); auto qubitPair = programBuilder.cx(q[1], q[0]); @@ -226,8 +226,8 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.x(qRef[0]); - auto qubitPairRef = referenceBuilder.ch(qRef[1], qRef[0]); - qubitPairRef = referenceBuilder.cz(qubitPairRef.first, qubitPairRef.second); + auto qubitPairRef = referenceBuilder.cx(qRef[1], qRef[0]); + qubitPairRef = referenceBuilder.ch(qubitPairRef.first, qubitPairRef.second); referenceBuilder.cx(qubitPairRef.first, qubitPairRef.second); reference = referenceBuilder.finalize(); @@ -342,70 +342,6 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } -/** - * @brief Test: Checks that a hadamard gate can be lifted over a controlled - * Pauli Z gate even if the targets are at different places. - */ -TEST_F(QCOHadamardLiftingTest, liftHadamardOverControlledPauliZ) { - auto q = programBuilder.allocQubitRegister(3); - q[0] = programBuilder.s(q[0]); - auto qubitPairRange = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.z(target[0])}; - }); - qubitPairRange = programBuilder.ctrl( - {qubitPairRange.second[0], qubitPairRange.first[1]}, - {qubitPairRange.first[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.h(target[0])}; - }); - q[0] = programBuilder.s(qubitPairRange.first[0]); - auto qubitPair = programBuilder.cz(qubitPairRange.second[0], q[0]); - qubitPairRange = programBuilder.ctrl( - {qubitPairRange.first[1], qubitPair.second}, {qubitPair.first}, - [&](const ValueRange target) { - return SmallVector{programBuilder.h(target[0])}; - }); - qubitPairRange = programBuilder.ctrl( - qubitPairRange.first, qubitPairRange.second, - [&](const ValueRange target) { - return SmallVector{programBuilder.z(target[0])}; - }); - programBuilder.cz(qubitPairRange.second[0], qubitPairRange.first[0]); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(3); - qRef[0] = referenceBuilder.s(qRef[0]); - auto qubitPairRangeRef = referenceBuilder.ctrl( - {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.h(target[0])}; - }); - qubitPairRangeRef = referenceBuilder.ctrl( - qubitPairRangeRef.first, qubitPairRangeRef.second, - [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; - }); - qRef[0] = referenceBuilder.s(qubitPairRangeRef.first[0]); - auto qubitPairRef = referenceBuilder.cz(qubitPairRangeRef.second[0], qRef[0]); - qubitPairRangeRef = referenceBuilder.ctrl( - {qubitPairRangeRef.first[1], qubitPairRef.second}, {qubitPairRef.first}, - [&](const ValueRange target) { - return SmallVector{referenceBuilder.h(target[0])}; - }); - qubitPairRangeRef = referenceBuilder.ctrl( - qubitPairRangeRef.first, qubitPairRangeRef.second, - [&](const ValueRange target) { - return SmallVector{referenceBuilder.z(target[0])}; - }); - referenceBuilder.cz(qubitPairRangeRef.second[0], qubitPairRangeRef.first[0]); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} - // ################################################## // # Raise Hadamard over CNOT gates Tests // ################################################## From de726cbb298408c4bc87158646835459cf4cbbf7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 13:41:50 +0200 Subject: [PATCH 039/131] :construction: Added global phase for Y-H commutation --- .../Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp | 6 +----- .../Transforms/Optimization/test_qco_hadamard_lifting.cpp | 6 +++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 5044a10aee..19676a85b9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -84,21 +84,17 @@ struct LiftHadamardsAbovePauliGatesPattern final if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { return failure(); } - rewriter.setInsertionPoint(gate); rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); if (llvm::isa(op)) { - rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); } else if (llvm::isa(op)) { - rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); } else { - rewriter.setInsertionPoint(hadamardGate); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); - // TODO: Add Gphase(pi) + GPhaseOp::create(rewriter, hadamardGate.getLoc(), M_PI); } return success(); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 178c6ef951..a905204e3d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -78,7 +78,8 @@ class QCOHadamardLiftingTest : public testing::Test { // ################################################## /** - * @brief Test: Hadamards should be lifted over one Pauli gate. + * @brief Test: Hadamards should be lifted over one Pauli gate. A global phase + * should be added for the Pauli-Y gate. */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { auto q = programBuilder.allocQubitRegister(3); @@ -97,6 +98,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { qRef[1] = referenceBuilder.x(qRef[1]); qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); + referenceBuilder.gphase(M_PI); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -161,12 +163,14 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { qRef[1] = referenceBuilder.h(qRef[1]); qRef[1] = referenceBuilder.z(qRef[1]); qRef[1] = referenceBuilder.y(qRef[1]); + referenceBuilder.gphase(M_PI); qRef[1] = referenceBuilder.x(qRef[1]); qRef[2] = referenceBuilder.x(qRef[2]); qRef[2] = referenceBuilder.s(qRef[2]); qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.z(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); + referenceBuilder.gphase(M_PI); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); From 500b0d14c33a5c04808185ffed02f72168b52cea Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 14:01:22 +0200 Subject: [PATCH 040/131] :construction: Added TypeSwitch --- .../Optimization/HadamardLifting.cpp | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 19676a85b9..b30846a00e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -14,9 +14,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -81,22 +83,27 @@ struct LiftHadamardsAbovePauliGatesPattern final HOp hadamardGate, PatternRewriter& rewriter) { auto op = gate.getOperation(); - if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { - return failure(); - } - rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); - if (llvm::isa(op)) { - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - } else if (llvm::isa(op)) { - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - } else { - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - GPhaseOp::create(rewriter, hadamardGate.getLoc(), M_PI); - } - return success(); + return TypeSwitch(op) + .Case([&](auto) { + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + return success(); + }) + .Case([&](auto) { + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + return success(); + }) + .Case([&](auto) { + rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); + rewriter.replaceOpWithNewOp(hadamardGate, + hadamardGate.getInputQubit(0)); + GPhaseOp::create(rewriter, hadamardGate.getLoc(), M_PI); + return success(); + }) + .Default([&](auto) { return failure(); }); } /** From b67a26f88902edb882e672e1ac0c6f02e5cef1fa Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 14:10:37 +0200 Subject: [PATCH 041/131] :memo: Improved documentation --- .../Transforms/Optimization/HadamardLifting.cpp | 14 +++++++++++--- .../Optimization/test_qco_hadamard_lifting.cpp | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index b30846a00e..8fb33f89ad 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -58,7 +58,7 @@ namespace { * - Y - H - = - H - Y - G(pi) - * - Z - H - = - H - X - * This is applied to uncontrolled gates. - * In case of Pauli Y, a global phase is applied, as HY = -YH. + * In case of Pauli-Y, a global phase is applied, as HY = -YH. */ struct LiftHadamardsAbovePauliGatesPattern final : OpInterfaceRewritePattern { @@ -138,12 +138,20 @@ struct LiftHadamardsAbovePauliGatesPattern final }; /** - * @brief This pattern removes an H gate between a CNOT and a measurement. + * @brief This pattern removes an H gate between a CNOT and a measurement, flips + * the CNOT and adds Hadamard gates before and after the new target and before + * the new control. * * If there is a Hadamard gate between the target qubit of a CNOT and a * measurement, we flip the CNOT and apply a hadamard gate to the incoming and * outcoming qubits. As H * H = id, the measurement is then the direct successor - * of a CNOT ctrl, which is beneficial for the qubit reuse routine. + * of a CNOT control, which is beneficial for the qubit reuse routine. After the + * application of LiftHadamardAboveCNOTPattern, a measurement will follow + * directly after a control. In that case, measurement lifting (a routine of + * qubit reuse) can remove the multi-qubit gate by lifting the measurement in + * front of the control and changing the qubit-controlled Pauli-X to a + * classically controlled Pauli-X. + * * The procedure also works if there are additional ctrls. Only the target * and ctrl involved in the transformation get hadamard gates assigned. * The involved ctrl to be flipped with the target is chosen randomly. diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index a905204e3d..8d660984ec 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -244,7 +244,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliGateIfControlled) { /** * @brief Test: Checks that Hadamard gates are not lifted if they are controlled - * and the Pauli gate is a Pauli Y gate. + * and the Pauli gate is a Pauli-Y gate. */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliYGateIfControlled) { auto q = programBuilder.allocQubitRegister(2); From 928f6c510926f79d652b134401083ab37de4759e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 15:23:29 +0200 Subject: [PATCH 042/131] :recycle: Removed temporary value and branchings, assisted-by: GPT 5 via KI:connect --- .../Optimization/HadamardLifting.cpp | 70 +++++++++---------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 8fb33f89ad..b3b7321af3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include #include #include #include @@ -82,7 +83,7 @@ struct LiftHadamardsAbovePauliGatesPattern final static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, HOp hadamardGate, PatternRewriter& rewriter) { - auto op = gate.getOperation(); + auto* op = gate.getOperation(); return TypeSwitch(op) .Case([&](auto) { rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); @@ -161,6 +162,24 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { explicit LiftHadamardAboveCNOTPattern(MLIRContext* context) : OpRewritePattern(context) {} + /** + * @brief This method swaps two operand usages in an operation. + * + * @param op The operation on with the operand usage should be swapped. + * @param a The operand value to be swapped with b. + * @param b The operand value to be swapped with a. + */ + static void swapOperandsInOp(Operation* op, Value a, Value b) { + for (OpOperand& operand : + llvm::make_filter_range(op->getOpOperands(), [&](const OpOperand& o) { + const Value valueOfOperand = o.get(); + return valueOfOperand == a || valueOfOperand == b; + })) { + const bool operandValueIsA = operand.get() == a; + operand.set(operandValueIsA ? b : a); + } + } + /** * @brief This method swaps two qubits on a gate. * @@ -176,44 +195,21 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param rewriter The used rewriter. */ static void swapQubits(UnitaryOpInterface gate, const Value inputQubit1, - const Value inputQubit2, - const Operation* succeedingOp1, - const Operation* succeedingOp2, - PatternRewriter& rewriter) { + const Value inputQubit2, Operation* succeedingOp1, + Operation* succeedingOp2, PatternRewriter& rewriter) { const Value outputQubit1 = gate.getOutputForInput(inputQubit1); const Value outputQubit2 = gate.getOutputForInput(inputQubit2); - const auto temporary = - IdOp::create(rewriter, gate.getLoc(), gate.getInputTarget(0)) - .getResult(); - - rewriter.replaceUsesWithIf(outputQubit1, temporary, - [&](const OpOperand& operand) { - return operand.getOwner() == gate || - operand.getOwner() == succeedingOp1 || - operand.getOwner() == succeedingOp2; - }); - rewriter.replaceUsesWithIf(outputQubit2, outputQubit1, - [&](const OpOperand& operand) { - return operand.getOwner() == gate || - operand.getOwner() == succeedingOp1 || - operand.getOwner() == succeedingOp2; - }); - rewriter.replaceUsesWithIf(temporary, outputQubit2, - [&](const OpOperand& operand) { - return operand.getOwner() == gate || - operand.getOwner() == succeedingOp1 || - operand.getOwner() == succeedingOp2; - }); - - rewriter.replaceUsesWithIf( - inputQubit1, temporary, - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); - rewriter.replaceUsesWithIf( - inputQubit2, inputQubit1, - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); - rewriter.replaceUsesWithIf( - temporary, inputQubit2, - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); + + rewriter.modifyOpInPlace( + gate, [&] { swapOperandsInOp(gate, inputQubit1, inputQubit2); }); + + rewriter.modifyOpInPlace(succeedingOp1, [&] { + swapOperandsInOp(succeedingOp1, outputQubit1, outputQubit2); + }); + + rewriter.modifyOpInPlace(succeedingOp2, [&] { + swapOperandsInOp(succeedingOp2, outputQubit1, outputQubit2); + }); } /** From 9b2397e5e84d7bad1ed72686259a9657d609e5e1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 17:31:25 +0200 Subject: [PATCH 043/131] :construction: Implemented changes requested in review --- .../Optimization/HadamardLifting.cpp | 89 +++++++++---------- .../test_qco_hadamard_lifting.cpp | 8 +- 2 files changed, 47 insertions(+), 50 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index b3b7321af3..a765467c43 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -23,30 +23,13 @@ #include #include -#include #include -#include namespace mlir::qco { #define GEN_PASS_DEF_HADAMARDLIFTING #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -/** - * @brief This method checks if two ranges contain of exactly the same - * elements. - * - * This method checks if two ranges contain of exactly the same elements. - * - * @param range1 The first range. - * @param range2 The second range. - */ -static bool containRangesOfSameElements(const std::vector& range1, - const std::vector& range2) { - return range1.size() == range2.size() && - std::ranges::is_permutation(range1, range2); -} - namespace { /** @@ -121,7 +104,7 @@ struct LiftHadamardsAbovePauliGatesPattern final return failure(); } - // op needs to be in front of a hadamard gate + // op needs to be in front of a Hadamard gate const auto& users = op->getUsers(); if (users.empty()) { return failure(); @@ -213,29 +196,29 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } /** - * @brief This method adds hadamrad gates before a given gate. + * @brief This method adds Hadamard gates before a given gate. * - * @param gate The gate before which hadamard gates should be applied. - * @param inputQubits The input qubits of gate before which hadamard gates + * @param gate The gate before which Hadamard gates should be applied. + * @param inputQubits The input qubits of gate before which Hadamard gates * should be applied. * @param rewriter The used rewriter. - * @returns One of the created hadamard gates. + * @returns One of the created Hadamard gates. */ static HOp addHadamardGatesBeforeGate(const UnitaryOpInterface gate, - const std::vector& inputQubits, + const ValueRange inputQubits, PatternRewriter& rewriter) { HOp newHOp; for (const Value inputQubit : inputQubits) { - std::vector inQubits{inputQubit}; + const ValueRange inQubits(inputQubit); newHOp = HOp::create(rewriter, gate->getLoc(), inQubits); rewriter.moveOpBefore(newHOp, gate); - rewriter.replaceUsesWithIf( - inputQubit, newHOp.getOutputTarget(0), - [&](const OpOperand& operand) { return operand.getOwner() == gate; }); + rewriter.modifyOpInPlace(gate, [&] { + swapOperandsInOp(gate, inputQubit, newHOp.getOutputTarget(0)); + }); } return newHOp; } @@ -247,16 +230,15 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param outputQubits The output qubits of gate after which Hadamard gates * should be applied. * @param rewriter The used rewriter. - * @returns One of the created hadamard gates. + * @returns One of the created Hadamard gates. */ static HOp addHadamardGatesAfterGate(const UnitaryOpInterface gate, - const std::vector& outputQubits, + const ValueRange outputQubits, PatternRewriter& rewriter) { HOp newHOp; for (Value outputQubit : outputQubits) { - std::vector inQubit{outputQubit}; - std::vector outQubit{outputQubit.getType()}; + const ValueRange inQubit(outputQubit); newHOp = HOp::create(rewriter, gate->getLoc(), inQubit); @@ -272,10 +254,12 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } /** - * @brief This pattern remove an H gate between a CNOT and a measurement. + * @brief This pattern removes an H gate between a CNOT and a measurement, + * flips the CNOT and adds Hadamard gates before and after the new target and + * before the new control. * - * @param op The operation to match (only uncontrolled Hadamard gates trigger - * the rewrite) + * @param op The operation to match (only measurements with an uncontrolled + * Hadamard gate before that trigger the rewrite) * @param rewriter Pattern rewriter for applying transformations * @return success() if circuit was changed, failure() otherwise */ @@ -284,9 +268,8 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { // A Hadamard gate needs to be in front of the measurement const auto qubitInMeasurement = op.getQubitIn(); auto* predecessor = qubitInMeasurement.getDefiningOp(); - auto hadamardGate = llvm::dyn_cast(predecessor); - if (!hadamardGate || hadamardGate.getNumTargets() != 1 || - hadamardGate->getName().stripDialect().str() != "h") { + auto hadamardGate = llvm::dyn_cast(predecessor); + if (!hadamardGate) { return failure(); } @@ -295,10 +278,24 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { predecessor = inQubitHadamard.getDefiningOp(); auto cnotGate = llvm::dyn_cast(predecessor); if (!cnotGate || cnotGate.getNumTargets() != 1 || - cnotGate.getBodyUnitary()->getName().stripDialect().str() != "x" || - cnotGate.getOutputTarget(0) != inQubitHadamard) { + cnotGate.getOutputTarget(0) != inQubitHadamard || + !llvm::dyn_cast(cnotGate.getBodyUnitary())) { return failure(); } + // Determine the index of the control that will become the new target. The + // control must not be succeeded by a measurement. + unsigned int controlIndex = 0; + for (unsigned int i = 0; i < cnotGate.getNumControls(); i++) { + if (llvm::dyn_cast( + *cnotGate.getOutputControl(i).getUsers().begin())) { + if (i == cnotGate.getNumControls() - 1) { + return failure(); + } + } else { + controlIndex = i; + break; + } + } // Remove the Hadamard gate for (const auto outQubit : hadamardGate.getOutputQubits()) { @@ -307,19 +304,19 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } rewriter.eraseOp(hadamardGate); - // Add Hadamard gates to the other in and output gates of cnot - const std::vector relevantInputQubitsForHadamard{ - cnotGate.getInputTarget(0), cnotGate.getInputControl(0)}; + // Add Hadamard gates to the other in- and output gates of CNOT + const ValueRange relevantInputQubitsForHadamard( + {cnotGate.getInputTarget(0), cnotGate.getInputControl(controlIndex)}); addHadamardGatesBeforeGate(cnotGate, relevantInputQubitsForHadamard, rewriter); - const std::vector relevantOutputQubitsForHadamard{ - cnotGate.getOutputForInput(cnotGate.getInputControl(0))}; const HOp newHOPAfterCtrl = addHadamardGatesAfterGate( - cnotGate, relevantOutputQubitsForHadamard, rewriter); + cnotGate, + cnotGate.getOutputForInput(cnotGate.getInputControl(controlIndex)), + rewriter); // Flip CNOT targets and ctrl - swapQubits(cnotGate, cnotGate.getInputControl(0), + swapQubits(cnotGate, cnotGate.getInputControl(controlIndex), cnotGate.getInputTarget(0), op, newHOPAfterCtrl, rewriter); return success(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 8d660984ec..46d72df08b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -326,7 +326,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { auto q = programBuilder.allocQubitRegister(3); auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.z(target[0])}; + return SmallVector{programBuilder.z(target[0])}; }); programBuilder.ch(q12[0], q0[0]); module = programBuilder.finalize(); @@ -334,7 +334,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { auto qRef = referenceBuilder.allocQubitRegister(3); auto [q12Ref, q0Ref] = referenceBuilder.ctrl( {qRef[1], qRef[2]}, {qRef[0]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.z(target[0])}; + return SmallVector{referenceBuilder.z(target[0])}; }); referenceBuilder.ch(q12Ref[0], q0Ref[0]); reference = referenceBuilder.finalize(); @@ -389,7 +389,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { const auto b = programBuilder.allocClassicalBitRegister(1); auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; + return SmallVector{programBuilder.x(target[0])}; }); q[1] = programBuilder.h(q0[0]); programBuilder.measure(q[1], b[0]); @@ -401,7 +401,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { qRef[1] = referenceBuilder.h(qRef[1]); auto [q02Ref, q1Ref] = referenceBuilder.ctrl( {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; + return SmallVector{referenceBuilder.x(target[0])}; }); referenceBuilder.h(q1Ref[0]); referenceBuilder.measure(q02Ref[0], bRef[0]); From 20b6580816e9d6332ad680c2fefa174ebbfbc152 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 18:21:43 +0200 Subject: [PATCH 044/131] :white_check_mark: Added test for measurement after target of CNOT --- .../test_qco_hadamard_lifting.cpp | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 46d72df08b..1b8b0e916b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -451,3 +451,50 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverCNOTGate) { EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } + +/** + * @brief Test: Checks that a Hadamard gate is not lifted over a CNOT gate + * target if a measurement is following directly after the controls. + */ +TEST_F(QCOHadamardLiftingTest, + doNotLiftHadamardOverCNOTIfMeasurementsAfterControlsGate) { + auto q = programBuilder.allocQubitRegister(5); + const auto b = programBuilder.allocClassicalBitRegister(4); + auto [q1, q0] = programBuilder.cx(q[1], q[0]); + q[0] = programBuilder.h(q0); + programBuilder.measure(q[0], b[0]); + programBuilder.measure(q1, b[1]); + auto [q34, q2] = + programBuilder.ctrl({q[3], q[4]}, {q[2]}, [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + q[2] = programBuilder.h(q2[0]); + programBuilder.measure(q[2], b[2]); + programBuilder.measure(q34[0], b[3]); + programBuilder.s(q34[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(5); + const auto bRef = referenceBuilder.allocClassicalBitRegister(4); + auto [qRef1, qRef0] = referenceBuilder.cx(qRef[1], qRef[0]); + qRef[0] = referenceBuilder.h(qRef0); + referenceBuilder.measure(qRef[0], bRef[0]); + referenceBuilder.measure(qRef1, bRef[1]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[4] = referenceBuilder.h(qRef[4]); + auto [qRef32, qRef4] = referenceBuilder.ctrl( + {qRef[3], qRef[2]}, {qRef[4]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + qRef[4] = referenceBuilder.h(qRef4[0]); + referenceBuilder.measure(qRef32[1], bRef[2]); + referenceBuilder.measure(qRef32[0], bRef[3]); + referenceBuilder.s(qRef[4]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} From 3b430d464f7d9eecfb81004ba00b90a55281b4be Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 09:58:42 +0200 Subject: [PATCH 045/131] :construction: Implemented changes requested in review --- .../Optimization/HadamardLifting.cpp | 21 +++++++------------ .../test_qco_hadamard_lifting.cpp | 2 ++ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index a765467c43..a484487764 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -15,14 +15,14 @@ #include #include #include -#include #include #include #include +#include #include #include -#include +#include #include namespace mlir::qco { @@ -105,12 +105,7 @@ struct LiftHadamardsAbovePauliGatesPattern final } // op needs to be in front of a Hadamard gate - const auto& users = op->getUsers(); - if (users.empty()) { - return failure(); - } - auto* const user = *users.begin(); - auto hadamardGate = llvm::dyn_cast(user); + auto hadamardGate = llvm::dyn_cast(*op->getUsers().begin()); if (!hadamardGate || op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { @@ -152,7 +147,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param a The operand value to be swapped with b. * @param b The operand value to be swapped with a. */ - static void swapOperandsInOp(Operation* op, Value a, Value b) { + static void swapOperandsInOp(Operation* op, const Value a, const Value b) { for (OpOperand& operand : llvm::make_filter_range(op->getOpOperands(), [&](const OpOperand& o) { const Value valueOfOperand = o.get(); @@ -177,7 +172,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * output of inputQubit2. * @param rewriter The used rewriter. */ - static void swapQubits(UnitaryOpInterface gate, const Value inputQubit1, + static void swapQubits(CtrlOp gate, const Value inputQubit1, const Value inputQubit2, Operation* succeedingOp1, Operation* succeedingOp2, PatternRewriter& rewriter) { const Value outputQubit1 = gate.getOutputForInput(inputQubit1); @@ -204,7 +199,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param rewriter The used rewriter. * @returns One of the created Hadamard gates. */ - static HOp addHadamardGatesBeforeGate(const UnitaryOpInterface gate, + static HOp addHadamardGatesBeforeGate(const CtrlOp gate, const ValueRange inputQubits, PatternRewriter& rewriter) { HOp newHOp; @@ -232,7 +227,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { * @param rewriter The used rewriter. * @returns One of the created Hadamard gates. */ - static HOp addHadamardGatesAfterGate(const UnitaryOpInterface gate, + static HOp addHadamardGatesAfterGate(const CtrlOp gate, const ValueRange outputQubits, PatternRewriter& rewriter) { HOp newHOp; @@ -279,7 +274,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { auto cnotGate = llvm::dyn_cast(predecessor); if (!cnotGate || cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || - !llvm::dyn_cast(cnotGate.getBodyUnitary())) { + llvm::dyn_cast(cnotGate.getBodyUnitary()) == nullptr) { return failure(); } // Determine the index of the control that will become the new target. The diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 1b8b0e916b..926331e09a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -25,6 +25,8 @@ #include #include +#include + namespace { using namespace mlir; From f3cb1d7c9b2f5bf3528de1c0549a7f69ee8d1079 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:20:27 +0000 Subject: [PATCH 046/131] =?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 --- cmake/ExternalDependencies.cmake | 2 +- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 15d37468fa..2d17ff04d5 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -41,7 +41,7 @@ if(BUILD_MQT_CORE_MLIR) FetchContent_Declare( jeff-mlir GIT_REPOSITORY https://github.com/PennyLaneAI/jeff-mlir.git - GIT_TAG v0.1.0) + GIT_TAG v0.1.0) list(APPEND FETCH_PACKAGES jeff-mlir) endif() diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 8ac78fee0c..348e6e75f9 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,10 +78,15 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by commuting them " - "with Pauli gates. This is done to in order to apply measurement lifting more efficiently, which is a sub-routine of " - "qubit resuse. Additionally, it can change target and control qubits from Pauli-Z gates to make Hadamard lifting " - "applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement directly after a control."; + let summary = + "This pass attempts to move Hadamard gates as far away from measurements " + "as possible by commuting them " + "with Pauli gates. This is done to in order to apply measurement lifting " + "more efficiently, which is a sub-routine of " + "qubit resuse. Additionally, it can change target and control qubits " + "from Pauli-Z gates to make Hadamard lifting " + "applicable. It also lifts Hadamard gates over CNOT gates if that moves " + "a measurement directly after a control."; let description = [{ This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. Measurement lifting is a subroutine of the qubit reuse routine. The goal is to measure qubits earlier in the From e5ba050cace85b10c0e96583a551e9e4662e7465 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 10:24:12 +0200 Subject: [PATCH 047/131] :pencil2: Fixed typos --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 8ac78fee0c..2db2f82a50 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -79,15 +79,15 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by commuting them " - "with Pauli gates. This is done to in order to apply measurement lifting more efficiently, which is a sub-routine of " - "qubit resuse. Additionally, it can change target and control qubits from Pauli-Z gates to make Hadamard lifting " + "with Pauli gates. This is done in order to apply measurement lifting more efficiently, which is a sub-routine of " + "qubit reuse. Additionally, it can change target and control qubits from Pauli-Z gates to make Hadamard lifting " "applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement directly after a control."; let description = [{ This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. Measurement lifting is a subroutine of the qubit reuse routine. The goal is to measure qubits earlier in the circuit to reuse them and to potentially remove some quantum gates. - Measurement lifting commutes measurements with Pauli and phase gates. However, the routine stops if a Hadmard gate + Measurement lifting commutes measurements with Pauli and phase gates. However, the routine stops if a Hadamard gate is applied before the measurement takes place. Hadamard lifting attempts to move Hadamards further in front of the circuit, in order to improve the results provided by measurement lifting. @@ -104,7 +104,7 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { Hadamard lifting is only applied to commute Hadamard gates further in front in the circuit, not the other way around. A global phase is added if the Hadamard gate is commuted with a Pauli-Y gate, as YH = -HY. - The routine is only applied to non-controlled Pauli- and Hadamard gates. + The routine is only applied to non-controlled Pauli and Hadamard gates. In order to reduce multi-qubit gates, a second transformation routine is applied. Using the commutation rule ┌───┐┌───┐┌───┐ From 44d8c98d80a707935f1b478e28b94f182ea36d3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 08:27:29 +0000 Subject: [PATCH 048/131] =?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/QCO/Transforms/Passes.td | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 2db2f82a50..2a15215dee 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -78,10 +78,14 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass attempts to move Hadamard gates as far away from measurements as possible by commuting them " - "with Pauli gates. This is done in order to apply measurement lifting more efficiently, which is a sub-routine of " - "qubit reuse. Additionally, it can change target and control qubits from Pauli-Z gates to make Hadamard lifting " - "applicable. It also lifts Hadamard gates over CNOT gates if that moves a measurement directly after a control."; + let summary = "This pass attempts to move Hadamard gates as far away from " + "measurements as possible by commuting them " + "with Pauli gates. This is done in order to apply measurement " + "lifting more efficiently, which is a sub-routine of " + "qubit reuse. Additionally, it can change target and control " + "qubits from Pauli-Z gates to make Hadamard lifting " + "applicable. It also lifts Hadamard gates over CNOT gates if " + "that moves a measurement directly after a control."; let description = [{ This pass lifts Hadamard gates away from the measurements in order to apply measurement lifting more effectively. Measurement lifting is a subroutine of the qubit reuse routine. The goal is to measure qubits earlier in the From 1ca77fa29031c37eced7e00b04a3f8f4fac6d617 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 10:54:42 +0200 Subject: [PATCH 049/131] :rotating_light: Fix linter errors --- .../QCO/Transforms/Optimization/HadamardLifting.cpp | 5 ++--- mlir/tools/mqt-cc/mqt-cc.cpp | 9 ++++----- .../Optimization/test_qco_hadamard_lifting.cpp | 8 +++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index a484487764..eda8da28e1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -22,7 +22,6 @@ #include #include -#include #include namespace mlir::qco { @@ -84,7 +83,7 @@ struct LiftHadamardsAbovePauliGatesPattern final rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); rewriter.replaceOpWithNewOp(hadamardGate, hadamardGate.getInputQubit(0)); - GPhaseOp::create(rewriter, hadamardGate.getLoc(), M_PI); + GPhaseOp::create(rewriter, hadamardGate.getLoc(), std::numbers::pi); return success(); }) .Default([&](auto) { return failure(); }); @@ -274,7 +273,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { auto cnotGate = llvm::dyn_cast(predecessor); if (!cnotGate || cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || - llvm::dyn_cast(cnotGate.getBodyUnitary()) == nullptr) { + nullptr == llvm::dyn_cast(cnotGate.getBodyUnitary())) { return failure(); } // Determine the index of the control that will become the new target. The diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 9da4d442c4..1e96b4ef08 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -71,10 +71,9 @@ static cl::opt cl::desc("Print IR after each compiler stage"), cl::init(false)); -static cl::opt - hadamardLifting("hadamard-lifting", - cl::desc("Apply Hadamard lifting during optimization"), - cl::init(false)); +static cl::opt enableHadamardLifting( + "hadamard-lifting", cl::desc("Apply Hadamard lifting during optimization"), + cl::init(false)); /** * @brief Load and parse a .qasm file @@ -170,7 +169,7 @@ int main(int argc, char** argv) { config.enableTiming = enableTiming; config.enableStatistics = enableStatistics; config.printIRAfterAllStages = printIRAfterAllStages; - config.hadamardLifting = hadamardLifting; + config.enableHadamardLifting = enableHadamardLifting; // Run the compilation pipeline CompilationRecord record; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 926331e09a..7bdc50d33a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -25,8 +25,6 @@ #include #include -#include - namespace { using namespace mlir; @@ -100,7 +98,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { qRef[1] = referenceBuilder.x(qRef[1]); qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); - referenceBuilder.gphase(M_PI); + referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); @@ -165,14 +163,14 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultiplePauliGate) { qRef[1] = referenceBuilder.h(qRef[1]); qRef[1] = referenceBuilder.z(qRef[1]); qRef[1] = referenceBuilder.y(qRef[1]); - referenceBuilder.gphase(M_PI); + referenceBuilder.gphase(std::numbers::pi); qRef[1] = referenceBuilder.x(qRef[1]); qRef[2] = referenceBuilder.x(qRef[2]); qRef[2] = referenceBuilder.s(qRef[2]); qRef[2] = referenceBuilder.h(qRef[2]); qRef[2] = referenceBuilder.z(qRef[2]); qRef[2] = referenceBuilder.y(qRef[2]); - referenceBuilder.gphase(M_PI); + referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); From 10b21ed61ec90dd945dca17a3956dcda352ce514 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 13:14:24 +0200 Subject: [PATCH 050/131] :rotating_light: Fix linter errors --- .../Transforms/Optimization/HadamardLifting.cpp | 1 + .../Optimization/test_qco_hadamard_lifting.cpp | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index eda8da28e1..0fa6c84ade 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace mlir::qco { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 7bdc50d33a..a747c9bc7c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -25,6 +25,8 @@ #include #include +#include + namespace { using namespace mlir; @@ -326,7 +328,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { auto q = programBuilder.allocQubitRegister(3); auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.z(target[0])}; + return llvm::SmallVector{programBuilder.z(target[0])}; }); programBuilder.ch(q12[0], q0[0]); module = programBuilder.finalize(); @@ -334,7 +336,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { auto qRef = referenceBuilder.allocQubitRegister(3); auto [q12Ref, q0Ref] = referenceBuilder.ctrl( {qRef[1], qRef[2]}, {qRef[0]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.z(target[0])}; + return llvm::SmallVector{referenceBuilder.z(target[0])}; }); referenceBuilder.ch(q12Ref[0], q0Ref[0]); reference = referenceBuilder.finalize(); @@ -389,7 +391,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { const auto b = programBuilder.allocClassicalBitRegister(1); auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; + return llvm::SmallVector{programBuilder.x(target[0])}; }); q[1] = programBuilder.h(q0[0]); programBuilder.measure(q[1], b[0]); @@ -401,7 +403,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { qRef[1] = referenceBuilder.h(qRef[1]); auto [q02Ref, q1Ref] = referenceBuilder.ctrl( {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; + return llvm::SmallVector{referenceBuilder.x(target[0])}; }); referenceBuilder.h(q1Ref[0]); referenceBuilder.measure(q02Ref[0], bRef[0]); @@ -466,7 +468,7 @@ TEST_F(QCOHadamardLiftingTest, programBuilder.measure(q1, b[1]); auto [q34, q2] = programBuilder.ctrl({q[3], q[4]}, {q[2]}, [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; + return llvm::SmallVector{programBuilder.x(target[0])}; }); q[2] = programBuilder.h(q2[0]); programBuilder.measure(q[2], b[2]); @@ -484,7 +486,7 @@ TEST_F(QCOHadamardLiftingTest, qRef[4] = referenceBuilder.h(qRef[4]); auto [qRef32, qRef4] = referenceBuilder.ctrl( {qRef[3], qRef[2]}, {qRef[4]}, [&](const ValueRange target) { - return SmallVector{referenceBuilder.x(target[0])}; + return llvm::SmallVector{referenceBuilder.x(target[0])}; }); qRef[4] = referenceBuilder.h(qRef4[0]); referenceBuilder.measure(qRef32[1], bRef[2]); From f183f631078e5af5cc2d9061d01d7b7253f8ba35 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:07:33 +0200 Subject: [PATCH 051/131] :rotating_light: Fix linter errors --- .../Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 0fa6c84ade..513708b6c8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -272,7 +272,10 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { const auto inQubitHadamard = hadamardGate.getInputQubit(0); predecessor = inQubitHadamard.getDefiningOp(); auto cnotGate = llvm::dyn_cast(predecessor); - if (!cnotGate || cnotGate.getNumTargets() != 1 || + if (!cnotGate) { + return failure(); + } + if (cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || nullptr == llvm::dyn_cast(cnotGate.getBodyUnitary())) { return failure(); From 89da08c4fccfa03db22fe0a8331f853ab3c8bf43 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:17:05 +0200 Subject: [PATCH 052/131] :rotating_light: Fix linter errors --- .../lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 513708b6c8..25c5401bb3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -277,7 +277,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } if (cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || - nullptr == llvm::dyn_cast(cnotGate.getBodyUnitary())) { + llvm::dyn_cast(cnotGate.getBodyUnitary())) { return failure(); } // Determine the index of the control that will become the new target. The From 2f0dbe64208c68b9633487b4782f67f2c2a1ff18 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:24:27 +0200 Subject: [PATCH 053/131] :rotating_light: Fix linter errors --- .../lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 25c5401bb3..fe635deaf4 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -277,7 +277,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } if (cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || - llvm::dyn_cast(cnotGate.getBodyUnitary())) { + !llvm::isa(cnotGate.getBodyUnitary())) { return failure(); } // Determine the index of the control that will become the new target. The From 31995a864f4a5998345af147148bece902f9d402 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 20 Apr 2026 08:43:32 +0200 Subject: [PATCH 054/131] :bug: Fix dangling range, assisted-by: GPT 5 via KI:connect --- .../QCO/Transforms/Optimization/HadamardLifting.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index fe635deaf4..9d8270aa63 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -303,10 +303,10 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { rewriter.eraseOp(hadamardGate); // Add Hadamard gates to the other in- and output gates of CNOT - const ValueRange relevantInputQubitsForHadamard( - {cnotGate.getInputTarget(0), cnotGate.getInputControl(controlIndex)}); - addHadamardGatesBeforeGate(cnotGate, relevantInputQubitsForHadamard, - rewriter); + addHadamardGatesBeforeGate( + cnotGate, + {cnotGate.getInputTarget(0), cnotGate.getInputControl(controlIndex)}, + rewriter); const HOp newHOPAfterCtrl = addHadamardGatesAfterGate( cnotGate, From 37505130712663cc1350ba79e6bf8d628ba84184 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 20 Apr 2026 08:58:30 +0200 Subject: [PATCH 055/131] :white_check_mark: Adapated tests to removal of controlled cases --- .../test_qco_hadamard_lifting.cpp | 92 ++----------------- 1 file changed, 7 insertions(+), 85 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index a747c9bc7c..4e7c4bd644 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp @@ -80,8 +80,8 @@ class QCOHadamardLiftingTest : public testing::Test { // ################################################## /** - * @brief Test: Hadamards should be lifted over one Pauli gate. A global phase - * should be added for the Pauli-Y gate. + * @brief Test: Hadamard gates should be lifted over one Pauli gate. A global + * phase should be added for the Pauli-Y gate. */ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { auto q = programBuilder.allocQubitRegister(3); @@ -111,7 +111,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGate) { } /** - * @brief Test: Pauli gates should not be lifted over Hadamards. + * @brief Test: Pauli gates should not be lifted over Hadamard gates. */ TEST_F(QCOHadamardLiftingTest, doNotLiftPauliOverHadamardGate) { auto q = programBuilder.allocQubitRegister(3); @@ -214,9 +214,9 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOnlyOverPrecedingPauliGate) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } -// ################################################## -// # Raise Hadamard over controlled Pauli gate Tests -// ################################################## +// ############################################################# +// # Do not raise Hadamard over controlled Pauli gate Tests +// ############################################################# /** * @brief Test: Checks if Hadamard gates are not lifted if they are controlled @@ -245,33 +245,7 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliGateIfControlled) { } /** - * @brief Test: Checks that Hadamard gates are not lifted if they are controlled - * and the Pauli gate is a Pauli-Y gate. - */ -TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliYGateIfControlled) { - auto q = programBuilder.allocQubitRegister(2); - q[0] = programBuilder.y(q[0]); - auto qubitPair = programBuilder.cy(q[1], q[0]); - qubitPair = programBuilder.ch(qubitPair.first, qubitPair.second); - programBuilder.cy(qubitPair.first, qubitPair.second); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(2); - qRef[0] = referenceBuilder.y(qRef[0]); - auto qubitPairRef = referenceBuilder.cy(qRef[1], qRef[0]); - qubitPairRef = referenceBuilder.ch(qubitPairRef.first, qubitPairRef.second); - referenceBuilder.cy(qubitPairRef.first, qubitPairRef.second); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} - -/** - * @brief Test: Checks that a hadamard gate is not lifted if they are controlled + * @brief Test: Checks that a Hadamard gate is not lifted if they are controlled * by a different qubit than the one lifted gate is. */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { @@ -296,58 +270,6 @@ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } -/** - * @brief Test: Checks that a Hadamard gate is not lifted if there is another - * gate between the controls of the Pauli and the Hadamard gate. - */ -TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfGateBetweenControls) { - auto q = programBuilder.allocQubitRegister(2); - auto [q1, q2] = programBuilder.cz(q[1], q[0]); - q[1] = programBuilder.s(q1); - programBuilder.ch(q[1], q2); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(2); - auto [q1Ref, q2Ref] = referenceBuilder.cz(qRef[1], qRef[0]); - qRef[1] = referenceBuilder.s(q1Ref); - referenceBuilder.ch(qRef[1], q2Ref); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} - -/** - * @brief Test: Checks that a hadamard gate is not lifted if they do not share - * all controls with the Pauli gate. - */ -TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfSomeDifferentControls) { - auto q = programBuilder.allocQubitRegister(3); - auto [q12, q0] = - programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return llvm::SmallVector{programBuilder.z(target[0])}; - }); - programBuilder.ch(q12[0], q0[0]); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(3); - auto [q12Ref, q0Ref] = referenceBuilder.ctrl( - {qRef[1], qRef[2]}, {qRef[0]}, [&](const ValueRange target) { - return llvm::SmallVector{referenceBuilder.z(target[0])}; - }); - referenceBuilder.ch(q12Ref[0], q0Ref[0]); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} - // ################################################## // # Raise Hadamard over CNOT gates Tests // ################################################## From 0da58cf1579614e992f47c5502ccc4b8a2d04ebc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 20 Apr 2026 09:04:58 +0200 Subject: [PATCH 056/131] :bug: Fixed dangling hadamard gate object --- .../Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp index 9d8270aa63..f75527c89a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp @@ -82,9 +82,9 @@ struct LiftHadamardsAbovePauliGatesPattern final }) .Case([&](auto) { rewriter.replaceOpWithNewOp(gate, gate.getInputQubit(0)); - rewriter.replaceOpWithNewOp(hadamardGate, - hadamardGate.getInputQubit(0)); - GPhaseOp::create(rewriter, hadamardGate.getLoc(), std::numbers::pi); + auto yGate = rewriter.replaceOpWithNewOp( + hadamardGate, hadamardGate.getInputQubit(0)); + GPhaseOp::create(rewriter, yGate.getLoc(), std::numbers::pi); return success(); }) .Default([&](auto) { return failure(); }); From fce739bed5c84e35063ceb585c2fd40bf213c736 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 07:47:33 +0000 Subject: [PATCH 057/131] =?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 --- .pre-commit-config.yaml | 10 +++++----- CHANGELOG.md | 11 ----------- UPGRADING.md | 1 - mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt | 4 ++-- .../Dialect/QCO/Transforms/Mapping/CMakeLists.txt | 4 ++-- .../QCO/Transforms/Optimizations/CMakeLists.txt | 9 +++++---- 6 files changed, 14 insertions(+), 25 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a51b721a7e..34ea1c086d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: rev: 2026.04.04 hooks: - id: sp-repo-review - additional_dependencies: [ "repo-review[cli]" ] + additional_dependencies: ["repo-review[cli]"] priority: 0 ## Check pyproject.toml file @@ -105,7 +105,7 @@ repos: rev: v3.8.2 hooks: - id: prettier - types_or: [ yaml, markdown, html, css, scss, javascript, json, json5 ] + types_or: [yaml, markdown, html, css, scss, javascript, json, json5] priority: 5 ## Format CMake files with cmake-format @@ -123,11 +123,11 @@ repos: rev: v22.1.3 hooks: - id: clang-format - types_or: [ c++, c, cuda ] + types_or: [c++, c, cuda] priority: 5 - id: clang-format name: clang-format (TableGen) - types_or: [ file ] + types_or: [file] files: \.td$ priority: 5 @@ -136,7 +136,7 @@ repos: rev: v0.15.10 hooks: - id: ruff-format - types_or: [ python, pyi, jupyter, markdown ] + types_or: [python, pyi, jupyter, markdown] priority: 6 - id: ruff-check require_serial: true diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd47c2acf..b1c2117fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -338,7 +338,6 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.5.0...HEAD - [3.5.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.0 [3.4.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.4.1 [3.4.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.4.0 @@ -357,15 +356,10 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 - [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 - [#1635]: https://github.com/munich-quantum-toolkit/core/pull/1635 - [#1627]: https://github.com/munich-quantum-toolkit/core/pull/1627 - [#1626]: https://github.com/munich-quantum-toolkit/core/pull/1626 - [#1624]: https://github.com/munich-quantum-toolkit/core/pull/1624 [#1623]: https://github.com/munich-quantum-toolkit/core/pull/1623 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 @@ -391,7 +385,6 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1547]: https://github.com/munich-quantum-toolkit/core/pull/1547 [#1542]: https://github.com/munich-quantum-toolkit/core/pull/1542 [#1537]: https://github.com/munich-quantum-toolkit/core/pull/1537 - [#1528]: https://github.com/munich-quantum-toolkit/core/pull/1528 [#1521]: https://github.com/munich-quantum-toolkit/core/pull/1521 [#1513]: https://github.com/munich-quantum-toolkit/core/pull/1513 @@ -423,7 +416,6 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1413]: https://github.com/munich-quantum-toolkit/core/pull/1413 [#1412]: https://github.com/munich-quantum-toolkit/core/pull/1412 [#1411]: https://github.com/munich-quantum-toolkit/core/pull/1411 - [#1407]: https://github.com/munich-quantum-toolkit/core/pull/1407 [#1406]: https://github.com/munich-quantum-toolkit/core/pull/1406 [#1403]: https://github.com/munich-quantum-toolkit/core/pull/1403 @@ -433,7 +425,6 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1383]: https://github.com/munich-quantum-toolkit/core/pull/1383 [#1382]: https://github.com/munich-quantum-toolkit/core/pull/1382 [#1381]: https://github.com/munich-quantum-toolkit/core/pull/1381 - [#1380]: https://github.com/munich-quantum-toolkit/core/pull/1380 [#1378]: https://github.com/munich-quantum-toolkit/core/pull/1378 [#1375]: https://github.com/munich-quantum-toolkit/core/pull/1375 @@ -583,9 +574,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [**@lirem101**]: https://github.com/lirem101 [**@Ectras**]: https://github.com/Ectras [**@simon1hofmann**]: https://github.com/simon1hofmann - [**@keefehuang**]: https://github.com/keefehuang - [**@J4MMlE**]: https://github.com/J4MMlE diff --git a/UPGRADING.md b/UPGRADING.md index 19bb6a65c4..916e61ccca 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -229,7 +229,6 @@ It also requires the `uv` library version 0.5.20 or higher. [unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.5.0...HEAD - [3.5.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.4.0...v3.5.0 [3.4.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.3.0...v3.4.0 [3.3.0]: https://github.com/munich-quantum-toolkit/core/compare/v3.2.0...v3.3.0 diff --git a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt index 821bfd0177..fc6ee74b9d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/QCO/Transforms/CMakeLists.txt @@ -15,8 +15,8 @@ add_mlir_library( PRIVATE MLIRQCODialect MLIRQCOUtils - MLIRArithDialect - MLIRMathDialect + MLIRArithDialect + MLIRMathDialect DEPENDS MLIRQCOTransformsIncGen) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt index c432a4d7d1..405159075a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Mapping/CMakeLists.txt @@ -11,8 +11,8 @@ add_executable(${target_name} test_mapping.cpp) target_link_libraries( ${target_name} - PRIVATE GTest::gtest_main - MLIRParser + PRIVATE GTest::gtest_main + MLIRParser MLIRQCProgramBuilder MLIRQCOProgramBuilder MLIRQCOUtils diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index ab4751371e..b785e5a400 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -7,7 +7,8 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-optimizations) -add_executable(${target_name} test_qco_merge_single_qubit_rotation.cpp test_qco_hadamard_lifting.cpp) +add_executable(${target_name} test_qco_hadamard_lifting.cpp + test_qco_merge_single_qubit_rotation.cpp) target_link_libraries( ${target_name} @@ -15,12 +16,12 @@ target_link_libraries( MLIRQCOProgramBuilder MLIRQCOTransforms MLIRQCOUtils - MLIRParser + MLIRParser MLIRIR MLIRPass MLIRSupport - LLVMSupport - MLIRSupportMQT) + LLVMSupport + MLIRSupportMQT) mqt_mlir_configure_unittest_target(${target_name}) From 7f1256b02b272f0facd0f594c45df01274ad5cc8 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:13:09 +0200 Subject: [PATCH 058/131] Simplify diff --- CHANGELOG.md | 11 +++++------ UPGRADING.md | 3 +-- docs/mlir/index.md | 6 ++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4738b6633d..6bf2676983 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,9 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**]) -- ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407]) ([ - **@J4MMlE**]) +- ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407]) ([**@J4MMlE**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637]) ([**@denialhaag**]) -- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588]) ([**@MatthiasReumann**]) -- ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1664]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475], [#1506], [#1510], [#1513], [#1521], [#1542], [#1548], [#1550], [#1554], [#1567], [#1569], [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], [#1626], [#1627], [#1635], [#1673]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) @@ -53,8 +51,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#350)._ - 📦 Switch to component-based installation for the MQT Core Python package ([#1596]) ([**@burgholzer**]) - ⬆️ Update QDMI to latest version from stable `v1.2.x` branch ([#1593]) ([**@burgholzer**]) - ⬆️ Update `clang-tidy` to version 22 ([#1564]) ([**@denialhaag**], [**@burgholzer**]) -- 👷 Build on `macos-26`/`macos-26-intel` by default and `macos-15`/`macos-15-intel` for extensive tests ([#1571]) ([* - *@denialhaag**]) +- 👷 Build on `macos-26`/`macos-26-intel` by default and `macos-15`/`macos-15-intel` for extensive tests ([#1571]) ([**@denialhaag**]) ## [3.4.1] - 2026-02-01 @@ -375,6 +372,8 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1626]: https://github.com/munich-quantum-toolkit/core/pull/1626 [#1624]: https://github.com/munich-quantum-toolkit/core/pull/1624 [#1623]: https://github.com/munich-quantum-toolkit/core/pull/1623 +[#1620]: https://github.com/munich-quantum-toolkit/core/pull/1620 +[#1605]: https://github.com/munich-quantum-toolkit/core/pull/1605 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 [#1593]: https://github.com/munich-quantum-toolkit/core/pull/1593 diff --git a/UPGRADING.md b/UPGRADING.md index 0b63dd08ee..b1182b0b35 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -49,8 +49,7 @@ Anyone relying on an installed version of `mqt-core` shall update from `3.5.0` t ## [3.5.0] The shared library ABI version (`SOVERSION`) is increased from `3.4` to `3.5`. -Thus, consuming libraries need to update their wheel repair configuration for `cibuildwheel` to ensure the `mqt-core` -libraries are properly skipped in the wheel repair step. +Thus, consuming libraries need to update their wheel repair configuration for `cibuildwheel` to ensure the `mqt-core` libraries are properly skipped in the wheel repair step. ### `nanobind` updated to version 2.12.0 diff --git a/docs/mlir/index.md b/docs/mlir/index.md index 2fdee142e5..fddf27e39b 100644 --- a/docs/mlir/index.md +++ b/docs/mlir/index.md @@ -1,7 +1,6 @@ # The MQT Compiler Collection -The MQT Compiler Collection (`mqt-cc`) is a blueprint for a future-proof quantum-classical compilation framework built -on the Multi-Level Intermediate Representation (MLIR). +The MQT Compiler Collection (`mqt-cc`) is a blueprint for a future-proof quantum-classical compilation framework built on the Multi-Level Intermediate Representation (MLIR). For an overview, see {cite:p}`MQTCompilerCollection2026`. This page gives a technical introduction. @@ -13,8 +12,7 @@ We define multiple dialects, each with its dedicated purpose: - The {doc}`QCO dialect ` uses value semantics and is mainly designed for running optimizations. - The {doc}`QTensor dialect ` adds support for one-dimensional tensors of qubits with linear typing and is used in the QCO dialect to represent collections of qubits such as registers. -These dialects define various canonicalization and transformation passes that enable the compilation of quantum programs -to native quantum hardware. +These dialects define various canonicalization and transformation passes that enable the compilation of quantum programs to native quantum hardware. For interoperability, we provide {doc}`conversions ` between dialects. ```{toctree} From 3084ec4598819d9e21cc12c7875a3628fb5e4a15 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:16:16 +0200 Subject: [PATCH 059/131] Adapt to new include style --- .../Transforms/Optimizations/HadamardLifting.cpp | 13 ++++++------- .../Optimizations/test_qco_hadamard_lifting.cpp | 10 +++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp index f75527c89a..fe82db2fc7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -100,12 +99,12 @@ struct LiftHadamardsAbovePauliGatesPattern final LogicalResult matchAndRewrite(UnitaryOpInterface op, PatternRewriter& rewriter) const override { // op needs to be an uncontrolled Pauli gate - if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { + if (!isa(op) && !isa(op) && !isa(op)) { return failure(); } // op needs to be in front of a Hadamard gate - auto hadamardGate = llvm::dyn_cast(*op->getUsers().begin()); + auto hadamardGate = dyn_cast(*op->getUsers().begin()); if (!hadamardGate || op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { @@ -263,7 +262,7 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { // A Hadamard gate needs to be in front of the measurement const auto qubitInMeasurement = op.getQubitIn(); auto* predecessor = qubitInMeasurement.getDefiningOp(); - auto hadamardGate = llvm::dyn_cast(predecessor); + auto hadamardGate = dyn_cast(predecessor); if (!hadamardGate) { return failure(); } @@ -271,20 +270,20 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { // The Hadamard gate must be successor of the target of a CNOT const auto inQubitHadamard = hadamardGate.getInputQubit(0); predecessor = inQubitHadamard.getDefiningOp(); - auto cnotGate = llvm::dyn_cast(predecessor); + auto cnotGate = dyn_cast(predecessor); if (!cnotGate) { return failure(); } if (cnotGate.getNumTargets() != 1 || cnotGate.getOutputTarget(0) != inQubitHadamard || - !llvm::isa(cnotGate.getBodyUnitary())) { + !isa(cnotGate.getBodyUnitary())) { return failure(); } // Determine the index of the control that will become the new target. The // control must not be succeeded by a measurement. unsigned int controlIndex = 0; for (unsigned int i = 0; i < cnotGate.getNumControls(); i++) { - if (llvm::dyn_cast( + if (dyn_cast( *cnotGate.getOutputControl(i).getUsers().begin())) { if (i == cnotGate.getNumControls() - 1) { return failure(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp index 4e7c4bd644..616acd3a58 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_hadamard_lifting.cpp @@ -14,7 +14,6 @@ #include "mlir/Support/IRVerification.h" #include -#include #include #include #include @@ -22,6 +21,7 @@ #include #include #include +#include #include #include @@ -313,7 +313,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { const auto b = programBuilder.allocClassicalBitRegister(1); auto [q12, q0] = programBuilder.ctrl({q[1], q[2]}, {q[0]}, [&](const ValueRange target) { - return llvm::SmallVector{programBuilder.x(target[0])}; + return SmallVector{programBuilder.x(target[0])}; }); q[1] = programBuilder.h(q0[0]); programBuilder.measure(q[1], b[0]); @@ -325,7 +325,7 @@ TEST_F(QCOHadamardLiftingTest, liftHadamardOverMultipleControlledXGate) { qRef[1] = referenceBuilder.h(qRef[1]); auto [q02Ref, q1Ref] = referenceBuilder.ctrl( {qRef[0], qRef[2]}, {qRef[1]}, [&](const ValueRange target) { - return llvm::SmallVector{referenceBuilder.x(target[0])}; + return SmallVector{referenceBuilder.x(target[0])}; }); referenceBuilder.h(q1Ref[0]); referenceBuilder.measure(q02Ref[0], bRef[0]); @@ -390,7 +390,7 @@ TEST_F(QCOHadamardLiftingTest, programBuilder.measure(q1, b[1]); auto [q34, q2] = programBuilder.ctrl({q[3], q[4]}, {q[2]}, [&](const ValueRange target) { - return llvm::SmallVector{programBuilder.x(target[0])}; + return SmallVector{programBuilder.x(target[0])}; }); q[2] = programBuilder.h(q2[0]); programBuilder.measure(q[2], b[2]); @@ -408,7 +408,7 @@ TEST_F(QCOHadamardLiftingTest, qRef[4] = referenceBuilder.h(qRef[4]); auto [qRef32, qRef4] = referenceBuilder.ctrl( {qRef[3], qRef[2]}, {qRef[4]}, [&](const ValueRange target) { - return llvm::SmallVector{referenceBuilder.x(target[0])}; + return SmallVector{referenceBuilder.x(target[0])}; }); qRef[4] = referenceBuilder.h(qRef4[0]); referenceBuilder.measure(qRef32[1], bRef[2]); From 9afaacd4ce92429358b4ffcbeb3a00b325196144 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Wed, 6 May 2026 16:54:24 +0200 Subject: [PATCH 060/131] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Simplify=20pass=20?= =?UTF-8?q?implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lukas Burgholzer --- CHANGELOG.md | 2 +- .../Optimizations/HadamardLifting.cpp | 183 ++++-------------- 2 files changed, 39 insertions(+), 146 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4bb49f1e6..8b8baefd2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added -- ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**]) +- ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], [**@MatthiasReumann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637]) ([**@denialhaag**]) - ✨ Add a `place-and-route` pass for mapping circuits to architectures with restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1664]) ([**@MatthiasReumann**], [**@burgholzer**]) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp index fe82db2fc7..5d80316ecb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include @@ -106,8 +105,7 @@ struct LiftHadamardsAbovePauliGatesPattern final // op needs to be in front of a Hadamard gate auto hadamardGate = dyn_cast(*op->getUsers().begin()); - if (!hadamardGate || - op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { + if (!hadamardGate) { return failure(); } @@ -121,17 +119,15 @@ struct LiftHadamardsAbovePauliGatesPattern final * the new control. * * If there is a Hadamard gate between the target qubit of a CNOT and a - * measurement, we flip the CNOT and apply a hadamard gate to the incoming and + * measurement, we flip the CNOT and apply a Hadamard gate to the incoming and * outcoming qubits. As H * H = id, the measurement is then the direct successor - * of a CNOT control, which is beneficial for the qubit reuse routine. After the - * application of LiftHadamardAboveCNOTPattern, a measurement will follow - * directly after a control. In that case, measurement lifting (a routine of - * qubit reuse) can remove the multi-qubit gate by lifting the measurement in - * front of the control and changing the qubit-controlled Pauli-X to a - * classically controlled Pauli-X. + * of a CNOT control, which is beneficial for the qubit reuse routine. In that + * case, measurement lifting (a routine of qubit reuse) can remove the + * multi-qubit gate by lifting the measurement in front of the control and + * changing the qubit-controlled Pauli-X to a classically-controlled Pauli-X. * - * The procedure also works if there are additional ctrls. Only the target - * and ctrl involved in the transformation get hadamard gates assigned. + * The procedure also works if there are additional controls. Only the target + * and control involved in the transformation get Hadamard gates assigned. * The involved ctrl to be flipped with the target is chosen randomly. */ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { @@ -139,114 +135,6 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { explicit LiftHadamardAboveCNOTPattern(MLIRContext* context) : OpRewritePattern(context) {} - /** - * @brief This method swaps two operand usages in an operation. - * - * @param op The operation on with the operand usage should be swapped. - * @param a The operand value to be swapped with b. - * @param b The operand value to be swapped with a. - */ - static void swapOperandsInOp(Operation* op, const Value a, const Value b) { - for (OpOperand& operand : - llvm::make_filter_range(op->getOpOperands(), [&](const OpOperand& o) { - const Value valueOfOperand = o.get(); - return valueOfOperand == a || valueOfOperand == b; - })) { - const bool operandValueIsA = operand.get() == a; - operand.set(operandValueIsA ? b : a); - } - } - - /** - * @brief This method swaps two qubits on a gate. - * - * This method swaps two qubits on a gate. Input and output are exchanged. - * - * @param gate The gate that the qubits belong to. - * @param inputQubit1 The input qubit of the qubit to be exchanged with 2. - * @param inputQubit2 The input qubit of the qubit to be exchanged with 1. - * @param succeedingOp1 The operation succeeding gate on the corresponding - * output of inputQubit1. - * @param succeedingOp2 The operation succeeding gate on the corresponding - * output of inputQubit2. - * @param rewriter The used rewriter. - */ - static void swapQubits(CtrlOp gate, const Value inputQubit1, - const Value inputQubit2, Operation* succeedingOp1, - Operation* succeedingOp2, PatternRewriter& rewriter) { - const Value outputQubit1 = gate.getOutputForInput(inputQubit1); - const Value outputQubit2 = gate.getOutputForInput(inputQubit2); - - rewriter.modifyOpInPlace( - gate, [&] { swapOperandsInOp(gate, inputQubit1, inputQubit2); }); - - rewriter.modifyOpInPlace(succeedingOp1, [&] { - swapOperandsInOp(succeedingOp1, outputQubit1, outputQubit2); - }); - - rewriter.modifyOpInPlace(succeedingOp2, [&] { - swapOperandsInOp(succeedingOp2, outputQubit1, outputQubit2); - }); - } - - /** - * @brief This method adds Hadamard gates before a given gate. - * - * @param gate The gate before which Hadamard gates should be applied. - * @param inputQubits The input qubits of gate before which Hadamard gates - * should be applied. - * @param rewriter The used rewriter. - * @returns One of the created Hadamard gates. - */ - static HOp addHadamardGatesBeforeGate(const CtrlOp gate, - const ValueRange inputQubits, - PatternRewriter& rewriter) { - HOp newHOp; - for (const Value inputQubit : inputQubits) { - - const ValueRange inQubits(inputQubit); - - newHOp = HOp::create(rewriter, gate->getLoc(), inQubits); - - rewriter.moveOpBefore(newHOp, gate); - - rewriter.modifyOpInPlace(gate, [&] { - swapOperandsInOp(gate, inputQubit, newHOp.getOutputTarget(0)); - }); - } - return newHOp; - } - - /** - * @brief This method adds Hadamard gates after a given gate. - * - * @param gate The gate after which Hadamard gates should be applied. - * @param outputQubits The output qubits of gate after which Hadamard gates - * should be applied. - * @param rewriter The used rewriter. - * @returns One of the created Hadamard gates. - */ - static HOp addHadamardGatesAfterGate(const CtrlOp gate, - const ValueRange outputQubits, - PatternRewriter& rewriter) { - HOp newHOp; - for (Value outputQubit : outputQubits) { - - const ValueRange inQubit(outputQubit); - - newHOp = HOp::create(rewriter, gate->getLoc(), inQubit); - - rewriter.moveOpAfter(newHOp, gate); - - rewriter.replaceUsesWithIf( - newHOp.getInputTarget(0), newHOp.getOutputTarget(0), - [&](const OpOperand& operand) { - return operand.getOwner() != gate && operand.getOwner() != newHOp; - }); - } - return newHOp; - } - /** * @brief This pattern removes an H gate between a CNOT and a measurement, * flips the CNOT and adds Hadamard gates before and after the new target and @@ -274,17 +162,16 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { if (!cnotGate) { return failure(); } - if (cnotGate.getNumTargets() != 1 || - cnotGate.getOutputTarget(0) != inQubitHadamard || - !isa(cnotGate.getBodyUnitary())) { + if (!isa(cnotGate.getBodyUnitary()) || + cnotGate.getOutputTarget(0) != inQubitHadamard) { return failure(); } - // Determine the index of the control that will become the new target. The - // control must not be succeeded by a measurement. + + // Find a control qubit not followed by a measurement. + // If there is no such control, the transformation cannot be applied. unsigned int controlIndex = 0; for (unsigned int i = 0; i < cnotGate.getNumControls(); i++) { - if (dyn_cast( - *cnotGate.getOutputControl(i).getUsers().begin())) { + if (isa(*cnotGate.getOutputControl(i).getUsers().begin())) { if (i == cnotGate.getNumControls() - 1) { return failure(); } @@ -294,27 +181,33 @@ struct LiftHadamardAboveCNOTPattern final : OpRewritePattern { } } - // Remove the Hadamard gate - for (const auto outQubit : hadamardGate.getOutputQubits()) { - rewriter.replaceAllUsesWith(outQubit, - hadamardGate.getInputForOutput(outQubit)); - } - rewriter.eraseOp(hadamardGate); + // Save all SSA values that will be needed after in-place modifications. + const Value origTgtIn = cnotGate.getInputTarget(0); + const Value origCtrlIn = cnotGate.getInputControl(controlIndex); + const Value origCtrlOut = cnotGate.getOutputControl(controlIndex); + + // Add Hadamard gates before the CNOT. + rewriter.setInsertionPoint(cnotGate); + auto h1 = HOp::create(rewriter, cnotGate->getLoc(), origTgtIn); + auto h2 = HOp::create(rewriter, cnotGate->getLoc(), origCtrlIn); + + // Rewire the CNOT operands in-place so that the roles are swapped + rewriter.modifyOpInPlace(cnotGate, [&]() { + cnotGate->setOperand(controlIndex, h1.getOutputTarget(0)); + cnotGate->setOperand(cnotGate.getNumControls(), h2.getOutputTarget(0)); + }); - // Add Hadamard gates to the other in- and output gates of CNOT - addHadamardGatesBeforeGate( - cnotGate, - {cnotGate.getInputTarget(0), cnotGate.getInputControl(controlIndex)}, - rewriter); + // Move hadamardGate immediately after the CNOT so that it dominates any + // downstream user of origCtrlOut that might appear before hadamardGate. + rewriter.moveOpAfter(hadamardGate, cnotGate); - const HOp newHOPAfterCtrl = addHadamardGatesAfterGate( - cnotGate, - cnotGate.getOutputForInput(cnotGate.getInputControl(controlIndex)), - rewriter); + // Feed the MeasureOp directly from origCtrlOut (the new control output). + rewriter.modifyOpInPlace(op, [&]() { op->setOperand(0, origCtrlOut); }); - // Flip CNOT targets and ctrl - swapQubits(cnotGate, cnotGate.getInputControl(controlIndex), - cnotGate.getInputTarget(0), op, newHOPAfterCtrl, rewriter); + // Every other downstream user of origCtrlOut except for the MeasureOp + // should now see hadamardGate's output. + rewriter.replaceAllUsesExcept(origCtrlOut, hadamardGate.getOutputTarget(0), + op); return success(); } From 0fcf1e6ea2a6d7b4b4a29d14e2597f14c8b978ca Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 7 May 2026 16:32:04 +0200 Subject: [PATCH 061/131] :memo: Adapted documentation --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 6 +++--- .../QCO/Transforms/Optimizations/HadamardLifting.cpp | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 946023a409..54fccffdc7 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -131,9 +131,9 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { ┌───┐ ┌───┐ ┌───┐ ┌───┐ ─┤ Z ├─┤ H ├─ => ─┤ H ├─┤ X ├─ └───┘ └───┘ └───┘ └───┘ - ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───────┐ - ─┤ Y ├─┤ H ├─ => ─┤ H ├─┤ Y ├─┤ G(pi) ├─ - └───┘ └───┘ └───┘ └───┘ └───────┘ + ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───────┐ + ─┤ Y ├─┤ H ├─ => ─┤ H ├─┤ Y ├─, while adding a global phase: │ G(pi) │ + └───┘ └───┘ └───┘ └───┘ └───────┘ Hadamard lifting is only applied to commute Hadamard gates further in front in the circuit, not the other way around. A global phase is added if the Hadamard gate is commuted with a Pauli-Y gate, as YH = -HY. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp index 5d80316ecb..0ca22726a1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/HadamardLifting.cpp @@ -37,7 +37,7 @@ namespace { * This pattern swaps a Pauli gate with a Hadamard gate. This is done using the * commutation rules of Pauli and Hadamard gates, which are: * - X - H - = - H - Z - - * - Y - H - = - H - Y - G(pi) - + * - Y - H - = - H - Y -, while adding a gPhase(pi) * - Z - H - = - H - X - * This is applied to uncontrolled gates. * In case of Pauli-Y, a global phase is applied, as HY = -YH. @@ -53,7 +53,7 @@ struct LiftHadamardsAbovePauliGatesPattern final * This method swaps a Pauli gate with a Hadamard gate. This is done using the * commutation rules of Pauli and Hadamard gates, which are: * - X - H - = - H - Z - - * - Y - H - = - H - Y - gPhase(pi) - + * - Y - H - = - H - Y -, while adding a gPhase(pi) * - Z - H - = - H - X - * * @param gate The Pauli gate. From 06c681fbd782a4172e67f8eebbfac46b2da905bb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 9 Jun 2026 16:15:18 +0200 Subject: [PATCH 062/131] :bricks: Add files for tracking QuantumStates --- .../ConstantPropagation/QuantumState.hpp | 25 +++++++++++++++++++ .../ConstantPropagation/QuantumState.cpp | 21 ++++++++++++++++ .../Transforms/Optimizations/CMakeLists.txt | 3 ++- .../ConstantPropagation/test_quantumState.cpp | 21 ++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp new file mode 100644 index 0000000000..c67b14dcb0 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_QUANTUMSTATEORTOP_H +#define MQT_CORE_QUANTUMSTATEORTOP_H + +namespace mlir::qco { + +class QuantumState { + int q = 1; + +public: + int inc(const int i) const; +}; + +} // namespace mlir::qco + +#endif // MQT_CORE_QUANTUMSTATEORTOP_H diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp new file mode 100644 index 0000000000..e5093043f8 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_QUANTUMSTATEORTOP +#define MQT_CORE_QUANTUMSTATEORTOP +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" + +namespace mlir::qco { + +int QuantumState::inc(const int i) const { return i + q; } + +} // namespace mlir::qco + +#endif // MQT_CORE_QUANTUMSTATEORTOP diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index b785e5a400..8d48d46273 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -8,7 +8,8 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable(${target_name} test_qco_hadamard_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp) + test_qco_merge_single_qubit_rotation.cpp + ConstantPropagation/test_quantumState.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp new file mode 100644 index 0000000000..4ff1a5de92 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "gtest/gtest.h" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" + +namespace mlir::qco { + +TEST(CPTest, cpTest) { + auto q = QuantumState(); + EXPECT_EQ(q.inc(3), 4); +} + +} // namespace mlir::qco From 4bd6f65871ffbfbd169aa83a9d711dfdd5f95a15 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 14:39:48 +0200 Subject: [PATCH 063/131] :construction: Added GateToMap.h --- .../ConstantPropagation/GateToMap.h | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h new file mode 100644 index 0000000000..97d9c76684 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_GATETOMAP_H +#define MQT_CORE_GATETOMAP_H + +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/Drivers.h" + +#include +#include +#include +#include +#include +#include + +using ResultMap = + std::unordered_map>>; + +inline std::unordered_map< + unsigned int, std::unordered_map>> +getQubitMappingOfGates(mlir::Operation* gate, + const std::vector& params) { + + return mlir::TypeSwitch< + mlir::Operation*, + std::unordered_map< + unsigned int, + std::unordered_map>>>(gate) + .Case([&](auto) { + return ResultMap{{0, {{0, std::complex(1, 0)}}}, + {1, {{1, std::complex(1, 0)}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(1 / std::numbers::sqrt2, 0)}, + {1, std::complex(1 / std::numbers::sqrt2, 0)}}}, + {1, + {{0, std::complex(1 / std::numbers::sqrt2, 0)}, + {1, std::complex(-1 / std::numbers::sqrt2, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{1, std::complex(1, 0)}}}, + {1, {{0, std::complex(1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{1, std::complex(0, 1)}}}, + {1, {{0, std::complex(0, -1)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{1, std::complex(-1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{1, std::complex(0, 1)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{1, std::complex(0, -1)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, {{0, std::complex(1, 0)}}}, + {1, + {{1, std::complex(1 / std::numbers::sqrt2, + 1 / std::numbers::sqrt2)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, {{0, std::complex(1, 0)}}}, + {1, + {{1, std::complex(1 / std::numbers::sqrt2, + -1 / std::numbers::sqrt2)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, + {{0, std::complex(1.0 / 2.0, 1.0 / 2.0)}, + {1, std::complex(1.0 / 2.0, -1.0 / 2.0)}}}, + {1, + {{0, std::complex(1.0 / 2.0, -1.0 / 2.0)}, + {1, std::complex(1.0 / 2.0, 1.0 / 2.0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(1.0 / 2.0, -1.0 / 2.0)}, + {1, std::complex(1.0 / 2.0, 1.0 / 2.0)}}}, + {1, + {{0, std::complex(1.0 / 2.0, 1.0 / 2.0)}, + {1, std::complex(1.0 / 2.0, -1.0 / 2.0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {1, std::complex(0, -sin(params[0] / 2))}}}, + {1, + {{0, std::complex(0, -sin(params[0] / 2))}, + {1, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {1, std::complex(sin(params[0] / 2), 0)}}}, + {1, + {{0, std::complex(-sin(params[0] / 2), 0)}, + {1, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, {{0, exp(std::complex(0, -params[0] / 2))}}}, + {1, {{1, exp(std::complex(0, params[0] / 2))}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{1, exp(std::complex(0, params[0]))}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {1, exp(std::complex(0, params[1])) * + std::complex(0, -sin(params[0] / 2))}}}, + {1, + {{0, exp(std::complex(0, -params[1])) * + std::complex(0, -sin(params[0] / 2))}, + {1, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(1 / std::numbers::sqrt2, 0)}, + {1, exp(std::complex(0, params[0]))}}}, + {1, + {{0, -exp(std::complex(0, params[1]))}, + {1, exp(std::complex(0, params[0] + params[1]))}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {1, + exp(std::complex(0, params[1])) * sin(params[0] / 2)}}}, + {1, + {{0, + -exp(std::complex(0, params[2])) * sin(params[0] / 2)}, + {1, exp(std::complex(0, params[1] + params[2])) * + cos(params[0] / 2)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{2, std::complex(1, 0)}}}, + {2, {{1, std::complex(1, 0)}}}, + {3, {{3, std::complex(1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{2, std::complex(0, -1)}}}, + {2, {{1, std::complex(0, -1)}}}, + {3, {{3, std::complex(1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, {{2, std::complex(1, 0)}}}, + {2, {{3, std::complex(1, 0)}}}, + {3, {{1, std::complex(1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{2, std::complex(1 / std::numbers::sqrt2, 0)}, + {3, std::complex(0, -1 / std::numbers::sqrt2)}}}, + {1, + {{2, std::complex(0, -1 / std::numbers::sqrt2)}, + {3, std::complex(1 / std::numbers::sqrt2, 0)}}}, + {2, + {{0, std::complex(1 / std::numbers::sqrt2, 0)}, + {1, std::complex(0, 1 / std::numbers::sqrt2)}}}, + {3, + {{0, std::complex(0, 1 / std::numbers::sqrt2)}, + {1, std::complex(1 / std::numbers::sqrt2, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {3, std::complex(0, -sin(params[0] / 2))}}}, + {1, + {{1, std::complex(cos(params[0] / 2), 0)}, + {2, std::complex(0, -sin(params[0] / 2))}}}, + {2, + {{1, std::complex(0, -sin(params[0] / 2))}, + {2, std::complex(cos(params[0] / 2), 0)}}}, + {3, + {{0, std::complex(0, -sin(params[0] / 2))}, + {3, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {3, std::complex(0, sin(params[0] / 2))}}}, + {1, + {{1, std::complex(cos(params[0] / 2), 0)}, + {2, std::complex(0, -sin(params[0] / 2))}}}, + {2, + {{1, std::complex(0, -sin(params[0] / 2))}, + {2, std::complex(cos(params[0] / 2), 0)}}}, + {3, + {{0, std::complex(0, sin(params[0] / 2))}, + {3, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {1, std::complex(0, -sin(params[0] / 2))}}}, + {1, + {{0, std::complex(0, -sin(params[0] / 2))}, + {1, std::complex(cos(params[0] / 2), 0)}}}, + {2, + {{2, std::complex(cos(params[0] / 2), 0)}, + {3, std::complex(0, sin(params[0] / 2))}}}, + {3, + {{2, std::complex(0, sin(params[0] / 2))}, + {3, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, {{0, exp(std::complex(0, -params[0] / 2))}}}, + {1, {{1, exp(std::complex(0, params[0] / 2))}}}, + {2, {{2, exp(std::complex(0, params[0] / 2))}}}, + {3, {{3, exp(std::complex(0, -params[0] / 2))}}}}}; + }) + .Case([&](auto) { + return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + {1, + {{1, std::complex(cos(params[0] / 2), 0)}, + {2, std::complex(0, -sin(params[0] / 2)) * + exp(std::complex(0, params[1]))}}}, + {2, + {{1, std::complex(0, -sin(params[0] / 2)) * + exp(std::complex(0, -params[1]))}, + {2, std::complex(cos(params[0] / 2), 0)}}}, + {3, {{3, std::complex(1, 0)}}}}}; + }) + .Case([&](auto) { + return ResultMap{ + {{0, + {{0, std::complex(cos(params[0] / 2), 0)}, + {3, std::complex(0, -sin(params[0] / 2)) * + exp(std::complex(0, params[1]))}}}, + {1, {{1, std::complex(1, 0)}}}, + {2, {{2, std::complex(1, 0)}}}, + {3, + {{0, std::complex(0, -sin(params[0] / 2)) * + exp(std::complex(0, -params[1]))}, + {3, std::complex(cos(params[0] / 2), 0)}}}}}; + }) + .Default([&](auto) -> ResultMap { + throw std::runtime_error("Unsupported gate in mlir::qco::gatetomap"); + }); +} + +#endif // MQT_CORE_GATETOMAP_H \ No newline at end of file From 87ddcde7d776e2637b5337655b8308d1c1a75a86 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:20:09 +0200 Subject: [PATCH 064/131] :recycle: Refactored GateToMap.h, Assisted-by: GPT OSS via KI:connect --- .../ConstantPropagation/GateToMap.h | 302 +++++++----------- 1 file changed, 123 insertions(+), 179 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h index 97d9c76684..22b9090ca5 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -17,260 +17,204 @@ #include #include #include +#include #include #include -#include + +using Complex = std::complex; using ResultMap = - std::unordered_map>>; + std::unordered_map>; + +constexpr double inv_sqrt2 = 1.0 / std::numbers::sqrt2; -inline std::unordered_map< - unsigned int, std::unordered_map>> -getQubitMappingOfGates(mlir::Operation* gate, - const std::vector& params) { +inline std::unordered_map> +getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { return mlir::TypeSwitch< mlir::Operation*, - std::unordered_map< - unsigned int, - std::unordered_map>>>(gate) + std::unordered_map>>( + gate) .Case([&](auto) { - return ResultMap{{0, {{0, std::complex(1, 0)}}}, - {1, {{1, std::complex(1, 0)}}}}; + return ResultMap{{0, {{0, Complex(1, 0)}}}, {1, {{1, Complex(1, 0)}}}}; }) .Case([&](auto) { return ResultMap{ - {{0, - {{0, std::complex(1 / std::numbers::sqrt2, 0)}, - {1, std::complex(1 / std::numbers::sqrt2, 0)}}}, - {1, - {{0, std::complex(1 / std::numbers::sqrt2, 0)}, - {1, std::complex(-1 / std::numbers::sqrt2, 0)}}}}}; + {{0, {{0, Complex(inv_sqrt2, 0)}, {1, Complex(inv_sqrt2, 0)}}}, + {1, {{0, Complex(inv_sqrt2, 0)}, {1, Complex(-inv_sqrt2, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{1, std::complex(1, 0)}}}, - {1, {{0, std::complex(1, 0)}}}}}; + return ResultMap{ + {{0, {{1, Complex(1, 0)}}}, {1, {{0, Complex(1, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{1, std::complex(0, 1)}}}, - {1, {{0, std::complex(0, -1)}}}}}; + return ResultMap{ + {{0, {{1, Complex(0, 1)}}}, {1, {{0, Complex(0, -1)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{1, std::complex(-1, 0)}}}}}; + return ResultMap{ + {{0, {{0, Complex(1, 0)}}}, {1, {{1, Complex(-1, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{1, std::complex(0, 1)}}}}}; + return ResultMap{ + {{0, {{0, Complex(1, 0)}}}, {1, {{1, Complex(0, 1)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{1, std::complex(0, -1)}}}}}; + return ResultMap{ + {{0, {{0, Complex(1, 0)}}}, {1, {{1, Complex(0, -1)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, {{0, std::complex(1, 0)}}}, - {1, - {{1, std::complex(1 / std::numbers::sqrt2, - 1 / std::numbers::sqrt2)}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{1, Complex(inv_sqrt2, inv_sqrt2)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, {{0, std::complex(1, 0)}}}, - {1, - {{1, std::complex(1 / std::numbers::sqrt2, - -1 / std::numbers::sqrt2)}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{1, Complex(inv_sqrt2, -inv_sqrt2)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, - {{0, std::complex(1.0 / 2.0, 1.0 / 2.0)}, - {1, std::complex(1.0 / 2.0, -1.0 / 2.0)}}}, - {1, - {{0, std::complex(1.0 / 2.0, -1.0 / 2.0)}, - {1, std::complex(1.0 / 2.0, 1.0 / 2.0)}}}}}; + return ResultMap{ + {{0, {{0, Complex(0.5, 0.5)}, {1, Complex(0.5, -0.5)}}}, + {1, {{0, Complex(0.5, -0.5)}, {1, Complex(0.5, 0.5)}}}}}; }) .Case([&](auto) { return ResultMap{ - {{0, - {{0, std::complex(1.0 / 2.0, -1.0 / 2.0)}, - {1, std::complex(1.0 / 2.0, 1.0 / 2.0)}}}, - {1, - {{0, std::complex(1.0 / 2.0, 1.0 / 2.0)}, - {1, std::complex(1.0 / 2.0, -1.0 / 2.0)}}}}}; + {{0, {{0, Complex(0.5, -0.5)}, {1, Complex(0.5, 0.5)}}}, + {1, {{0, Complex(0.5, 0.5)}, {1, Complex(0.5, -0.5)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {1, std::complex(0, -sin(params[0] / 2))}}}, - {1, - {{0, std::complex(0, -sin(params[0] / 2))}, - {1, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(c, 0)}, {1, Complex(0, -s)}}}, + {1, {{0, Complex(0, -s)}, {1, Complex(c, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {1, std::complex(sin(params[0] / 2), 0)}}}, - {1, - {{0, std::complex(-sin(params[0] / 2), 0)}, - {1, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(c, 0)}, {1, Complex(s, 0)}}}, + {1, {{0, Complex(-s, 0)}, {1, Complex(c, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, {{0, exp(std::complex(0, -params[0] / 2))}}}, - {1, {{1, exp(std::complex(0, params[0] / 2))}}}}}; + const double halfParameter = 0.5 * params[0]; + return ResultMap{{{0, {{0, exp(Complex(0, -halfParameter))}}}, + {1, {{1, exp(Complex(0, halfParameter))}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{1, exp(std::complex(0, params[0]))}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{1, exp(Complex(0, params[0]))}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {1, exp(std::complex(0, params[1])) * - std::complex(0, -sin(params[0] / 2))}}}, - {1, - {{0, exp(std::complex(0, -params[1])) * - std::complex(0, -sin(params[0] / 2))}, - {1, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, + {{0, Complex(c, 0)}, + {1, exp(Complex(0, params[1])) * Complex(0, -s)}}}, + {1, + {{0, exp(Complex(0, -params[1])) * Complex(0, -s)}, + {1, Complex(c, 0)}}}}}; }) .Case([&](auto) { return ResultMap{ - {{0, - {{0, std::complex(1 / std::numbers::sqrt2, 0)}, - {1, exp(std::complex(0, params[0]))}}}, + {{0, {{0, Complex(inv_sqrt2, 0)}, {1, exp(Complex(0, params[0]))}}}, {1, - {{0, -exp(std::complex(0, params[1]))}, - {1, exp(std::complex(0, params[0] + params[1]))}}}}}; + {{0, -exp(Complex(0, params[1]))}, + {1, exp(Complex(0, params[0] + params[1]))}}}}}; }) .Case([&](auto) { + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {1, - exp(std::complex(0, params[1])) * sin(params[0] / 2)}}}, + {{0, {{0, Complex(c, 0)}, {1, exp(Complex(0, params[1])) * s}}}, {1, - {{0, - -exp(std::complex(0, params[2])) * sin(params[0] / 2)}, - {1, exp(std::complex(0, params[1] + params[2])) * - cos(params[0] / 2)}}}}}; + {{0, -exp(Complex(0, params[2])) * s}, + {1, exp(Complex(0, params[1] + params[2])) * c}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{2, std::complex(1, 0)}}}, - {2, {{1, std::complex(1, 0)}}}, - {3, {{3, std::complex(1, 0)}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{2, Complex(1, 0)}}}, + {2, {{1, Complex(1, 0)}}}, + {3, {{3, Complex(1, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{2, std::complex(0, -1)}}}, - {2, {{1, std::complex(0, -1)}}}, - {3, {{3, std::complex(1, 0)}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{2, Complex(0, -1)}}}, + {2, {{1, Complex(0, -1)}}}, + {3, {{3, Complex(1, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, - {1, {{2, std::complex(1, 0)}}}, - {2, {{3, std::complex(1, 0)}}}, - {3, {{1, std::complex(1, 0)}}}}}; + return ResultMap{{{0, {{0, Complex(1, 0)}}}, + {1, {{2, Complex(1, 0)}}}, + {2, {{3, Complex(1, 0)}}}, + {3, {{1, Complex(1, 0)}}}}}; }) .Case([&](auto) { return ResultMap{ - {{0, - {{2, std::complex(1 / std::numbers::sqrt2, 0)}, - {3, std::complex(0, -1 / std::numbers::sqrt2)}}}, - {1, - {{2, std::complex(0, -1 / std::numbers::sqrt2)}, - {3, std::complex(1 / std::numbers::sqrt2, 0)}}}, - {2, - {{0, std::complex(1 / std::numbers::sqrt2, 0)}, - {1, std::complex(0, 1 / std::numbers::sqrt2)}}}, - {3, - {{0, std::complex(0, 1 / std::numbers::sqrt2)}, - {1, std::complex(1 / std::numbers::sqrt2, 0)}}}}}; + {{0, {{2, Complex(inv_sqrt2, 0)}, {3, Complex(0, -inv_sqrt2)}}}, + {1, {{2, Complex(0, -inv_sqrt2)}, {3, Complex(inv_sqrt2, 0)}}}, + {2, {{0, Complex(inv_sqrt2, 0)}, {1, Complex(0, inv_sqrt2)}}}, + {3, {{0, Complex(0, inv_sqrt2)}, {1, Complex(inv_sqrt2, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {3, std::complex(0, -sin(params[0] / 2))}}}, - {1, - {{1, std::complex(cos(params[0] / 2), 0)}, - {2, std::complex(0, -sin(params[0] / 2))}}}, - {2, - {{1, std::complex(0, -sin(params[0] / 2))}, - {2, std::complex(cos(params[0] / 2), 0)}}}, - {3, - {{0, std::complex(0, -sin(params[0] / 2))}, - {3, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(c, 0)}, {3, Complex(0, -s)}}}, + {1, {{1, Complex(c, 0)}, {2, Complex(0, -s)}}}, + {2, {{1, Complex(0, -s)}, {2, Complex(c, 0)}}}, + {3, {{0, Complex(0, -s)}, {3, Complex(c, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {3, std::complex(0, sin(params[0] / 2))}}}, - {1, - {{1, std::complex(cos(params[0] / 2), 0)}, - {2, std::complex(0, -sin(params[0] / 2))}}}, - {2, - {{1, std::complex(0, -sin(params[0] / 2))}, - {2, std::complex(cos(params[0] / 2), 0)}}}, - {3, - {{0, std::complex(0, sin(params[0] / 2))}, - {3, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(c, 0)}, {3, Complex(0, s)}}}, + {1, {{1, Complex(c, 0)}, {2, Complex(0, -s)}}}, + {2, {{1, Complex(0, -s)}, {2, Complex(c, 0)}}}, + {3, {{0, Complex(0, s)}, {3, Complex(c, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {1, std::complex(0, -sin(params[0] / 2))}}}, - {1, - {{0, std::complex(0, -sin(params[0] / 2))}, - {1, std::complex(cos(params[0] / 2), 0)}}}, - {2, - {{2, std::complex(cos(params[0] / 2), 0)}, - {3, std::complex(0, sin(params[0] / 2))}}}, - {3, - {{2, std::complex(0, sin(params[0] / 2))}, - {3, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(c, 0)}, {1, Complex(0, -s)}}}, + {1, {{0, Complex(0, -s)}, {1, Complex(c, 0)}}}, + {2, {{2, Complex(c, 0)}, {3, Complex(0, s)}}}, + {3, {{2, Complex(0, s)}, {3, Complex(c, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, {{0, exp(std::complex(0, -params[0] / 2))}}}, - {1, {{1, exp(std::complex(0, params[0] / 2))}}}, - {2, {{2, exp(std::complex(0, params[0] / 2))}}}, - {3, {{3, exp(std::complex(0, -params[0] / 2))}}}}}; + const double halfParam = 0.5 * params[0]; + Complex ePos = exp(Complex(0, halfParam)); + Complex eNeg = exp(Complex(0, -halfParam)); + return ResultMap{{{0, {{0, eNeg}}}, + {1, {{1, ePos}}}, + {2, {{2, ePos}}}, + {3, {{3, eNeg}}}}}; }) .Case([&](auto) { - return ResultMap{{{0, {{0, std::complex(1, 0)}}}, + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, {{0, Complex(1, 0)}}}, {1, - {{1, std::complex(cos(params[0] / 2), 0)}, - {2, std::complex(0, -sin(params[0] / 2)) * - exp(std::complex(0, params[1]))}}}, + {{1, Complex(c, 0)}, + {2, Complex(0, -s) * exp(Complex(0, params[1]))}}}, {2, - {{1, std::complex(0, -sin(params[0] / 2)) * - exp(std::complex(0, -params[1]))}, - {2, std::complex(cos(params[0] / 2), 0)}}}, - {3, {{3, std::complex(1, 0)}}}}}; + {{1, Complex(0, -s) * exp(Complex(0, -params[1]))}, + {2, Complex(c, 0)}}}, + {3, {{3, Complex(1, 0)}}}}}; }) .Case([&](auto) { - return ResultMap{ - {{0, - {{0, std::complex(cos(params[0] / 2), 0)}, - {3, std::complex(0, -sin(params[0] / 2)) * - exp(std::complex(0, params[1]))}}}, - {1, {{1, std::complex(1, 0)}}}, - {2, {{2, std::complex(1, 0)}}}, - {3, - {{0, std::complex(0, -sin(params[0] / 2)) * - exp(std::complex(0, -params[1]))}, - {3, std::complex(cos(params[0] / 2), 0)}}}}}; + const double c = cos(0.5 * params[0]); + const double s = sin(0.5 * params[0]); + return ResultMap{{{0, + {{0, Complex(c, 0)}, + {3, Complex(0, -s) * exp(Complex(0, params[1]))}}}, + {1, {{1, Complex(1, 0)}}}, + {2, {{2, Complex(1, 0)}}}, + {3, + {{0, Complex(0, -s) * exp(Complex(0, -params[1]))}, + {3, Complex(c, 0)}}}}}; }) .Default([&](auto) -> ResultMap { throw std::runtime_error("Unsupported gate in mlir::qco::gatetomap"); }); } -#endif // MQT_CORE_GATETOMAP_H \ No newline at end of file +#endif // MQT_CORE_GATETOMAP_H From 992c0f624754efb3cd6e18545c9baa6d02552c6f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:26:11 +0200 Subject: [PATCH 065/131] =?UTF-8?q?=F0=9F=93=9D=20Added=20Docstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/GateToMap.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h index 22b9090ca5..c3903119b7 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -8,6 +8,8 @@ * Licensed under the MIT License */ +#pragma once + #ifndef MQT_CORE_GATETOMAP_H #define MQT_CORE_GATETOMAP_H @@ -21,6 +23,12 @@ #include #include +/** + * This file provides information of available quantum gates as mappings. It is + * used in constant propagation to get the factors each amplitude has to be + * multiplied with to get the new amplitudes after a gate application. + */ + using Complex = std::complex; using ResultMap = From 3954213aba854f5463e6be6e41412950202e4441 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:46:31 +0200 Subject: [PATCH 066/131] :construction: Added Header File for QuantumState --- .../ConstantPropagation/QuantumState.hpp | 130 +++++++++++++++++- 1 file changed, 128 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index c67b14dcb0..db1227b8fb 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -10,14 +10,140 @@ #ifndef MQT_CORE_QUANTUMSTATEORTOP_H #define MQT_CORE_QUANTUMSTATEORTOP_H +#include + +#include +#include +#include +#include namespace mlir::qco { +/** + * @brief This class represents a quantum state. + * + * This class holds n qubits in different basis states with their corresponding + * complex amplitude. It holds the information about which global qubit number + * corresponds to which local qubit number. + */ + class QuantumState { - int q = 1; + std::size_t nQubits; + std::size_t maxNonzeroAmplitudes; + std::unordered_map globalToLocalQubitNumber; + std::unordered_map> amplitudeMap; public: - int inc(const int i) const; + QuantumState(std::span globalQubitNumber, + std::size_t maxNonzeroAmplitudes); + + ~QuantumState(); + + void print(std::ostream& os) const; + + std::string toString() const; + + bool operator==(const QuantumState& that) const; + + /** + * @brief This method unifies two QuantumState. + * + * This method unifies the current QuantumState with the given one and returns + * a new QuantumState, if the new state has no more than maxNonzeroAmplitude + * nonzero amplitudes. Otherwise, throws a domain_error. + * + * @param that The QuantumState to unify this with. + * @throw std::domain_error If the number of nonzero amplitudes would exceed + * maxNonzeroAmplitudes of this. + */ + QuantumState unify(const QuantumState& that); + + /** + * @brief This method applies a gate to the qubits. + * + * This method changes the amplitudes of a QuantumState according to the + * applied gate. Returns the current QuantumState if it has no more than + * maxNonZeroAmplitude nonzero amplitudes. Otherwise, throws a domain_error. + * + * @param gate The gate to be applied. + * @param targets A span of the global indices of the target qubits. + * @param posCtrls A span of the global indices of the ctrl qubits. + * @param params The parameter applied to the gate. + * @throw std::domain_error If the number of nonzero amplitudes would exceed + * maxNonzeroAmplitudes. + */ + void propagateGate(Operation* gate, const std::span& targets, + const std::span& posCtrls = {}, + const std::span& params = {}); + + /** + * @brief This method applies a measurement to the qubits. + * + * This method applies a measurement to the qubits. It returns the + * QuantumState in case the measurement was 0 and in case it was 1, alongside + * the respective probabilities. + * + * @param target The global index of the qubit to be measured. + * @return A map of the measurement result (zero and/or one) pointing to the + * probability for the result and the QuantumStates after measurement. + */ + std::map>> + measureQubit(unsigned int target); + + /** + * @brief This method resets a qubit. + * + * This method resets a qubit. This is done by assuming that a measurement is + * applied and measurements in the one-state are set to the zero state. In + * order to not get a mixed state, the method returns one to two + * QuantumStates, one in case the measurement was 0 and one in case it was 1, + * alongside the respective probabilities. In both cases, the target qubit + * will be zero (as a reset is performed). + * + * @param target The global index of the qubit to be measured. + * @return A set of one to two QuantumStates and their corresponding + * probabilities. + */ + std::set>> + resetQubit(unsigned int target); + + /** + * @brief This method checks if only amplitudes with a given qubit = 1 are + * nonzero. + * + * @param q The global index of the qubit. + * @return A set of one to two QuantumStates and their corresponding + * probabilities. + */ + [[nodiscard("QuantumState::isQubitAlwaysOne called but ignored")]] bool + isQubitAlwaysOne(size_t q) const; + + /** + * @brief This method checks if only amplitudes with a given qubit = 0 are + * nonzero. + * + * @param q The global index of the qubit. + * @return A set of one to two QuantumStates and their corresponding + * probabilities. + */ + [[nodiscard("QuantumState::isQubitAlwaysZero called but ignored")]] bool + isQubitAlwaysZero(size_t q) const; + + /** + * @brief Returns whether the given qubits have for a given value always a + * zero amplitude. + * + * This method receives a number of global qubit indices and checks whether + * they have for a given value always a zero amplitude. + * + * @param qubits The qubits which are being checked. + * @param value The value for which is tested whether there is a nonzero + * amplitude. + * @returns True if the amplitude is always zero, false otherwise. + */ + [[nodiscard("QuantumState::hasAlwaysZeroAmplitude called but ignored")]] bool + hasAlwaysZeroAmplitude(const std::vector& qubits, + unsigned int value) const; }; } // namespace mlir::qco From 549e7868d677b126cdb3a83b37ab1f46c89bced0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:59:55 +0200 Subject: [PATCH 067/131] :recycle: Refactored QuantumState.hpp, assisted-by GPT OSS via KI:connect --- .../ConstantPropagation/QuantumState.hpp | 59 ++++++++++++++----- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index db1227b8fb..9f418b4831 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -14,11 +14,35 @@ #include #include +#include #include +#include #include namespace mlir::qco { +/** + * @brief Result of a measurement or reset. + * + * The result contains 0, 1 or 2 QuantumStates, together with their respective + * results. + */ +struct MeasurementResult { + // The pair is (probability, resulting state). + std::array>, 2> + states{}; + // How many entries in `states` are actually filled (0, 1 or 2). + std::size_t size = 0; + + [[nodiscard]] constexpr std::size_t count() const noexcept { return size; } + + // Range‑for friendliness + [[nodiscard]] constexpr auto begin() const noexcept { return states.begin(); } + [[nodiscard]] constexpr auto end() const noexcept { + return states.begin() + size; + } +}; + /** * @brief This class represents a quantum state. * @@ -26,7 +50,6 @@ namespace mlir::qco { * complex amplitude. It holds the information about which global qubit number * corresponds to which local qubit number. */ - class QuantumState { std::size_t nQubits; std::size_t maxNonzeroAmplitudes; @@ -41,9 +64,11 @@ class QuantumState { void print(std::ostream& os) const; - std::string toString() const; + [[nodiscard("QuantumState::toString called but ignored")]] std::string + toString() const; - bool operator==(const QuantumState& that) const; + [[nodiscard("QuantumState::== called but ignored")]] bool + operator==(const QuantumState& that) const; /** * @brief This method unifies two QuantumState. @@ -56,7 +81,8 @@ class QuantumState { * @throw std::domain_error If the number of nonzero amplitudes would exceed * maxNonzeroAmplitudes of this. */ - QuantumState unify(const QuantumState& that); + [[nodiscard("QuantumState::unify called but ignored")]] QuantumState + unify(const QuantumState& that); /** * @brief This method applies a gate to the qubits. @@ -72,9 +98,9 @@ class QuantumState { * @throw std::domain_error If the number of nonzero amplitudes would exceed * maxNonzeroAmplitudes. */ - void propagateGate(Operation* gate, const std::span& targets, - const std::span& posCtrls = {}, - const std::span& params = {}); + void propagateGate(Operation* gate, std::span targets, + std::span posCtrls = {}, + std::span params = {}); /** * @brief This method applies a measurement to the qubits. @@ -84,10 +110,11 @@ class QuantumState { * the respective probabilities. * * @param target The global index of the qubit to be measured. - * @return A map of the measurement result (zero and/or one) pointing to the - * probability for the result and the QuantumStates after measurement. + * @return MeasurementResult, containing the probability for the result and + * the QuantumStates after measurement. */ - std::map>> + [[nodiscard( + "QuantumState::measureQubit called but ignored")]] MeasurementResult measureQubit(unsigned int target); /** @@ -101,10 +128,10 @@ class QuantumState { * will be zero (as a reset is performed). * * @param target The global index of the qubit to be measured. - * @return A set of one to two QuantumStates and their corresponding - * probabilities. + * @return MeasurementResult, containing the probability for the result and + * the QuantumStates after measurement. */ - std::set>> + [[nodiscard("QuantumState::resetQubit called but ignored")]] MeasurementResult resetQubit(unsigned int target); /** @@ -116,7 +143,7 @@ class QuantumState { * probabilities. */ [[nodiscard("QuantumState::isQubitAlwaysOne called but ignored")]] bool - isQubitAlwaysOne(size_t q) const; + isQubitAlwaysOne(unsigned int q) const; /** * @brief This method checks if only amplitudes with a given qubit = 0 are @@ -127,7 +154,7 @@ class QuantumState { * probabilities. */ [[nodiscard("QuantumState::isQubitAlwaysZero called but ignored")]] bool - isQubitAlwaysZero(size_t q) const; + isQubitAlwaysZero(unsigned int q) const; /** * @brief Returns whether the given qubits have for a given value always a @@ -142,7 +169,7 @@ class QuantumState { * @returns True if the amplitude is always zero, false otherwise. */ [[nodiscard("QuantumState::hasAlwaysZeroAmplitude called but ignored")]] bool - hasAlwaysZeroAmplitude(const std::vector& qubits, + hasAlwaysZeroAmplitude(std::span qubits, unsigned int value) const; }; From 060c7c767b8fde9feb87690a412a3c78d118bb75 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 17:42:30 +0200 Subject: [PATCH 068/131] :construction: Added QuantumState class --- .../ConstantPropagation/QuantumState.hpp | 215 ++++++++++++++++- .../ConstantPropagation/QuantumState.cpp | 223 +++++++++++++++++- 2 files changed, 434 insertions(+), 4 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 9f418b4831..304a8edb33 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -12,12 +12,16 @@ #define MQT_CORE_QUANTUMSTATEORTOP_H #include +#include #include #include #include +#include #include #include #include +#include +#include namespace mlir::qco { @@ -56,11 +60,211 @@ class QuantumState { std::unordered_map globalToLocalQubitNumber; std::unordered_map> amplitudeMap; + std::string qubitStringToBinary(unsigned int q) const { + std::string str; + for (int i = static_cast(nQubits) - 1; i >= 0; i--) { + if (const auto currentDigit = static_cast(pow(2, i)); + q & currentDigit) { + str += "1"; + q -= currentDigit; + } else { + str += "0"; + } + } + return str; + } + + /** + * @brief This method receives a two qubit gate mapping and a bitmask for + * targets and ctrls. + * + * This method receives a two qubit gate mapping and a bitmask for target and + * ctrl qubits. The gate is applied to the valid qubit states. It returns the + * map which would be the qubit state after gate application. + * + * @param gateMapping The mapping representing the gate + * @param bitmaskForQubitTargets The bitmask of the qubit targets. I.e. 011, + * if zeroth and first qubit are targets. + * @param bitmaskForCtrls The bitmask of the positively controlling qubits. + * @return The qubit state after the gate has been applied. + */ + std::unordered_map> + getNewMappingForTwoQubitGate( + std::unordered_map>> + gateMapping, + std::unordered_map bitmaskForQubitTargets, + const unsigned int bitmaskForCtrls) { + std::unordered_map> newValues; + + for (const auto& [key, value] : amplitudeMap) { + if ((bitmaskForCtrls & key) != bitmaskForCtrls) { + newValues[key] += value; + continue; + } + + unsigned int mapFrom = 0; + std::vector keysForNewValue(4); + + if ((key & bitmaskForQubitTargets[3]) == bitmaskForQubitTargets[3]) { + mapFrom = 3; + keysForNewValue[3] = key; + keysForNewValue[2] = key - bitmaskForQubitTargets[1]; + keysForNewValue[1] = key - bitmaskForQubitTargets[2]; + keysForNewValue[0] = key - bitmaskForQubitTargets[3]; + } else if ((key & bitmaskForQubitTargets[2]) == + bitmaskForQubitTargets[2]) { + mapFrom = 2; + keysForNewValue[3] = key + bitmaskForQubitTargets[1]; + keysForNewValue[2] = key; + keysForNewValue[1] = key ^ bitmaskForQubitTargets[3]; + keysForNewValue[0] = key - bitmaskForQubitTargets[2]; + } else if ((key & bitmaskForQubitTargets[1]) == + bitmaskForQubitTargets[1]) { + mapFrom = 1; + keysForNewValue[3] = key + bitmaskForQubitTargets[2]; + keysForNewValue[2] = key ^ bitmaskForQubitTargets[3]; + keysForNewValue[1] = key; + keysForNewValue[0] = key - bitmaskForQubitTargets[1]; + } else { + keysForNewValue[3] = key + bitmaskForQubitTargets[3]; + keysForNewValue[2] = key + bitmaskForQubitTargets[2]; + keysForNewValue[1] = key + bitmaskForQubitTargets[1]; + keysForNewValue[0] = key; + } + + auto mapForThisQubit = gateMapping[mapFrom]; + for (int i = 0; i < 4; i++) { + if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { + newValues[keysForNewValue[i]] += valueToI * value; + } + } + } + + return newValues; + } + + /** + * @brief This method receives a single qubit gate mapping and a bitmask for + * target and ctrls. + * + * This method receives a single qubit gate mapping and a bitmask for target + * and ctrl qubits. The gate is applied to the valid qubit states. It returns + * the map which would be the qubit state after gate application. + * + * @param gateMapping The mapping representing the gate + * @param bitmaskForQubitTargets The bitmask of the qubit targets. I.e. 011, + * if zeroth and first qubit are targets. + * @param bitmaskForCtrls The bitmask of the positively controlling qubits. + * @return The qubit state after the gate has been applied. + */ + std::unordered_map> + getNewMappingForSingleQubitGate( + std::unordered_map>> + gateMapping, + std::unordered_map bitmaskForQubitTargets, + const unsigned int bitmaskForCtrls) { + std::unordered_map> newValues; + + for (const auto& [key, value] : amplitudeMap) { + if ((bitmaskForCtrls & key) != bitmaskForCtrls) { + newValues[key] += value; + continue; + } + + unsigned int mapFrom = 0; + std::vector keysForNewValue(2); + + if ((key & bitmaskForQubitTargets[1]) == bitmaskForQubitTargets[1]) { + mapFrom = 1; + keysForNewValue[1] = key; + keysForNewValue[0] = key - bitmaskForQubitTargets[1]; + } else { + keysForNewValue[1] = key + bitmaskForQubitTargets[1]; + keysForNewValue[0] = key; + } + + auto mapForThisQubit = gateMapping[mapFrom]; + for (int i = 0; i < 2; i++) { + if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { + newValues[keysForNewValue[i]] += valueToI * value; + } + } + } + + return newValues; + } + + /** + * @brief This method applies a measurement or reset to the qubits. + * + * This method applies a measurement or reset to the qubits. It returns the + * QuantumState in case the measurement was 0 and in case it was 1, alongside + * the respective probabilities. If a reset was applied, the qubit that was + * measured is set to 0. + * + * @param target The global index of the qubit to be measured. + * @return MeasurementResult, containing the probability for the result and + * the QuantumStates after measurement. + */ + MeasurementResult measureOrResetQubit(const unsigned int target, + const bool reset) { + const auto qubitMask = static_cast(pow(2, target) + 0.1); + + double probabilityZero = 0.0; + double probabilityOne = 0.0; + std::unordered_map> newValuesZeroRes; + std::unordered_map> newValuesOneRes; + + for (const auto& [key, value] : amplitudeMap) { + if ((qubitMask & key) == 0) { + probabilityZero += norm(value); + newValuesZeroRes.insert({key, value}); + } else { + if (reset) { + const unsigned int newKey = key ^ qubitMask; + } + probabilityOne += norm(value); + newValuesOneRes.insert({key, value}); + } + } + + if (std::abs(1.0 - probabilityZero - probabilityOne) > 1e-4) { + throw std::domain_error( + "Probabilities of 0 and 1 do not add up to one after measurement."); + } + auto globalKeysView = std::views::keys(globalToLocalQubitNumber); + std::vector globalKeys{globalKeysView.begin(), + globalKeysView.end()}; + auto stateZero = + std::make_shared(globalKeys, maxNonzeroAmplitudes); + stateZero->amplitudeMap = newValuesZeroRes; + stateZero->normalize(); + auto stateOne = + std::make_shared(globalKeys, maxNonzeroAmplitudes); + stateOne->amplitudeMap = newValuesOneRes; + stateOne->normalize(); + + MeasurementResult res = {}; + if (probabilityZero > 1e-4) { + ++res.size; + res.states[0] = {probabilityZero, stateZero}; + } + + if (probabilityOne > 1e-4) { + ++res.size; + res.states[1] = {probabilityOne, stateOne}; + } + + return res; + } + public: QuantumState(std::span globalQubitNumber, std::size_t maxNonzeroAmplitudes); - ~QuantumState(); + ~QuantumState() = default; void print(std::ostream& os) const; @@ -70,6 +274,11 @@ class QuantumState { [[nodiscard("QuantumState::== called but ignored")]] bool operator==(const QuantumState& that) const; + /** + * @brief This method normalizes the amplitudes of a state. + */ + void normalize(); + /** * @brief This method unifies two QuantumState. * @@ -93,13 +302,13 @@ class QuantumState { * * @param gate The gate to be applied. * @param targets A span of the global indices of the target qubits. - * @param posCtrls A span of the global indices of the ctrl qubits. + * @param ctrls A span of the global indices of the ctrl qubits. * @param params The parameter applied to the gate. * @throw std::domain_error If the number of nonzero amplitudes would exceed * maxNonzeroAmplitudes. */ void propagateGate(Operation* gate, std::span targets, - std::span posCtrls = {}, + std::span ctrls = {}, std::span params = {}); /** diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index e5093043f8..3794adce3a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -12,9 +12,230 @@ #define MQT_CORE_QUANTUMSTATEORTOP #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" + +#include + +#include +#include +#include +#include + namespace mlir::qco { -int QuantumState::inc(const int i) const { return i + q; } +QuantumState::QuantumState(const std::span globalQubitNumber, + const std::size_t maxNonzeroAmplitudes) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes) { + nQubits = globalQubitNumber.size(); + std::ranges::sort(globalQubitNumber); + unsigned int localQ = 0; + for (auto globalQ : globalQubitNumber) { + globalToLocalQubitNumber.insert({globalQ, localQ}); + ++localQ; + } + amplitudeMap = std::unordered_map>(); + amplitudeMap.insert({0, std::complex(1.0, 0.0)}); +} + +void QuantumState::print(std::ostream& os) const { os << this->toString(); } + +std::string QuantumState::toString() const { + std::string str; + bool first = true; + for (auto ordered = + std::map(this->amplitudeMap.begin(), this->amplitudeMap.end()); + auto const& [key, val] : ordered) { + if (!first) { + str += ", "; + } + first = false; + std::string cn = std::format("{:.2f}", val.real()); + if (val.imag() > 1e-4) { + cn += " + i" + std::format("{:.2f}", val.imag()); + } else if (val.imag() < -1e-4) { + cn += " - i" + std::format("{:.2f}", -val.imag()); + } + str += "|" + qubitStringToBinary(key) + "> -> " + cn; + } + + return str; +} + +bool QuantumState::operator==(const QuantumState& that) const { + if (this->nQubits != that.nQubits || + this->maxNonzeroAmplitudes != that.maxNonzeroAmplitudes || + this->globalToLocalQubitNumber != that.globalToLocalQubitNumber) { + return false; + } + + return std::ranges::all_of( + this->amplitudeMap, + [&](const std::pair>& p) { + auto [key, val] = p; + return that.amplitudeMap.contains(key) && + abs(val - that.amplitudeMap.at(key)) < 1e-4; + }); +} + +void QuantumState::normalize() { + double denominator = 0.0; + for (const auto& value : amplitudeMap | std::views::values) { + denominator += norm(value); + } + for (const auto& key : amplitudeMap | std::views::keys) { + amplitudeMap[key] /= std::sqrt(denominator); + } +} + +QuantumState QuantumState::unify(const QuantumState& that) { + // Check if future state would be too large + if (amplitudeMap.size() * that.amplitudeMap.size() > maxNonzeroAmplitudes) { + throw std::domain_error("Number of nonzero amplitudes too high. State " + "needs to be treated as TOP."); + } + + std::unordered_map newGlobalToLocalMapping; + std::unordered_map> newAmplitudes; + + // Create the new global indices + const auto globalIndicesThis = std::views::keys(globalToLocalQubitNumber); + const auto globalIndicesThat = + std::views::keys(that.globalToLocalQubitNumber); + std::vector combinedGlobalIndices; + combinedGlobalIndices.reserve(globalToLocalQubitNumber.size() + + that.globalToLocalQubitNumber.size()); + std::ranges::copy(globalIndicesThis, + std::back_inserter(combinedGlobalIndices)); + std::ranges::copy(globalIndicesThat, + std::back_inserter(combinedGlobalIndices)); + std::ranges::sort(combinedGlobalIndices); + + for (unsigned int i = 0; i < combinedGlobalIndices.size(); ++i) { + newGlobalToLocalMapping[combinedGlobalIndices[i]] = i; + } + + // Create mappings from the old to the new indices + std::unordered_map oldToNewIndicesThis; + std::unordered_map oldToNewIndicesThat; + for (const auto& keyThis : globalToLocalQubitNumber | std::views::keys) { + oldToNewIndicesThis[globalToLocalQubitNumber.at(keyThis)] = + newGlobalToLocalMapping.at(keyThis); + } + for (const auto& keyThat : that.globalToLocalQubitNumber | std::views::keys) { + oldToNewIndicesThis[that.globalToLocalQubitNumber.at(keyThat)] = + newGlobalToLocalMapping.at(keyThat); + } + + // Create new amplitude map + for (const auto& [keyThis, valThis] : amplitudeMap) { + for (const auto& [keyThat, valThat] : that.amplitudeMap) { + unsigned int currentQubitState = 0; + + for (const auto& indicesOfThis : oldToNewIndicesThis | std::views::keys) { + unsigned int bitOfQubitState = pow(2, indicesOfThis); + if ((keyThis & bitOfQubitState) == bitOfQubitState) { + currentQubitState += pow(2, oldToNewIndicesThis.at(indicesOfThis)); + } + } + + for (const auto& indicesOfThat : oldToNewIndicesThat | std::views::keys) { + unsigned int bitOfQubitState = pow(2, indicesOfThat); + if ((keyThat & bitOfQubitState) == bitOfQubitState) { + currentQubitState += pow(2, oldToNewIndicesThis.at(indicesOfThat)); + } + } + newAmplitudes[currentQubitState] = valThis * valThat; + } + } + auto newState = QuantumState(combinedGlobalIndices, maxNonzeroAmplitudes); + newState.amplitudeMap = newAmplitudes; + newState.globalToLocalQubitNumber = newGlobalToLocalMapping; + + return newState; +} + +void QuantumState::propagateGate(Operation* gate, + std::span targets, + std::span ctrls, + std::span params) { + const auto gateMapping = getQubitMappingOfGates(gate, params); + + unsigned int ctrlMask = 0; + for (unsigned int const posCtrl : ctrls) { + ctrlMask += static_cast(pow(2, posCtrl) + 0.1); + } + + std::unordered_map> newValues; + std::unordered_map bitmaskForQubitTargets; + if (targets.size() == 2) { + bitmaskForQubitTargets.insert({3, pow(2, targets[1]) + pow(2, targets[0])}); + bitmaskForQubitTargets.insert({2, pow(2, targets[0])}); + bitmaskForQubitTargets.insert({1, pow(2, targets[1])}); + } else { + bitmaskForQubitTargets.insert({1, pow(2, targets[0])}); + } + + if (targets.size() == 2) { + newValues = getNewMappingForTwoQubitGate(gateMapping, + bitmaskForQubitTargets, ctrlMask); + } else if (targets.size() == 1) { + newValues = getNewMappingForSingleQubitGate( + gateMapping, bitmaskForQubitTargets, ctrlMask); + } + + amplitudeMap.clear(); + for (const auto& [key, value] : newValues) { + if (norm(value) > 1e-4) { + amplitudeMap.insert({key, value}); + } + } + if (amplitudeMap.size() > maxNonzeroAmplitudes) { + throw std::domain_error("Number of nonzero amplitudes too high. State " + "needs to be treated as TOP."); + } +} + +MeasurementResult QuantumState::measureQubit(const unsigned int target) { + return measureOrResetQubit(target, false); +} + +MeasurementResult QuantumState::resetQubit(const unsigned int target) { + return measureOrResetQubit(target, true); +} + +bool QuantumState::isQubitAlwaysOne(unsigned int q) const { + const auto mask = + static_cast(pow(2, static_cast(q)) + 0.1); + return std::ranges::all_of( + amplitudeMap | std::views::keys, + [mask](auto qubits) { return (qubits & mask) == mask; }); +} + +bool QuantumState::isQubitAlwaysZero(unsigned int q) const { + const auto mask = + static_cast(pow(2, static_cast(q)) + 0.1); + return std::ranges::all_of( + amplitudeMap | std::views::keys, + [mask](auto qubits) { return (qubits & mask) == 0; }); +} +bool QuantumState::hasAlwaysZeroAmplitude(const std::span qubits, + const unsigned int value) const { + unsigned int localValue = 0; + unsigned int mask = 0; + for (unsigned int i = 0; i < qubits.size(); ++i) { + const unsigned int currentPower = + static_cast(pow(2, i) + 0.1); + const unsigned int qubitPower = + static_cast(pow(2, qubits[i]) + 0.1); + mask += qubitPower; + if ((value & currentPower) != 0) { + localValue += qubitPower; + } + } + return std::ranges::all_of( + amplitudeMap | std::views::keys, + [localValue, mask](auto qbit) { return (qbit & mask) != localValue; }); +} } // namespace mlir::qco From fca582975e0a387be960edb465074e960a117be5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 23:09:47 +0200 Subject: [PATCH 069/131] :recycle: Refactored QuantumState.hpp and QuantumState.cpp, assisted-by GPT OSS via KI:connect --- .../ConstantPropagation/QuantumState.hpp | 143 +++++++----------- .../ConstantPropagation/QuantumState.cpp | 17 +-- 2 files changed, 52 insertions(+), 108 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 304a8edb33..a7beacd4ef 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -61,111 +62,55 @@ class QuantumState { std::unordered_map> amplitudeMap; std::string qubitStringToBinary(unsigned int q) const { - std::string str; - for (int i = static_cast(nQubits) - 1; i >= 0; i--) { - if (const auto currentDigit = static_cast(pow(2, i)); - q & currentDigit) { - str += "1"; - q -= currentDigit; - } else { - str += "0"; - } + std::string result; + result.reserve(nQubits); + + for (std::size_t i = nQubits; i > 0; --i) { + result.push_back((q >> i & 1U) != 0 ? '1' : '0'); } - return str; + return result; } /** - * @brief This method receives a two qubit gate mapping and a bitmask for - * targets and ctrls. + * @brief Create a bitstring that has ones exactly at the given positions * - * This method receives a two qubit gate mapping and a bitmask for target and - * ctrl qubits. The gate is applied to the valid qubit states. It returns the - * map which would be the qubit state after gate application. - * - * @param gateMapping The mapping representing the gate - * @param bitmaskForQubitTargets The bitmask of the qubit targets. I.e. 011, - * if zeroth and first qubit are targets. - * @param bitmaskForCtrls The bitmask of the positively controlling qubits. - * @return The qubit state after the gate has been applied. + * @param positions The positions at which the bits should be one. + * @return The finished bitstring. */ - std::unordered_map> - getNewMappingForTwoQubitGate( - std::unordered_map>> - gateMapping, - std::unordered_map bitmaskForQubitTargets, - const unsigned int bitmaskForCtrls) { - std::unordered_map> newValues; - - for (const auto& [key, value] : amplitudeMap) { - if ((bitmaskForCtrls & key) != bitmaskForCtrls) { - newValues[key] += value; - continue; - } - - unsigned int mapFrom = 0; - std::vector keysForNewValue(4); - - if ((key & bitmaskForQubitTargets[3]) == bitmaskForQubitTargets[3]) { - mapFrom = 3; - keysForNewValue[3] = key; - keysForNewValue[2] = key - bitmaskForQubitTargets[1]; - keysForNewValue[1] = key - bitmaskForQubitTargets[2]; - keysForNewValue[0] = key - bitmaskForQubitTargets[3]; - } else if ((key & bitmaskForQubitTargets[2]) == - bitmaskForQubitTargets[2]) { - mapFrom = 2; - keysForNewValue[3] = key + bitmaskForQubitTargets[1]; - keysForNewValue[2] = key; - keysForNewValue[1] = key ^ bitmaskForQubitTargets[3]; - keysForNewValue[0] = key - bitmaskForQubitTargets[2]; - } else if ((key & bitmaskForQubitTargets[1]) == - bitmaskForQubitTargets[1]) { - mapFrom = 1; - keysForNewValue[3] = key + bitmaskForQubitTargets[2]; - keysForNewValue[2] = key ^ bitmaskForQubitTargets[3]; - keysForNewValue[1] = key; - keysForNewValue[0] = key - bitmaskForQubitTargets[1]; - } else { - keysForNewValue[3] = key + bitmaskForQubitTargets[3]; - keysForNewValue[2] = key + bitmaskForQubitTargets[2]; - keysForNewValue[1] = key + bitmaskForQubitTargets[1]; - keysForNewValue[0] = key; - } - - auto mapForThisQubit = gateMapping[mapFrom]; - for (int i = 0; i < 4; i++) { - if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { - newValues[keysForNewValue[i]] += valueToI * value; - } - } + static constexpr std::size_t + makeTargetMask(std::span positions) noexcept { + std::size_t mask = 0; + for (unsigned int p : positions) { + mask |= 1U << p; } - - return newValues; + return mask; } /** - * @brief This method receives a single qubit gate mapping and a bitmask for - * target and ctrls. + * @brief This method receives a gate mapping and a bitmask ctrls and the + * positions of the one (or two) qubit targets. * - * This method receives a single qubit gate mapping and a bitmask for target - * and ctrl qubits. The gate is applied to the valid qubit states. It returns - * the map which would be the qubit state after gate application. + * This method receives a two qubit gate mapping and a bitmask for ctrl qubits + * and a span with the first target qubit position at first position and the + * potential second target qubit at second position. The gate is applied to + * the valid qubit states. It returns the map which would be the qubit state + * after gate application. * * @param gateMapping The mapping representing the gate - * @param bitmaskForQubitTargets The bitmask of the qubit targets. I.e. 011, - * if zeroth and first qubit are targets. + * @param positionOfTargetQubits The position of the qubit targets. * @param bitmaskForCtrls The bitmask of the positively controlling qubits. * @return The qubit state after the gate has been applied. */ std::unordered_map> - getNewMappingForSingleQubitGate( + getNewMappingFromQubitGate( std::unordered_map>> gateMapping, - std::unordered_map bitmaskForQubitTargets, + std::span positionOfTargetQubits, const unsigned int bitmaskForCtrls) { std::unordered_map> newValues; + const auto numberOfTargetValues = static_cast( + pow(2, static_cast(positionOfTargetQubits.size())) + 0.1); for (const auto& [key, value] : amplitudeMap) { if ((bitmaskForCtrls & key) != bitmaskForCtrls) { @@ -174,19 +119,33 @@ class QuantumState { } unsigned int mapFrom = 0; - std::vector keysForNewValue(2); - if ((key & bitmaskForQubitTargets[1]) == bitmaskForQubitTargets[1]) { - mapFrom = 1; - keysForNewValue[1] = key; - keysForNewValue[0] = key - bitmaskForQubitTargets[1]; - } else { - keysForNewValue[1] = key + bitmaskForQubitTargets[1]; - keysForNewValue[0] = key; + std::vector keysForNewValue(numberOfTargetValues); + + // Find the target keys from the current keys + for (unsigned int i = 0; i < numberOfTargetValues; ++i) { + keysForNewValue[i] = key; + auto maskFirstPos = 1U << positionOfTargetQubits[0]; + if (i % 2 == 1) { + keysForNewValue[i] |= maskFirstPos; + } else { + keysForNewValue[i] &= ~maskFirstPos; + } + if (numberOfTargetValues > 2) { + auto maskSecondPos = 1U << positionOfTargetQubits[1]; + if (i > 2) { + keysForNewValue[i] |= maskSecondPos; + } else { + keysForNewValue[i] &= ~maskSecondPos; + } + } + if (keysForNewValue[i] == key) { + mapFrom = i; + } } auto mapForThisQubit = gateMapping[mapFrom]; - for (int i = 0; i < 2; i++) { + for (int i = 0; i < 4; i++) { if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { newValues[keysForNewValue[i]] += valueToI * value; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 3794adce3a..1adc85288f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -166,22 +166,7 @@ void QuantumState::propagateGate(Operation* gate, } std::unordered_map> newValues; - std::unordered_map bitmaskForQubitTargets; - if (targets.size() == 2) { - bitmaskForQubitTargets.insert({3, pow(2, targets[1]) + pow(2, targets[0])}); - bitmaskForQubitTargets.insert({2, pow(2, targets[0])}); - bitmaskForQubitTargets.insert({1, pow(2, targets[1])}); - } else { - bitmaskForQubitTargets.insert({1, pow(2, targets[0])}); - } - - if (targets.size() == 2) { - newValues = getNewMappingForTwoQubitGate(gateMapping, - bitmaskForQubitTargets, ctrlMask); - } else if (targets.size() == 1) { - newValues = getNewMappingForSingleQubitGate( - gateMapping, bitmaskForQubitTargets, ctrlMask); - } + newValues = getNewMappingFromQubitGate(gateMapping, targets, ctrlMask); amplitudeMap.clear(); for (const auto& [key, value] : newValues) { From dfb3c792944d0d1a61f609b4a65865a75a8565dd Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 23:29:23 +0200 Subject: [PATCH 070/131] =?UTF-8?q?=E2=9C=85=20Added=20tests=20for=20Quant?= =?UTF-8?q?umState?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConstantPropagation/GateToMap.h | 1 + .../ConstantPropagation/test_quantumState.cpp | 351 +++++++++++++++++- 2 files changed, 346 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h index c3903119b7..6a2326e6ba 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -10,6 +10,7 @@ #pragma once +#include #ifndef MQT_CORE_GATETOMAP_H #define MQT_CORE_GATETOMAP_H diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 4ff1a5de92..72709ac11a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -8,14 +8,353 @@ * Licensed under the MIT License */ -#include "gtest/gtest.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" -namespace mlir::qco { +#include +#include -TEST(CPTest, cpTest) { - auto q = QuantumState(); - EXPECT_EQ(q.inc(3), 4); +#include +#include +#include +#include +#include +#include + +using namespace mlir::qco; + +class QuantumStateTest : public ::testing::Test { +protected: + void SetUp() override {} + + void TearDown() override {} +}; + +TEST_F(QuantumStateTest, ApplyHGate) { + std::vector qubits = {0}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), qubits); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0> -> 0.71, |1> -> 0.71")); } -} // namespace mlir::qco +TEST_F(QuantumStateTest, ApplyHGateToThirdQubit) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targets = {2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targets); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0000> -> 0.71, |0100> -> 0.71")); +} + +TEST_F(QuantumStateTest, ApplyHHGateToThirdQubit) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targets = {2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targets); + qState.propagateGate(HOp(), targets); + + EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 1")); +} + +TEST_F(QuantumStateTest, ApplyHZGateToThirdQubit) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targets = {2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targets); + qState.propagateGate(ZOp(), targets); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0000> -> 0.71, |0100> -> -0.71")); +} + +TEST_F(QuantumStateTest, ApplyHZHGateToThirdQubit) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targets = {2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targets); + qState.propagateGate(ZOp(), targets); + qState.propagateGate(HOp(), targets); + + EXPECT_THAT(qState.toString(), testing::HasSubstr("|0100> -> 1")); +} + +TEST_F(QuantumStateTest, ApplyHGatesToTwoQubits) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetZero = {0}; + std::vector targetTwo = {2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetTwo); + qState.propagateGate(XOp(), targetZero); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0001> -> 0.71, |0101> -> 0.71")); +} + +TEST_F(QuantumStateTest, ApplyParametrizedGateToThirdQubit) { + std::vector qubits = {0, 1, 2, 3}; + std::vector target = {2}; + std::vector params = {1, 0.5, 2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), target); + qState.propagateGate(UOp(), target, {}, params); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr("|0000> -> 0.76 - i0.31, |0100> -> -0.20 + i0.53")); +} + +TEST_F(QuantumStateTest, ApplyTwoQubitGate) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targets = {2, 1}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetOne); + qState.propagateGate(SOp(), targetOne); + qState.propagateGate(HOp(), targetTwo); + qState.propagateGate(TdgOp(), targetTwo); + qState.propagateGate(DCXOp(), targets); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr("|0000> -> 0.35 + i0.35, |0010> -> 0.35 - i0.35, " + "|0100> -> 0.50, |0110> -> 0.00 + i0.50")); +} + +TEST_F(QuantumStateTest, ApplyTwoQubitGateReversedOrd) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targets = {1, 2}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetOne); + qState.propagateGate(SOp(), targetOne); + qState.propagateGate(HOp(), targetTwo); + qState.propagateGate(TdgOp(), targetTwo); + qState.propagateGate(DCXOp(), targets); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr("|0000> -> 0.35 + i0.35, |0010> -> 0.50, " + "|0100> -> 0.00 + i0.50, |0110> -> 0.35 - i0.35")); +} + +TEST_F(QuantumStateTest, ApplySwapGate) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetOne = {1}; + std::vector targets = {1, 3}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetOne); + qState.propagateGate(SWAPOp(), targets); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0000> -> 0.71, |1000> -> 0.71")); +} + +TEST_F(QuantumStateTest, ApplyControlledGate) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetOne = {1}; + std::vector targetThree = {3}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetOne); + qState.propagateGate(XOp(), targetThree); + qState.propagateGate(XOp(), targetThree, targetOne); + + EXPECT_THAT(qState.toString(), + testing::HasSubstr("|0010> -> 0.71, |1000> -> 0.71")); +} + +TEST_F(QuantumStateTest, ApplyPosNegControlledGate) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + std::vector targets = {0, 1}; + std::vector params = {2.0}; + QuantumState qState = QuantumState(qubits, 8); + qState.propagateGate(HOp(), targetZero); + qState.propagateGate(HOp(), targetOne); + qState.propagateGate(HOp(), targetTwo); + qState.propagateGate(XOp(), targetThree, targets, params); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr("|0000> -> 0.35, |0001> -> 0.35, |0010> -> 0.35, " + "|0100> -> 0.35, |0101> -> 0.35, |0110> -> 0.35, " + "|0111> -> 0.35, |1011> -> 0.35")); +} + +TEST_F(QuantumStateTest, ApplyControlledTwoQubitGate) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + std::vector targets = {2, 1}; + QuantumState qState = QuantumState(qubits, 4); + qState.propagateGate(HOp(), targetThree); + qState.propagateGate(HOp(), targetTwo); + qState.propagateGate(SWAPOp(), targets, targetThree); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr( + "|0000> -> 0.50, |0100> -> 0.50, |1000> -> 0.50, |1010> -> 0.50")); +} + +TEST_F(QuantumStateTest, propagateGateCheckErrorIfTwoManyAmplitudesAreNonzero) { + std::vector qubits = {0, 1, 2, 3}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + QuantumState qState = QuantumState(qubits, 2); + qState.propagateGate(HOp(), targetThree); + qState.propagateGate(XOp(), targetTwo, targetThree); + + EXPECT_THROW(qState.propagateGate(HOp(), targetTwo);, std::domain_error); +} + +TEST_F(QuantumStateTest, doMeasurementWithZeroResult) { + std::vector qubit = {0}; + QuantumState qState = QuantumState(qubit, 2); + MeasurementResult const res = qState.measureQubit(0); + + EXPECT_TRUE(res.size == 1); + auto [probability, qs] = res.states.at(0); + EXPECT_TRUE(qState == *qs.get()); + EXPECT_DOUBLE_EQ(probability, 1); +} + +TEST_F(QuantumStateTest, doMeasurementWithOneResult) { + std::vector qubit = {0}; + std::vector qubitsOne = {0, 2, 4}; + std::vector qubitsTwo = {1, 3}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + std::vector targetFour = {4}; + QuantumState qState = QuantumState(qubit, 2); + qState.propagateGate(XOp(), qubit); + MeasurementResult const res = qState.measureQubit(0); + + EXPECT_TRUE(res.size == 1); + auto [probability, qs] = res.states.at(1); + EXPECT_TRUE(qState == *qs.get()); + EXPECT_DOUBLE_EQ(probability, 1); +} + +TEST_F(QuantumStateTest, doMeasurementWithTwoResults) { + std::vector qubits = {0, 1}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + QuantumState qState = QuantumState(qubits, 2); + qState.propagateGate(HOp(), targetZero); + qState.propagateGate(XOp(), targetOne, targetZero); + MeasurementResult const res = qState.measureQubit(0); + + QuantumState const zeroReference = QuantumState(qubits, 2); + QuantumState oneReference = QuantumState(qubits, 2); + oneReference.propagateGate(XOp(), targetZero); + oneReference.propagateGate(XOp(), targetOne); + + EXPECT_TRUE(res.size == 2); + auto [probabilityZero, qsZero] = res.states.at(0); + EXPECT_TRUE(zeroReference == *qsZero.get()); + EXPECT_DOUBLE_EQ(probabilityZero, 0.5); + auto [probabilityOne, qsOne] = res.states.at(1); + EXPECT_TRUE(oneReference == *qsOne.get()); + EXPECT_DOUBLE_EQ(probabilityOne, 0.5); +} + +TEST_F(QuantumStateTest, doResetWithOnlyZeros) { + std::vector qubit = {0}; + QuantumState qState = QuantumState(qubit, 2); + MeasurementResult const res = qState.resetQubit(0); + + EXPECT_TRUE(res.size == 1); + auto [probability, qs] = res.states.at(0); + EXPECT_TRUE(qState == *qs.get()); + EXPECT_DOUBLE_EQ(probability, 1); +} + +TEST_F(QuantumStateTest, doResetWithOnlyOnes) { + std::vector qubit = {0}; + QuantumState qState = QuantumState(qubit, 2); + qState.propagateGate(XOp(), qubit); + MeasurementResult const res = qState.resetQubit(0); + + QuantumState const refState = QuantumState(qubit, 2); + + EXPECT_TRUE(res.size == 1); + auto [probability, qs] = res.states.at(1); + EXPECT_TRUE(refState == *qs.get()); + EXPECT_DOUBLE_EQ(probability, 1); +} + +TEST_F(QuantumStateTest, doResetWithZerosAndOnes) { + std::vector qubits = {0, 1}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + QuantumState qState = QuantumState(qubits, 2); + qState.propagateGate(HOp(), targetZero); + qState.propagateGate(XOp(), targetOne, targetZero); + MeasurementResult const res = qState.resetQubit(0); + + QuantumState const zeroReference = QuantumState(targetZero, 2); + QuantumState oneReference = QuantumState(targetZero, 2); + oneReference.propagateGate(XOp(), targetOne); + + EXPECT_TRUE(res.size == 2); + auto [probabilityZero, qsZero] = res.states.at(0); + EXPECT_DOUBLE_EQ(probabilityZero, 0.5); + auto [probabilityOne, qsOne] = res.states.at(1); + EXPECT_DOUBLE_EQ(probabilityOne, 0.5); + EXPECT_TRUE(*qsZero == oneReference || *qsZero == oneReference); + EXPECT_TRUE(*qsOne == zeroReference || *qsOne == zeroReference); +} + +TEST_F(QuantumStateTest, unifyTwoQuantumStates) { + std::vector qubitsOne = {0, 2, 4}; + std::vector qubitsTwo = {1, 3}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + std::vector targetFour = {4}; + QuantumState qState1 = QuantumState(qubitsOne, 10); + qState1.propagateGate(HOp(), targetFour); + qState1.propagateGate(XOp(), targetTwo, targetFour); + qState1.propagateGate(XOp(), targetZero, targetTwo); + + QuantumState qState2 = QuantumState(qubitsTwo, 10); + qState2.propagateGate(HOp(), targetThree); + qState2.propagateGate(XOp(), targetOne, targetThree); + + const QuantumState unified = qState1.unify(qState2); + + EXPECT_THAT(unified.toString(), + testing::HasSubstr("|00000> -> 0.50, |01010> -> 0.50, " + "|10101> -> 0.50, |11111> -> 0.50")); +} + +TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { + std::vector qubitsOne = {0, 2, 4}; + std::vector qubitsTwo = {1, 3}; + std::vector targetZero = {0}; + std::vector targetOne = {1}; + std::vector targetTwo = {2}; + std::vector targetThree = {3}; + std::vector targetFour = {4}; + QuantumState qState1 = QuantumState(qubitsOne, 3); + qState1.propagateGate(HOp(), targetFour); + qState1.propagateGate(XOp(), targetTwo, targetFour); + qState1.propagateGate(XOp(), targetZero, targetTwo); + + QuantumState qState2 = QuantumState(qubitsTwo, 3); + qState2.propagateGate(HOp(), targetThree); + qState2.propagateGate(XOp(), targetOne, targetThree); + + EXPECT_THROW(qState1.unify(qState2);, std::domain_error); +} From edbde78d53818b8a7d55e287f49aa300a67be94d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 09:41:24 +0200 Subject: [PATCH 071/131] :white_check_mark: Passes first test --- .../ConstantPropagation/QuantumState.hpp | 4 +-- .../ConstantPropagation/QuantumState.cpp | 2 +- .../Transforms/Optimizations/CMakeLists.txt | 3 ++- .../ConstantPropagation/test_quantumState.cpp | 25 ++++++++++++++++--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index a7beacd4ef..da649da267 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -66,7 +66,7 @@ class QuantumState { result.reserve(nQubits); for (std::size_t i = nQubits; i > 0; --i) { - result.push_back((q >> i & 1U) != 0 ? '1' : '0'); + result.push_back((q >> (i - 1) & 1U) != 0 ? '1' : '0'); } return result; } @@ -145,7 +145,7 @@ class QuantumState { } auto mapForThisQubit = gateMapping[mapFrom]; - for (int i = 0; i < 4; i++) { + for (unsigned int i = 0; i < numberOfTargetValues; i++) { if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { newValues[keysForNewValue[i]] += valueToI * value; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 1adc85288f..dad92f1609 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -171,7 +171,7 @@ void QuantumState::propagateGate(Operation* gate, amplitudeMap.clear(); for (const auto& [key, value] : newValues) { if (norm(value) > 1e-4) { - amplitudeMap.insert({key, value}); + amplitudeMap[key] = value; } } if (amplitudeMap.size() > maxNonzeroAmplitudes) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 8d48d46273..1551c633fe 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -13,7 +13,8 @@ add_executable(${target_name} test_qco_hadamard_lifting.cpp target_link_libraries( ${target_name} - PRIVATE GTest::gtest_main + PRIVATE GTest::gmock + GTest::gtest_main MLIRQCOProgramBuilder MLIRQCOTransforms MLIRQCOUtils diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 72709ac11a..a8c4a73c9e 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -8,15 +8,16 @@ * Licensed under the MIT License */ +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" #include #include -#include #include -#include +#include +#include #include #include #include @@ -31,9 +32,25 @@ class QuantumStateTest : public ::testing::Test { }; TEST_F(QuantumStateTest, ApplyHGate) { + mlir::MLIRContext context; + QCOProgramBuilder programBuilder(&context); + QCOProgramBuilder referenceBuilder(&context); + + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(1); + + auto h = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); + std::vector qubits = {0}; QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), qubits); + qState.propagateGate(h.getOperation(), qubits); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0> -> 0.71, |1> -> 0.71")); @@ -356,5 +373,5 @@ TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { qState2.propagateGate(HOp(), targetThree); qState2.propagateGate(XOp(), targetOne, targetThree); - EXPECT_THROW(qState1.unify(qState2);, std::domain_error); + EXPECT_THROW(auto qs = qState1.unify(qState2), std::domain_error); } From 24903c130778219b3c170bc88f57ea79e26713af Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 16:09:24 +0200 Subject: [PATCH 072/131] :white_check_mark: Passed tests --- .../ConstantPropagation/QuantumState.hpp | 9 +- .../ConstantPropagation/QuantumState.cpp | 15 +- .../ConstantPropagation/test_quantumState.cpp | 394 ++++++++---------- 3 files changed, 195 insertions(+), 223 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index da649da267..7136f5f1a2 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -133,7 +133,7 @@ class QuantumState { } if (numberOfTargetValues > 2) { auto maskSecondPos = 1U << positionOfTargetQubits[1]; - if (i > 2) { + if (i > 1) { keysForNewValue[i] |= maskSecondPos; } else { keysForNewValue[i] &= ~maskSecondPos; @@ -147,7 +147,7 @@ class QuantumState { auto mapForThisQubit = gateMapping[mapFrom]; for (unsigned int i = 0; i < numberOfTargetValues; i++) { if (auto valueToI = mapForThisQubit[i]; abs(valueToI) > 1e-4) { - newValues[keysForNewValue[i]] += valueToI * value; + newValues[keysForNewValue.at(i)] += valueToI * value; } } } @@ -177,15 +177,16 @@ class QuantumState { std::unordered_map> newValuesOneRes; for (const auto& [key, value] : amplitudeMap) { + unsigned int newKey = key; if ((qubitMask & key) == 0) { probabilityZero += norm(value); newValuesZeroRes.insert({key, value}); } else { if (reset) { - const unsigned int newKey = key ^ qubitMask; + newKey = key ^ qubitMask; } probabilityOne += norm(value); - newValuesOneRes.insert({key, value}); + newValuesOneRes[newKey] = value; } } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index dad92f1609..d92c560903 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -122,7 +122,7 @@ QuantumState QuantumState::unify(const QuantumState& that) { newGlobalToLocalMapping.at(keyThis); } for (const auto& keyThat : that.globalToLocalQubitNumber | std::views::keys) { - oldToNewIndicesThis[that.globalToLocalQubitNumber.at(keyThat)] = + oldToNewIndicesThat[that.globalToLocalQubitNumber.at(keyThat)] = newGlobalToLocalMapping.at(keyThat); } @@ -141,7 +141,7 @@ QuantumState QuantumState::unify(const QuantumState& that) { for (const auto& indicesOfThat : oldToNewIndicesThat | std::views::keys) { unsigned int bitOfQubitState = pow(2, indicesOfThat); if ((keyThat & bitOfQubitState) == bitOfQubitState) { - currentQubitState += pow(2, oldToNewIndicesThis.at(indicesOfThat)); + currentQubitState += pow(2, oldToNewIndicesThat.at(indicesOfThat)); } } newAmplitudes[currentQubitState] = valThis * valThat; @@ -162,11 +162,16 @@ void QuantumState::propagateGate(Operation* gate, unsigned int ctrlMask = 0; for (unsigned int const posCtrl : ctrls) { - ctrlMask += static_cast(pow(2, posCtrl) + 0.1); + ctrlMask += static_cast( + pow(2, globalToLocalQubitNumber.at(posCtrl)) + 0.1); + } + std::vector localTargets; + for (unsigned int q : targets) { + localTargets.push_back(globalToLocalQubitNumber.at(q)); } - std::unordered_map> newValues; - newValues = getNewMappingFromQubitGate(gateMapping, targets, ctrlMask); + std::unordered_map> newValues = + getNewMappingFromQubitGate(gateMapping, localTargets, ctrlMask); amplitudeMap.clear(); for (const auto& [key, value] : newValues) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index a8c4a73c9e..48c7cb6590 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -24,99 +24,121 @@ using namespace mlir::qco; -class QuantumStateTest : public ::testing::Test { +class QuantumStateTest : public testing::Test { protected: - void SetUp() override {} + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + std::vector fourQubits = {0, 1, 2, 3}; + std::vector vectorZero = {0}; + std::vector vectorOne = {1}; + std::vector vectorTwo = {2}; + std::vector vectorThree = {3}; + std::vector vectorFour = {4}; + std::vector vectorZeroOne = {0, 1}; + std::vector vectorOneThree = {1, 3}; + std::vector vectorTwoOne = {2, 1}; + std::vector vectorZeroTwoFour = {0, 2, 4}; + + HOp hOp; + XOp xOp; + ZOp zOp; + SOp sOp; + TdgOp tdgOp; + UOp uOp; + DCXOp dcxOp; + SWAPOp swapOp; + + QuantumStateTest() : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(4); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + sOp = SOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + uOp = UOp::create(programBuilder, programBuilder.getLoc(), {q[0].getType()}, + {q[0], q[1], q[2], q[3]}); + tdgOp = TdgOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[0]); + dcxOp = DCXOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1]); + swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1]); + } void TearDown() override {} }; TEST_F(QuantumStateTest, ApplyHGate) { - mlir::MLIRContext context; - QCOProgramBuilder programBuilder(&context); - QCOProgramBuilder referenceBuilder(&context); - - mlir::DialectRegistry registry; - registry.insert(); - context.appendDialectRegistry(registry); - context.loadAllAvailableDialects(); - - programBuilder.initialize(); - referenceBuilder.initialize(); - - auto q = programBuilder.allocQubitRegister(1); - - auto h = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); - - std::vector qubits = {0}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(h.getOperation(), qubits); + auto qState = QuantumState(vectorZero, 4); + qState.propagateGate(hOp.getOperation(), vectorZero); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0> -> 0.71, |1> -> 0.71")); } TEST_F(QuantumStateTest, ApplyHGateToThirdQubit) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targets = {2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targets); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 0.71, |0100> -> 0.71")); } TEST_F(QuantumStateTest, ApplyHHGateToThirdQubit) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targets = {2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targets); - qState.propagateGate(HOp(), targets); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(hOp.getOperation(), vectorTwo); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 1")); } TEST_F(QuantumStateTest, ApplyHZGateToThirdQubit) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targets = {2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targets); - qState.propagateGate(ZOp(), targets); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(zOp.getOperation(), vectorTwo); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 0.71, |0100> -> -0.71")); } TEST_F(QuantumStateTest, ApplyHZHGateToThirdQubit) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targets = {2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targets); - qState.propagateGate(ZOp(), targets); - qState.propagateGate(HOp(), targets); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(zOp.getOperation(), vectorTwo); + qState.propagateGate(hOp.getOperation(), vectorTwo); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0100> -> 1")); } TEST_F(QuantumStateTest, ApplyHGatesToTwoQubits) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetZero = {0}; - std::vector targetTwo = {2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetTwo); - qState.propagateGate(XOp(), targetZero); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(xOp.getOperation(), vectorZero); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0001> -> 0.71, |0101> -> 0.71")); } TEST_F(QuantumStateTest, ApplyParametrizedGateToThirdQubit) { - std::vector qubits = {0, 1, 2, 3}; - std::vector target = {2}; std::vector params = {1, 0.5, 2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), target); - qState.propagateGate(UOp(), target, {}, params); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(uOp.getOperation(), vectorTwo, {}, params); EXPECT_THAT( qState.toString(), @@ -124,96 +146,73 @@ TEST_F(QuantumStateTest, ApplyParametrizedGateToThirdQubit) { } TEST_F(QuantumStateTest, ApplyTwoQubitGate) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targets = {2, 1}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetOne); - qState.propagateGate(SOp(), targetOne); - qState.propagateGate(HOp(), targetTwo); - qState.propagateGate(TdgOp(), targetTwo); - qState.propagateGate(DCXOp(), targets); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorOne); + qState.propagateGate(sOp.getOperation(), vectorOne); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(tdgOp.getOperation(), vectorTwo); + qState.propagateGate(dcxOp.getOperation(), vectorTwoOne); EXPECT_THAT( qState.toString(), - testing::HasSubstr("|0000> -> 0.35 + i0.35, |0010> -> 0.35 - i0.35, " - "|0100> -> 0.50, |0110> -> 0.00 + i0.50")); + testing::HasSubstr("|0000> -> 0.50, |0010> -> 0.35 - i0.35, " + "|0100> -> 0.35 + i0.35, |0110> -> 0.00 + i0.50")); } TEST_F(QuantumStateTest, ApplyTwoQubitGateReversedOrd) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targets = {1, 2}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetOne); - qState.propagateGate(SOp(), targetOne); - qState.propagateGate(HOp(), targetTwo); - qState.propagateGate(TdgOp(), targetTwo); - qState.propagateGate(DCXOp(), targets); + std::vector vectorOneTwo = {1, 2}; + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorOne); + qState.propagateGate(sOp.getOperation(), vectorOne); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(tdgOp.getOperation(), vectorTwo); + qState.propagateGate(dcxOp.getOperation(), vectorOneTwo); EXPECT_THAT( qState.toString(), - testing::HasSubstr("|0000> -> 0.35 + i0.35, |0010> -> 0.50, " - "|0100> -> 0.00 + i0.50, |0110> -> 0.35 - i0.35")); + testing::HasSubstr("|0000> -> 0.50, |0010> -> 0.35 + i0.35, |0100> -> " + "0.00 + i0.50, |0110> -> 0.35 - i0.35")); } TEST_F(QuantumStateTest, ApplySwapGate) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetOne = {1}; - std::vector targets = {1, 3}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetOne); - qState.propagateGate(SWAPOp(), targets); + std::vector vectorOneThree = {1, 3}; + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorOne); + qState.propagateGate(swapOp.getOperation(), vectorOneThree); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 0.71, |1000> -> 0.71")); } TEST_F(QuantumStateTest, ApplyControlledGate) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetOne = {1}; - std::vector targetThree = {3}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetOne); - qState.propagateGate(XOp(), targetThree); - qState.propagateGate(XOp(), targetThree, targetOne); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorOne); + qState.propagateGate(xOp.getOperation(), vectorThree); + qState.propagateGate(xOp.getOperation(), vectorThree, vectorOne); EXPECT_THAT(qState.toString(), testing::HasSubstr("|0010> -> 0.71, |1000> -> 0.71")); } TEST_F(QuantumStateTest, ApplyPosNegControlledGate) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - std::vector targets = {0, 1}; - std::vector params = {2.0}; - QuantumState qState = QuantumState(qubits, 8); - qState.propagateGate(HOp(), targetZero); - qState.propagateGate(HOp(), targetOne); - qState.propagateGate(HOp(), targetTwo); - qState.propagateGate(XOp(), targetThree, targets, params); + auto qState = QuantumState(fourQubits, 8); + qState.propagateGate(hOp.getOperation(), vectorZero); + qState.propagateGate(hOp.getOperation(), vectorOne); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(xOp.getOperation(), vectorThree, vectorZeroOne); EXPECT_THAT( qState.toString(), - testing::HasSubstr("|0000> -> 0.35, |0001> -> 0.35, |0010> -> 0.35, " - "|0100> -> 0.35, |0101> -> 0.35, |0110> -> 0.35, " - "|0111> -> 0.35, |1011> -> 0.35")); + testing::HasSubstr( + "|0000> -> 0.35, |0001> -> 0.35, |0010> -> 0.35, |0100> -> 0.35, " + "|0101> -> 0.35, |0110> -> 0.35, |1011> -> 0.35, |1111> -> 0.35")); } TEST_F(QuantumStateTest, ApplyControlledTwoQubitGate) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - std::vector targets = {2, 1}; - QuantumState qState = QuantumState(qubits, 4); - qState.propagateGate(HOp(), targetThree); - qState.propagateGate(HOp(), targetTwo); - qState.propagateGate(SWAPOp(), targets, targetThree); + auto qState = QuantumState(fourQubits, 4); + qState.propagateGate(hOp.getOperation(), vectorThree); + qState.propagateGate(hOp.getOperation(), vectorTwo); + qState.propagateGate(swapOp.getOperation(), vectorTwoOne, vectorThree); EXPECT_THAT( qState.toString(), @@ -222,132 +221,106 @@ TEST_F(QuantumStateTest, ApplyControlledTwoQubitGate) { } TEST_F(QuantumStateTest, propagateGateCheckErrorIfTwoManyAmplitudesAreNonzero) { - std::vector qubits = {0, 1, 2, 3}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - QuantumState qState = QuantumState(qubits, 2); - qState.propagateGate(HOp(), targetThree); - qState.propagateGate(XOp(), targetTwo, targetThree); - - EXPECT_THROW(qState.propagateGate(HOp(), targetTwo);, std::domain_error); + auto qState = QuantumState(fourQubits, 2); + qState.propagateGate(hOp.getOperation(), vectorThree); + qState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); + + EXPECT_THROW(qState.propagateGate(hOp.getOperation(), vectorTwo); + , std::domain_error); } TEST_F(QuantumStateTest, doMeasurementWithZeroResult) { - std::vector qubit = {0}; - QuantumState qState = QuantumState(qubit, 2); - MeasurementResult const res = qState.measureQubit(0); + auto qState = QuantumState(vectorZero, 2); + const auto [states, numberOfStates] = qState.measureQubit(0); - EXPECT_TRUE(res.size == 1); - auto [probability, qs] = res.states.at(0); + EXPECT_TRUE(numberOfStates == 1); + auto [probability, qs] = states.at(0); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); } TEST_F(QuantumStateTest, doMeasurementWithOneResult) { - std::vector qubit = {0}; - std::vector qubitsOne = {0, 2, 4}; - std::vector qubitsTwo = {1, 3}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - std::vector targetFour = {4}; - QuantumState qState = QuantumState(qubit, 2); - qState.propagateGate(XOp(), qubit); - MeasurementResult const res = qState.measureQubit(0); - - EXPECT_TRUE(res.size == 1); - auto [probability, qs] = res.states.at(1); + auto qState = QuantumState(vectorZero, 2); + qState.propagateGate(xOp.getOperation(), vectorZero); + const auto [states, numberOfStates] = qState.measureQubit(0); + + EXPECT_TRUE(numberOfStates == 1); + auto [probability, qs] = states.at(1); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); } TEST_F(QuantumStateTest, doMeasurementWithTwoResults) { - std::vector qubits = {0, 1}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - QuantumState qState = QuantumState(qubits, 2); - qState.propagateGate(HOp(), targetZero); - qState.propagateGate(XOp(), targetOne, targetZero); - MeasurementResult const res = qState.measureQubit(0); - - QuantumState const zeroReference = QuantumState(qubits, 2); - QuantumState oneReference = QuantumState(qubits, 2); - oneReference.propagateGate(XOp(), targetZero); - oneReference.propagateGate(XOp(), targetOne); - - EXPECT_TRUE(res.size == 2); - auto [probabilityZero, qsZero] = res.states.at(0); + auto qState = QuantumState(vectorZeroOne, 2); + qState.propagateGate(hOp.getOperation(), vectorZero); + qState.propagateGate(xOp.getOperation(), vectorOne, vectorZero); + const auto [states, numberOfStates] = qState.measureQubit(0); + + auto const zeroReference = QuantumState(vectorZeroOne, 2); + auto oneReference = QuantumState(vectorZeroOne, 2); + oneReference.propagateGate(xOp.getOperation(), vectorZero); + oneReference.propagateGate(xOp.getOperation(), vectorOne); + + EXPECT_TRUE(numberOfStates == 2); + auto [probabilityZero, qsZero] = states.at(0); EXPECT_TRUE(zeroReference == *qsZero.get()); EXPECT_DOUBLE_EQ(probabilityZero, 0.5); - auto [probabilityOne, qsOne] = res.states.at(1); + auto [probabilityOne, qsOne] = states.at(1); EXPECT_TRUE(oneReference == *qsOne.get()); EXPECT_DOUBLE_EQ(probabilityOne, 0.5); } TEST_F(QuantumStateTest, doResetWithOnlyZeros) { - std::vector qubit = {0}; - QuantumState qState = QuantumState(qubit, 2); - MeasurementResult const res = qState.resetQubit(0); + auto qState = QuantumState(vectorZero, 2); + const auto [states, numberOfStates] = qState.resetQubit(0); - EXPECT_TRUE(res.size == 1); - auto [probability, qs] = res.states.at(0); + EXPECT_TRUE(numberOfStates == 1); + auto [probability, qs] = states.at(0); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); } TEST_F(QuantumStateTest, doResetWithOnlyOnes) { - std::vector qubit = {0}; - QuantumState qState = QuantumState(qubit, 2); - qState.propagateGate(XOp(), qubit); - MeasurementResult const res = qState.resetQubit(0); + auto qState = QuantumState(vectorZero, 2); + qState.propagateGate(xOp.getOperation(), vectorZero); + const auto [states, numberOfStates] = qState.resetQubit(0); - QuantumState const refState = QuantumState(qubit, 2); + auto const refState = QuantumState(vectorZero, 2); - EXPECT_TRUE(res.size == 1); - auto [probability, qs] = res.states.at(1); + EXPECT_TRUE(numberOfStates == 1); + auto [probability, qs] = states.at(1); EXPECT_TRUE(refState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); } TEST_F(QuantumStateTest, doResetWithZerosAndOnes) { - std::vector qubits = {0, 1}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - QuantumState qState = QuantumState(qubits, 2); - qState.propagateGate(HOp(), targetZero); - qState.propagateGate(XOp(), targetOne, targetZero); - MeasurementResult const res = qState.resetQubit(0); - - QuantumState const zeroReference = QuantumState(targetZero, 2); - QuantumState oneReference = QuantumState(targetZero, 2); - oneReference.propagateGate(XOp(), targetOne); - - EXPECT_TRUE(res.size == 2); - auto [probabilityZero, qsZero] = res.states.at(0); + auto qState = QuantumState(vectorZeroOne, 2); + qState.propagateGate(hOp.getOperation(), vectorZero); + qState.propagateGate(xOp.getOperation(), vectorOne, vectorZero); + const auto [states, numberOfStates] = qState.resetQubit(0); + + auto const zeroReference = QuantumState(vectorZeroOne, 2); + auto oneReference = QuantumState(vectorZeroOne, 2); + oneReference.propagateGate(xOp.getOperation(), vectorOne); + + EXPECT_TRUE(numberOfStates == 2); + auto [probabilityZero, qsZero] = states.at(0); EXPECT_DOUBLE_EQ(probabilityZero, 0.5); - auto [probabilityOne, qsOne] = res.states.at(1); + auto [probabilityOne, qsOne] = states.at(1); EXPECT_DOUBLE_EQ(probabilityOne, 0.5); - EXPECT_TRUE(*qsZero == oneReference || *qsZero == oneReference); - EXPECT_TRUE(*qsOne == zeroReference || *qsOne == zeroReference); + EXPECT_TRUE(*qsZero.get() == zeroReference); + EXPECT_TRUE(*qsOne.get() == oneReference); } TEST_F(QuantumStateTest, unifyTwoQuantumStates) { - std::vector qubitsOne = {0, 2, 4}; - std::vector qubitsTwo = {1, 3}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - std::vector targetFour = {4}; - QuantumState qState1 = QuantumState(qubitsOne, 10); - qState1.propagateGate(HOp(), targetFour); - qState1.propagateGate(XOp(), targetTwo, targetFour); - qState1.propagateGate(XOp(), targetZero, targetTwo); - - QuantumState qState2 = QuantumState(qubitsTwo, 10); - qState2.propagateGate(HOp(), targetThree); - qState2.propagateGate(XOp(), targetOne, targetThree); + auto qState1 = QuantumState(vectorZeroTwoFour, 10); + qState1.propagateGate(hOp.getOperation(), vectorFour); + qState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + qState1.propagateGate(xOp.getOperation(), vectorZero, vectorTwo); + + auto qState2 = QuantumState(vectorOneThree, 10); + qState2.propagateGate(hOp.getOperation(), vectorThree); + qState2.propagateGate(xOp.getOperation(), vectorOne, vectorThree); const QuantumState unified = qState1.unify(qState2); @@ -357,21 +330,14 @@ TEST_F(QuantumStateTest, unifyTwoQuantumStates) { } TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { - std::vector qubitsOne = {0, 2, 4}; - std::vector qubitsTwo = {1, 3}; - std::vector targetZero = {0}; - std::vector targetOne = {1}; - std::vector targetTwo = {2}; - std::vector targetThree = {3}; - std::vector targetFour = {4}; - QuantumState qState1 = QuantumState(qubitsOne, 3); - qState1.propagateGate(HOp(), targetFour); - qState1.propagateGate(XOp(), targetTwo, targetFour); - qState1.propagateGate(XOp(), targetZero, targetTwo); - - QuantumState qState2 = QuantumState(qubitsTwo, 3); - qState2.propagateGate(HOp(), targetThree); - qState2.propagateGate(XOp(), targetOne, targetThree); + auto qState1 = QuantumState(vectorZeroTwoFour, 3); + qState1.propagateGate(hOp.getOperation(), vectorFour); + qState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + qState1.propagateGate(xOp.getOperation(), vectorZero, vectorTwo); + + auto qState2 = QuantumState(vectorOneThree, 3); + qState2.propagateGate(hOp.getOperation(), vectorThree); + qState2.propagateGate(xOp.getOperation(), vectorOne, vectorThree); EXPECT_THROW(auto qs = qState1.unify(qState2), std::domain_error); } From 4c8b2bfc7ad8e8f760b387a3ae2b6138575c70ac Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 16:21:14 +0200 Subject: [PATCH 073/131] :recycle: Refactored QuantumState.hpp --- .../ConstantPropagation/QuantumState.hpp | 70 ++++++++++--------- .../ConstantPropagation/test_quantumState.cpp | 1 + 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 7136f5f1a2..7a47715965 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -16,9 +16,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -71,19 +73,20 @@ class QuantumState { return result; } - /** - * @brief Create a bitstring that has ones exactly at the given positions + /** @brief Puts a zero or one at bit position n in a bitstring. * - * @param positions The positions at which the bits should be one. - * @return The finished bitstring. + * @param bitstring Bitstring that gets altered + * @param position Position of bit that needs to be changed + * @param one True if bit should be set to one + * @return The altered bitstring */ - static constexpr std::size_t - makeTargetMask(std::span positions) noexcept { - std::size_t mask = 0; - for (unsigned int p : positions) { - mask |= 1U << p; + static unsigned int setBitN(const unsigned int bitstring, + const unsigned int position, const bool one) { + const unsigned int mask = 1U << position; + if (one) { + return bitstring | mask; } - return mask; + return bitstring & ~mask; } /** @@ -109,8 +112,7 @@ class QuantumState { std::span positionOfTargetQubits, const unsigned int bitmaskForCtrls) { std::unordered_map> newValues; - const auto numberOfTargetValues = static_cast( - pow(2, static_cast(positionOfTargetQubits.size())) + 0.1); + const auto numberOfTargetValues = 1U << positionOfTargetQubits.size(); for (const auto& [key, value] : amplitudeMap) { if ((bitmaskForCtrls & key) != bitmaskForCtrls) { @@ -124,19 +126,18 @@ class QuantumState { // Find the target keys from the current keys for (unsigned int i = 0; i < numberOfTargetValues; ++i) { - keysForNewValue[i] = key; - auto maskFirstPos = 1U << positionOfTargetQubits[0]; if (i % 2 == 1) { - keysForNewValue[i] |= maskFirstPos; + keysForNewValue[i] = setBitN(key, positionOfTargetQubits[0], true); } else { - keysForNewValue[i] &= ~maskFirstPos; + keysForNewValue[i] = setBitN(key, positionOfTargetQubits[0], false); } if (numberOfTargetValues > 2) { - auto maskSecondPos = 1U << positionOfTargetQubits[1]; if (i > 1) { - keysForNewValue[i] |= maskSecondPos; + keysForNewValue[i] = + setBitN(keysForNewValue[i], positionOfTargetQubits[1], true); } else { - keysForNewValue[i] &= ~maskSecondPos; + keysForNewValue[i] = + setBitN(keysForNewValue[i], positionOfTargetQubits[1], false); } } if (keysForNewValue[i] == key) { @@ -164,12 +165,13 @@ class QuantumState { * measured is set to 0. * * @param target The global index of the qubit to be measured. + * @param reset True if target should be resetted in addition to measured. * @return MeasurementResult, containing the probability for the result and * the QuantumStates after measurement. */ MeasurementResult measureOrResetQubit(const unsigned int target, const bool reset) { - const auto qubitMask = static_cast(pow(2, target) + 0.1); + const auto qubitMask = 1U << target; double probabilityZero = 0.0; double probabilityOne = 0.0; @@ -178,14 +180,16 @@ class QuantumState { for (const auto& [key, value] : amplitudeMap) { unsigned int newKey = key; - if ((qubitMask & key) == 0) { - probabilityZero += norm(value); - newValuesZeroRes.insert({key, value}); + const bool isZero = (qubitMask & key) == 0; + const double probability = norm(value); + if (isZero) { + probabilityZero += probability; + newValuesZeroRes[newKey] = value; } else { if (reset) { newKey = key ^ qubitMask; } - probabilityOne += norm(value); + probabilityOne += probability; newValuesOneRes[newKey] = value; } } @@ -197,23 +201,21 @@ class QuantumState { auto globalKeysView = std::views::keys(globalToLocalQubitNumber); std::vector globalKeys{globalKeysView.begin(), globalKeysView.end()}; - auto stateZero = - std::make_shared(globalKeys, maxNonzeroAmplitudes); - stateZero->amplitudeMap = newValuesZeroRes; - stateZero->normalize(); - auto stateOne = - std::make_shared(globalKeys, maxNonzeroAmplitudes); - stateOne->amplitudeMap = newValuesOneRes; - stateOne->normalize(); - MeasurementResult res = {}; if (probabilityZero > 1e-4) { ++res.size; + auto stateZero = + std::make_shared(globalKeys, maxNonzeroAmplitudes); + stateZero->amplitudeMap = std::move(newValuesZeroRes); + stateZero->normalize(); res.states[0] = {probabilityZero, stateZero}; } - if (probabilityOne > 1e-4) { ++res.size; + auto stateOne = + std::make_shared(globalKeys, maxNonzeroAmplitudes); + stateOne->amplitudeMap = std::move(newValuesOneRes); + stateOne->normalize(); res.states[1] = {probabilityOne, stateOne}; } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 48c7cb6590..3d17e46eb8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include From 37df86db6292aa0c9eb2de7d95d6ecb1b0351f4c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:23:32 +0200 Subject: [PATCH 074/131] :recycle: Refactored QuantumState.hpp, assisted-by GPT-OSS va KI:connect --- .../ConstantPropagation/QuantumState.cpp | 81 ++++++++++--------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index d92c560903..d3f710468d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -14,27 +14,29 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" -#include - +#include #include #include #include +#include +#include +#include +#include #include namespace mlir::qco { QuantumState::QuantumState(const std::span globalQubitNumber, const std::size_t maxNonzeroAmplitudes) - : maxNonzeroAmplitudes(maxNonzeroAmplitudes) { - nQubits = globalQubitNumber.size(); + : nQubits(globalQubitNumber.size()), + maxNonzeroAmplitudes(maxNonzeroAmplitudes) { std::ranges::sort(globalQubitNumber); unsigned int localQ = 0; for (auto globalQ : globalQubitNumber) { - globalToLocalQubitNumber.insert({globalQ, localQ}); + globalToLocalQubitNumber[globalQ] = localQ; ++localQ; } - amplitudeMap = std::unordered_map>(); - amplitudeMap.insert({0, std::complex(1.0, 0.0)}); + amplitudeMap[0] = std::complex(1.0, 0.0); } void QuantumState::print(std::ostream& os) const { os << this->toString(); } @@ -49,13 +51,17 @@ std::string QuantumState::toString() const { str += ", "; } first = false; - std::string cn = std::format("{:.2f}", val.real()); - if (val.imag() > 1e-4) { - cn += " + i" + std::format("{:.2f}", val.imag()); - } else if (val.imag() < -1e-4) { - cn += " - i" + std::format("{:.2f}", -val.imag()); + + str.push_back('|'); + str.append(qubitStringToBinary(key)); + str.append("> -> "); + + str.append(std::format("{:.2f}", val.real())); + + if (std::abs(val.imag()) > 1e-4) { + str.append(val.imag() > 0 ? " + i" : " - i"); + str.append(std::format("{:.2f}", std::abs(val.imag()))); } - str += "|" + qubitStringToBinary(key) + "> -> " + cn; } return str; @@ -68,6 +74,10 @@ bool QuantumState::operator==(const QuantumState& that) const { return false; } + if (amplitudeMap.size() != that.amplitudeMap.size()) { + return false; + } + return std::ranges::all_of( this->amplitudeMap, [&](const std::pair>& p) { @@ -82,8 +92,9 @@ void QuantumState::normalize() { for (const auto& value : amplitudeMap | std::views::values) { denominator += norm(value); } + const double invDenominator = 1 / std::sqrt(denominator); for (const auto& key : amplitudeMap | std::views::keys) { - amplitudeMap[key] /= std::sqrt(denominator); + amplitudeMap[key] *= invDenominator; } } @@ -132,16 +143,16 @@ QuantumState QuantumState::unify(const QuantumState& that) { unsigned int currentQubitState = 0; for (const auto& indicesOfThis : oldToNewIndicesThis | std::views::keys) { - unsigned int bitOfQubitState = pow(2, indicesOfThis); + unsigned int bitOfQubitState = 1U << indicesOfThis; if ((keyThis & bitOfQubitState) == bitOfQubitState) { - currentQubitState += pow(2, oldToNewIndicesThis.at(indicesOfThis)); + currentQubitState += 1U << oldToNewIndicesThis.at(indicesOfThis); } } for (const auto& indicesOfThat : oldToNewIndicesThat | std::views::keys) { - unsigned int bitOfQubitState = pow(2, indicesOfThat); + unsigned int bitOfQubitState = 1U << indicesOfThat; if ((keyThat & bitOfQubitState) == bitOfQubitState) { - currentQubitState += pow(2, oldToNewIndicesThat.at(indicesOfThat)); + currentQubitState += 1U << oldToNewIndicesThat.at(indicesOfThat); } } newAmplitudes[currentQubitState] = valThis * valThat; @@ -155,17 +166,17 @@ QuantumState QuantumState::unify(const QuantumState& that) { } void QuantumState::propagateGate(Operation* gate, - std::span targets, - std::span ctrls, - std::span params) { + const std::span targets, + const std::span ctrls, + const std::span params) { const auto gateMapping = getQubitMappingOfGates(gate, params); unsigned int ctrlMask = 0; - for (unsigned int const posCtrl : ctrls) { - ctrlMask += static_cast( - pow(2, globalToLocalQubitNumber.at(posCtrl)) + 0.1); + for (unsigned int const ctrl : ctrls) { + ctrlMask |= 1U << globalToLocalQubitNumber.at(ctrl); } std::vector localTargets; + localTargets.reserve(targets.size()); for (unsigned int q : targets) { localTargets.push_back(globalToLocalQubitNumber.at(q)); } @@ -193,17 +204,15 @@ MeasurementResult QuantumState::resetQubit(const unsigned int target) { return measureOrResetQubit(target, true); } -bool QuantumState::isQubitAlwaysOne(unsigned int q) const { - const auto mask = - static_cast(pow(2, static_cast(q)) + 0.1); +bool QuantumState::isQubitAlwaysOne(const unsigned int q) const { + const auto mask = 1U << q; return std::ranges::all_of( amplitudeMap | std::views::keys, [mask](auto qubits) { return (qubits & mask) == mask; }); } -bool QuantumState::isQubitAlwaysZero(unsigned int q) const { - const auto mask = - static_cast(pow(2, static_cast(q)) + 0.1); +bool QuantumState::isQubitAlwaysZero(const unsigned int q) const { + const auto mask = 1U << q; return std::ranges::all_of( amplitudeMap | std::views::keys, [mask](auto qubits) { return (qubits & mask) == 0; }); @@ -213,13 +222,11 @@ bool QuantumState::hasAlwaysZeroAmplitude(const std::span qubits, unsigned int localValue = 0; unsigned int mask = 0; for (unsigned int i = 0; i < qubits.size(); ++i) { - const unsigned int currentPower = - static_cast(pow(2, i) + 0.1); - const unsigned int qubitPower = - static_cast(pow(2, qubits[i]) + 0.1); - mask += qubitPower; - if ((value & currentPower) != 0) { - localValue += qubitPower; + const unsigned int bitMask = 1U << i; + const unsigned int qubitMask = 1U << qubits[i]; + mask += qubitMask; + if ((value & bitMask) != 0) { + localValue += qubitMask; } } return std::ranges::all_of( From 515928df402258ddf2bc53fd1d4c54ba2a13ef82 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:27:39 +0200 Subject: [PATCH 075/131] :construction: Corrected macros --- .../Optimizations/ConstantPropagation/QuantumState.hpp | 6 +++--- .../Optimizations/ConstantPropagation/QuantumState.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 7a47715965..81a80b0578 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_QUANTUMSTATEORTOP_H -#define MQT_CORE_QUANTUMSTATEORTOP_H +#ifndef MQT_CORE_QUANTUMSTATE_H +#define MQT_CORE_QUANTUMSTATE_H #include #include @@ -346,4 +346,4 @@ class QuantumState { } // namespace mlir::qco -#endif // MQT_CORE_QUANTUMSTATEORTOP_H +#endif // MQT_CORE_QUANTUMSTATE_H diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index d3f710468d..91f2372473 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -8,8 +8,8 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_QUANTUMSTATEORTOP -#define MQT_CORE_QUANTUMSTATEORTOP +#ifndef MQT_CORE_QUANTUMSTATE +#define MQT_CORE_QUANTUMSTATE #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" @@ -236,4 +236,4 @@ bool QuantumState::hasAlwaysZeroAmplitude(const std::span qubits, } // namespace mlir::qco -#endif // MQT_CORE_QUANTUMSTATEORTOP +#endif // MQT_CORE_QUANTUMSTATE From 9e5d0514f54f7a54742d4dd0a3fc2dc4578b5317 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:48:05 +0200 Subject: [PATCH 076/131] :rotating_light: Corrected clang errors --- .../Optimizations/ConstantPropagation/QuantumState.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 81a80b0578..cb37c7ff46 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -63,7 +63,7 @@ class QuantumState { std::unordered_map globalToLocalQubitNumber; std::unordered_map> amplitudeMap; - std::string qubitStringToBinary(unsigned int q) const { + std::string qubitStringToBinary(const unsigned int q) const { std::string result; result.reserve(nQubits); @@ -109,7 +109,7 @@ class QuantumState { std::unordered_map>> gateMapping, - std::span positionOfTargetQubits, + const std::span positionOfTargetQubits, const unsigned int bitmaskForCtrls) { std::unordered_map> newValues; const auto numberOfTargetValues = 1U << positionOfTargetQubits.size(); From 2fc98052d28cb25a5b2dc43134f4907f532be590 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 18:01:08 +0200 Subject: [PATCH 077/131] =?UTF-8?q?=F0=9F=9A=A7=20Created=20HybridState.hp?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConstantPropagation/HybridState.hpp | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp new file mode 100644 index 0000000000..8cd3ca0091 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_HYBRIDSTATE_H +#define MQT_CORE_HYBRIDSTATE_H + +#include "QuantumState.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace mlir::qco { +/** + * @brief This class represents a hybrid state. + * + * This class holds a QuantumState and a mapping form values to integers or + * doubles, as well as a probability. + */ +class HybridState { + bool top = false; + std::shared_ptr qState; + double probability; + llvm::DenseMap integerValues; + llvm::DenseMap doubleValues; + +public: + explicit HybridState(std::size_t nQubits, std::size_t maxNonzeroAmplitudes, + double probability = 1.0); + + ~HybridState(); + + void print(std::ostream& os) const; + + [[nodiscard("HybridState::toString called but ignored")]] + std::string toString() const; + + bool isHybridStateTop() const { return top; } + + /** + * @brief This method applies a gate to the state. + * + * This method changes the hybrid state according to a gate. + * + * @param gate The name of the gate to be applied. + * @param targets An array of the indices of the target qubits. + * @param ctrlsQuantum An array of the indices of the ctrl qubits. + * @param ctrlsClassical An array of the indices of the ctrl bits. + * @param params The parameter applied to the gate. + * @throw std::domain_error If the number of nonzero amplitudes would exceed + * maxNonzeroAmplitudes or if the hybridState does not hold a quantumState. + */ + void propagateGate(Operation* gate, std::span targets, + std::span ctrlsQuantum = {}, + std::span ctrlsClassical = {}, + std::span params = {}); + + /** + * @brief This method adds a classical integer value to the hybrid state. + * + * @param value The value object of the new value. + * @param number The number of the new value. + */ + void addIntegerValue(Value value, int64_t number); + + /** + * @brief This method adds a classical double value to the hybrid state. + * + * @param value The value object of the new value. + * @param number The number of the new value. + */ + void addDoubleValue(Value value, double number); + + /** + * @brief This method applies a measurement. + * + * This method applies a measurement, changing the qubits and the classical + * values corresponding to the measurement. + * + * @param quantumTarget The index of the qubit to be measured. + * @param classicalTarget The value to save the measurement result to. + * @param ctrlsClassical An array of ctrl values. + * @throws domain_error If the quantum state of the hybrid state is TOP. + * @return One or two hybrid states corresponding to the measurement + * outcomes. + */ + std::vector + propagateMeasurement(unsigned int quantumTarget, Value classicalTarget, + std::span ctrlsClassical = {}); + + /** + * @brief This method applies a reset. + * + * This method applies a reset, changing the qubits and creates one or two new + * states. The procedure is done as if the qubit was measured, put to zero if + * the measurement was one, and the result discarded. + * + * @param target The index of the qubit to be measured. + * @param ctrlsClassical An array of the ctrl values. + * @throws domain_error If the quantum state of the hybrid state is TOP. + * @return One or two hybrid states corresponding to the measurement outcomes + * during the reset, but with the qubit always in the zero state. + */ + std::vector propagateReset(unsigned int target, + std::span ctrlsClassical = {}); + + /** + * @brief This method unifies two HybridStates. + * + * This method unifies the current HybridState with the given one and returns + * a new HybridState, if the new state has no more than maxNonzeroAmplitudes. + * Otherwise, throws a domain_error. + * + * @param that The HybridState to unify this with. + * @throw std::domain_error If the unified QuantumState would exceed + * maxNonzeroAmplitudes of this. + */ + HybridState unify(HybridState that); + + bool operator==(const HybridState& that) const; + + [[nodiscard("HybridState::isQubitAlwaysOne called but ignored")]] bool + isQubitAlwaysOne(unsigned int q) const; + + [[nodiscard("HybridState::isQubitAlwaysZero called but ignored")]] bool + isQubitAlwaysZero(unsigned int q) const; + + [[nodiscard("HybridState::isValueTrue called but ignored")]] bool + isValueTrue(Value v) const; + + [[nodiscard("HybridState::isValueFalse called but ignored")]] bool + isValueFalse(Value v) const; + + /** + * @brief Checks if a given combination of values-qubit values has a nonzero + * probability. + * + * This method receives a number of qubit and values and checks whether + * they have for a given value always a zero amplitude. If the hybridState is + * top, it is not guaranteed that the amplitude is always zero and false is + * returned. + * The values for the classical values are not the numeric ones, but whether + * they are zero (false) or non-zero (true). + * + * @param qubits The qubits which are being checked. + * @param qubitValue The value for which is tested whether there is a nonzero + * amplitude. + * @param values The values to check. + * @param classicalValuesToCheck Whether to check if the values are zero + * (false) or non-zero (true). + * @returns True if the amplitude is always zero, false otherwise. + */ + [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool + hasAlwaysZeroAmplitude(std::span qubits, + unsigned int qubitValue, + std::span values = {}, + std::span classicalValuesToCheck = {}) const; + + /** + * @brief Returns whether the given qubit and the given classical value always + * have the same value or always a different value. + * + * Returns whether the given qubit and the given classical value always + * have the same value or always a different value. For the comparison, a + * qubit = 0 is equal to a classical value = 0 and a qubit = 1 to a classical + * value =/= 0. + * + * @param value Classical value. + * @param qubit Index of qubit. + * @returns Non-empty optional if the bit and qubit have always the same or + * different values. Optional contains true if they have the same value, false + * if they have always different values. + */ + std::optional getIsValueEquivalentToQubit(Value value, + unsigned int qubit); +}; +} // namespace mlir::qco + +#endif // MQT_CORE_HYBRIDSTATE_H From eeec576900a4ce2d5e5cd5c2e24aa1062e81fbc5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 16 Jun 2026 21:55:48 +0200 Subject: [PATCH 078/131] =?UTF-8?q?=F0=9F=9A=A7=20Adapted=20HybridState=20?= =?UTF-8?q?and=20created=20ClassicalArithOperation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClassicalArithOperation.h | 107 ++++++++++++++++++ .../ConstantPropagation/HybridState.hpp | 85 ++++++++------ 2 files changed, 157 insertions(+), 35 deletions(-) create mode 100644 mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h new file mode 100644 index 0000000000..1c020afb99 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include +#ifndef MQT_CORE_CLASSICALARITHOPERATION_H +#define MQT_CORE_CLASSICALARITHOPERATION_H + +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Utils/Drivers.h" + +#include + +#include +#include + +/** + * This file provides information of available arith operations. It calculates + * the result of valid arith operations. Operations are only valid with one to + * two operands, not if they are applied to sequences. + */ +inline int64_t getArithOpResult(mlir::Operation* operation, int64_t value1, + int64_t value2 = 0, int64_t value3 = 0) { + + for (mlir::Value operand : operation->getOperands()) { + if (isa(operand.getType())) { + throw std::runtime_error( + "Constant propagation does not support vectors as classical types."); + } + } + + return mlir::TypeSwitch(operation) + .Case([&](auto) { return value1 + value2; }) + .Case([&](auto) { return value1 & value2; }) + .Case([&](auto) { + // Division that rounds to positive infinity + return ceil(1.0 * value1 / value2); + }) + .Case([&](auto) { + // Division that rounds towards zero + return value1 / value2; + }) + .Case([&](auto) { + // Division that rounds to negative infinity + return floor(1.0 * value1 / value2); + }) + .Case( + [&](auto) { return value1 > value2 ? value1 : value2; }) + .Case( + [&](auto) { return value1 < value2 ? value1 : value2; }) + .Case([&](auto) { return value1 * value2; }) + .Case([&](auto) { return value1 | value2; }) + .Case( + [&](auto) { return remainder(value1, value2); }) + .Case([&](auto) { return value1 << value2; }) + .Case([&](auto) { return value1 >> value2; }) + .Case([&](auto) { return value1 - value2; }) + .Case([&](auto) { return value1 ^ value2; }) + .Case( + [&](auto) { return value3 == 0 ? value3 : value2; }) + .Default([&](auto) -> int64_t { + throw std::runtime_error("Unsupported integer operation in " + "mlir::qco::classicalarithoperation"); + }); +} + +inline double getArithOpResult(mlir::Operation* operation, double value1, + double value2 = 0.0) { + + for (mlir::Value operand : operation->getOperands()) { + if (isa(operand.getType())) { + throw std::runtime_error( + "Constant propagation does not support vectors as classical types."); + } + } + + return mlir::TypeSwitch(operation) + .Case([&](auto) { return value1 + value2; }) + .Case([&](auto) { return value1 / value2; }) + .Case( + [&](auto) { return value1 > value2 ? value1 : value2; }) + .Case( + [&](auto) { return value1 > value2 ? value1 : value2; }) + .Case( + [&](auto) { return value1 < value2 ? value1 : value2; }) + .Case( + [&](auto) { return value1 < value2 ? value1 : value2; }) + .Case([&](auto) { return value1 * value2; }) + .Case([&](auto) { return -value1; }) + .Case( + [&](auto) { return remainder(value1, value2); }) + .Case([&](auto) { return value1 - value2; }) + .Default([&](auto) -> double { + throw std::runtime_error("Unsupported floating-point operation in " + "mlir::qco::classicalarithoperation"); + }); +} + +#endif // MQT_CORE_MQT_CORE_CLASSICALARITHOPERATION_H diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 8cd3ca0091..c331a27d6e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -13,12 +13,11 @@ #include "QuantumState.hpp" -#include +#include + #include #include -#include #include -#include #include namespace mlir::qco { @@ -48,6 +47,22 @@ class HybridState { bool isHybridStateTop() const { return top; } + /** + * @brief This method adds a classical integer value to the hybrid state. + * + * @param value The value object of the new value. + * @param number The number of the new value. + */ + void addIntegerValue(Value value, int64_t number); + + /** + * @brief This method adds a classical double value to the hybrid state. + * + * @param value The value object of the new value. + * @param number The number of the new value. + */ + void addDoubleValue(Value value, double number); + /** * @brief This method applies a gate to the state. * @@ -66,22 +81,6 @@ class HybridState { std::span ctrlsClassical = {}, std::span params = {}); - /** - * @brief This method adds a classical integer value to the hybrid state. - * - * @param value The value object of the new value. - * @param number The number of the new value. - */ - void addIntegerValue(Value value, int64_t number); - - /** - * @brief This method adds a classical double value to the hybrid state. - * - * @param value The value object of the new value. - * @param number The number of the new value. - */ - void addDoubleValue(Value value, double number); - /** * @brief This method applies a measurement. * @@ -115,6 +114,24 @@ class HybridState { std::vector propagateReset(unsigned int target, std::span ctrlsClassical = {}); + /** + * @brief This method applies a classical operation. + * + * This method changes the hybrid state according to a classical operation. + * The operation might be controlled by classical values. + * + * @param op The operation to be applied. + * @param dest The value the result of the operation is written to. + * @param operand1 The first value used by the operation. + * @param operand2 The second value used by the operation, might be null. + * @param operand3 The third value used by the operation, might be null. + * @param ctrls An array of the ctrl values. + */ + void propagateClassicalOperation(Operation* op, Value dest, Value operand1, + Value operand2 = nullptr, + Value operand3 = nullptr, + std::span ctrls = {}); + /** * @brief This method unifies two HybridStates. * @@ -162,28 +179,26 @@ class HybridState { * @returns True if the amplitude is always zero, false otherwise. */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool - hasAlwaysZeroAmplitude(std::span qubits, - unsigned int qubitValue, - std::span values = {}, - std::span classicalValuesToCheck = {}) const; + hasAlwaysZeroProbability(std::span qubits, + unsigned int qubitValue, + std::span values = {}, + std::span classicalValuesToCheck = {}) const; /** - * @brief Returns whether the given qubit and the given classical value always - * have the same value or always a different value. + * @brief Returns a classical value that is equivalent to qubit. * - * Returns whether the given qubit and the given classical value always - * have the same value or always a different value. For the comparison, a - * qubit = 0 is equal to a classical value = 0 and a qubit = 1 to a classical - * value =/= 0. + * Returns a classical value that is always true (=/= 0) when the given qubit + * is 1, and the boolean value true. Alternatively, it can return a value that + * is always false (== 0) if the qubit is 1. In that case, the returned bool + * is false. * - * @param value Classical value. * @param qubit Index of qubit. - * @returns Non-empty optional if the bit and qubit have always the same or - * different values. Optional contains true if they have the same value, false - * if they have always different values. + * @returns Classical value that is equivalent or inverse to qubit if it + * exists and true, if the qubit is equivalent to the value. False, if the + * qubit is the inverse of the value. */ - std::optional getIsValueEquivalentToQubit(Value value, - unsigned int qubit); + std::pair, bool> + getValueThatIsEquivalentToQubit(unsigned int qubit); }; } // namespace mlir::qco From fa6667bfde512dd3c2339e4edee6750619657d2b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 17 Jun 2026 12:41:48 +0200 Subject: [PATCH 079/131] =?UTF-8?q?=F0=9F=9A=A7=20Added=20HybridState=20im?= =?UTF-8?q?plementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConstantPropagation/HybridState.hpp | 159 +++++++-- .../ConstantPropagation/QuantumState.hpp | 21 +- .../ConstantPropagation/HybridState.cpp | 303 ++++++++++++++++++ 3 files changed, 441 insertions(+), 42 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index c331a27d6e..5a2b13e373 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -34,8 +34,105 @@ class HybridState { llvm::DenseMap integerValues; llvm::DenseMap doubleValues; + HybridState() : probability(0.0) {} + + /** + * @brief Checks if all positive classical controls hold and all negative + * classical controls do not hold. + * + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @return True if the controls together evaluate to true. + * @throws domain_error If a classical control value cannot be found. + */ + bool isOperationExecutable(const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + for (const Value posCtrl : posCtrlsClassical) { + if (integerValues.contains(posCtrl) && integerValues.at(posCtrl) == 0) { + return false; + } + if (doubleValues.contains(posCtrl) && + std::norm(doubleValues.at(posCtrl)) < 1e-4) { + return false; + } + if (!doubleValues.contains(posCtrl) && !integerValues.contains(posCtrl)) { + throw std::domain_error( + "HybridState needs a classical value for operation control that is " + "not existent in current HybridState."); + } + } + for (const Value negCtrl : negCtrlsClassical) { + if (integerValues.contains(negCtrl) && integerValues.at(negCtrl) != 0) { + return false; + } + if (doubleValues.contains(negCtrl) && + std::norm(doubleValues.at(negCtrl)) > 1e-4) { + return false; + } + if (!doubleValues.contains(negCtrl) && !integerValues.contains(negCtrl)) { + throw std::domain_error( + "HybridState needs a classical value for operation control that is " + "not existent in current HybridState."); + } + } + return true; + } + + /** + * @brief This method applies a measurement or reset. + * + * This method applies a measurement or reset, changing the qubits and the + * classical values (if a measurement is applied) corresponding to the + * measurement. + * + * @param quantumTarget The index of the qubit to be measured. + * @param reset True if a reset is applied. + * @param classicalTarget The value to save the measurement result to. + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @throws domain_error If a classical control value cannot be found. + * @return One or two hybrid states corresponding to the measurement or reset + * outcomes. + */ + std::vector + propagateMeasurementOrReset(const unsigned int quantumTarget, bool reset, + const Value classicalTarget = nullptr, + const std::span posCtrlsClassical = {}, + const std::span negCtrlsClassical = {}) { + if (top || !isOperationExecutable(posCtrlsClassical, negCtrlsClassical)) { + return {*this}; + } + + std::vector results; + + const auto [newQuantumStates, availableStates] = + reset ? qState->resetQubit(quantumTarget) + : qState->measureQubit(quantumTarget); + + for (int64_t i = 0; i < 2; ++i) { + if (!availableStates.at(i)) { + continue; + } + + const auto& [measProbability, measQS] = newQuantumStates.at(i); + auto newHybrid = HybridState(); + newHybrid.probability = measProbability * probability; + newHybrid.integerValues = integerValues; + newHybrid.doubleValues = doubleValues; + if (!reset) { + newHybrid.integerValues[classicalTarget] = i; + } + newHybrid.qState = measQS; + + results.push_back(newHybrid); + } + + return results; + } + public: - explicit HybridState(std::size_t nQubits, std::size_t maxNonzeroAmplitudes, + explicit HybridState(std::span globalQubitNumber, + std::size_t maxNonzeroAmplitudes, double probability = 1.0); ~HybridState(); @@ -70,16 +167,17 @@ class HybridState { * * @param gate The name of the gate to be applied. * @param targets An array of the indices of the target qubits. - * @param ctrlsQuantum An array of the indices of the ctrl qubits. - * @param ctrlsClassical An array of the indices of the ctrl bits. - * @param params The parameter applied to the gate. - * @throw std::domain_error If the number of nonzero amplitudes would exceed - * maxNonzeroAmplitudes or if the hybridState does not hold a quantumState. + * @param ctrlsQuantum An array of the global indices of the ctrl qubits. + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @param params The values of parameters applied to the gate. + * @throws domain_error If a classical control value cannot be found. */ void propagateGate(Operation* gate, std::span targets, std::span ctrlsQuantum = {}, - std::span ctrlsClassical = {}, - std::span params = {}); + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}, + std::span params = {}); /** * @brief This method applies a measurement. @@ -89,14 +187,16 @@ class HybridState { * * @param quantumTarget The index of the qubit to be measured. * @param classicalTarget The value to save the measurement result to. - * @param ctrlsClassical An array of ctrl values. - * @throws domain_error If the quantum state of the hybrid state is TOP. + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @throws domain_error If a classical control value cannot be found. * @return One or two hybrid states corresponding to the measurement * outcomes. */ std::vector propagateMeasurement(unsigned int quantumTarget, Value classicalTarget, - std::span ctrlsClassical = {}); + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); /** * @brief This method applies a reset. @@ -106,13 +206,15 @@ class HybridState { * the measurement was one, and the result discarded. * * @param target The index of the qubit to be measured. - * @param ctrlsClassical An array of the ctrl values. - * @throws domain_error If the quantum state of the hybrid state is TOP. + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @throws domain_error If a classical control value cannot be found. * @return One or two hybrid states corresponding to the measurement outcomes * during the reset, but with the qubit always in the zero state. */ - std::vector propagateReset(unsigned int target, - std::span ctrlsClassical = {}); + std::vector + propagateReset(unsigned int target, std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); /** * @brief This method applies a classical operation. @@ -125,12 +227,16 @@ class HybridState { * @param operand1 The first value used by the operation. * @param operand2 The second value used by the operation, might be null. * @param operand3 The third value used by the operation, might be null. - * @param ctrls An array of the ctrl values. + * @param posCtrlsClassical An array of the classical positive control values. + * @param negCtrlsClassical An array of the classical negative control values. + * @throws domain_error If a classical value cannot be found. + * @throws runtime_error If classical operation is not supported. */ void propagateClassicalOperation(Operation* op, Value dest, Value operand1, Value operand2 = nullptr, Value operand3 = nullptr, - std::span ctrls = {}); + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); /** * @brief This method unifies two HybridStates. @@ -156,9 +262,6 @@ class HybridState { [[nodiscard("HybridState::isValueTrue called but ignored")]] bool isValueTrue(Value v) const; - [[nodiscard("HybridState::isValueFalse called but ignored")]] bool - isValueFalse(Value v) const; - /** * @brief Checks if a given combination of values-qubit values has a nonzero * probability. @@ -173,16 +276,16 @@ class HybridState { * @param qubits The qubits which are being checked. * @param qubitValue The value for which is tested whether there is a nonzero * amplitude. - * @param values The values to check. - * @param classicalValuesToCheck Whether to check if the values are zero - * (false) or non-zero (true). + * @param classicalIntegerValues The integer values to check. + * @param classicalDoubleValues The double values to check. + * @throws domain_error If a classical value cannot be found. * @returns True if the amplitude is always zero, false otherwise. */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool - hasAlwaysZeroProbability(std::span qubits, - unsigned int qubitValue, - std::span values = {}, - std::span classicalValuesToCheck = {}) const; + hasAlwaysZeroProbability( + std::span qubits, unsigned int qubitValue, + std::span> classicalIntegerValues = {}, + std::span> classicalDoubleValues = {}) const; /** * @brief Returns a classical value that is equivalent to qubit. @@ -198,7 +301,7 @@ class HybridState { * qubit is the inverse of the value. */ std::pair, bool> - getValueThatIsEquivalentToQubit(unsigned int qubit); + getValueThatIsEquivalentToQubit(unsigned int qubit) const; }; } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index cb37c7ff46..95cc6329b6 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -12,7 +12,6 @@ #define MQT_CORE_QUANTUMSTATE_H #include -#include #include #include #include @@ -36,18 +35,12 @@ namespace mlir::qco { */ struct MeasurementResult { // The pair is (probability, resulting state). - std::array>, 2> - states{}; - // How many entries in `states` are actually filled (0, 1 or 2). - std::size_t size = 0; + std::unordered_map>> + states; - [[nodiscard]] constexpr std::size_t count() const noexcept { return size; } - - // Range‑for friendliness - [[nodiscard]] constexpr auto begin() const noexcept { return states.begin(); } - [[nodiscard]] constexpr auto end() const noexcept { - return states.begin() + size; - } + // Which entries are available + std::unordered_map availableStates; }; /** @@ -203,20 +196,20 @@ class QuantumState { globalKeysView.end()}; MeasurementResult res = {}; if (probabilityZero > 1e-4) { - ++res.size; auto stateZero = std::make_shared(globalKeys, maxNonzeroAmplitudes); stateZero->amplitudeMap = std::move(newValuesZeroRes); stateZero->normalize(); res.states[0] = {probabilityZero, stateZero}; + res.availableStates[0] = true; } if (probabilityOne > 1e-4) { - ++res.size; auto stateOne = std::make_shared(globalKeys, maxNonzeroAmplitudes); stateOne->amplitudeMap = std::move(newValuesOneRes); stateOne->normalize(); res.states[1] = {probabilityOne, stateOne}; + res.availableStates[1] = true; } return res; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp new file mode 100644 index 0000000000..6504425a6e --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_HYBRIDSTATE +#define MQT_CORE_HYBRIDSTATE +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp" + +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" + +#include + +namespace mlir::qco { + +HybridState::HybridState(std::span globalQubitNumber, + std::size_t maxNonzeroAmplitudes, + const double probability) + : probability(probability) { + qState = make_shared(globalQubitNumber, maxNonzeroAmplitudes); +} + +HybridState::~HybridState() { + qState.reset(); + integerValues.clear(); + doubleValues.clear(); +} + +void HybridState::print(std::ostream& os) const { os << this->toString(); } +std::string HybridState::toString() const { + std::string str = "{" + this->qState->toString() + "}: "; + unsigned int i = 0; + for (const auto& key : integerValues.keys()) { + str += "integerValue" + std::to_string(i) + " = " + + std::to_string(integerValues.at(key)) + ", "; + ++i; + } + unsigned int j = 0; + for (const auto& key : integerValues.keys()) { + str += "doubleValue" + std::to_string(j) + " = " + + std::format("{:.2f}", doubleValues.at(key)) + ", "; + ++j; + } + if (i > 0 || j > 0) { + str += "; "; + } + str += "p = " + std::format("{:.2f}", this->probability) + ";"; + return str; +} + +bool HybridState::operator==(const HybridState& that) const { + if (top) { + return that.top; + } + if (probability != that.probability || *qState.get() != *that.qState.get()) { + return false; + } + + if (integerValues.size() != that.integerValues.size() || + doubleValues.size() != that.doubleValues.size()) { + return false; + } + + for (const auto& [i, v] : integerValues) { + if (!that.integerValues.contains(i) || that.integerValues.at(i) != v) { + return false; + } + } + + for (const auto& [d, v] : doubleValues) { + if (!that.doubleValues.contains(d) || + std::norm(that.doubleValues.at(d)) > 1e-4) { + return false; + } + } + + return true; +} + +void HybridState::addIntegerValue(const Value value, const int64_t number) { + integerValues[value] = number; +} + +void HybridState::addDoubleValue(const Value value, const double number) { + doubleValues[value] = number; +} + +void HybridState::propagateGate(Operation* gate, + const std::span targets, + const std::span ctrlsQuantum, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical, + const std::span params) { + if (top) { + return; + } + + if (!isOperationExecutable(posCtrlsClassical, negCtrlsClassical)) { + return; + } + + if (!params.empty()) { + std::vector paramValues; + paramValues.reserve(params.size()); + for (Value p : params) { + if (integerValues.contains(p)) { + paramValues.push_back(integerValues.at(p)); + } else if (doubleValues.contains(p)) { + paramValues.push_back(doubleValues.at(p)); + } else { + throw std::domain_error( + "HybridState needs a classical value for gate parameters that is " + "not existent in current HybridState."); + } + } + try { + qState->propagateGate(gate, targets, ctrlsQuantum, paramValues); + } catch (std::domain_error const&) { + top = true; + } + } + + try { + qState->propagateGate(gate, targets, ctrlsQuantum); + } catch (std::domain_error const&) { + top = true; + } +} + +std::vector +HybridState::propagateMeasurement(const unsigned int quantumTarget, + const Value classicalTarget, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + + return propagateMeasurementOrReset(quantumTarget, false, classicalTarget, + posCtrlsClassical, negCtrlsClassical); +} + +std::vector +HybridState::propagateReset(const unsigned int target, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + return propagateMeasurementOrReset(target, true, nullptr, posCtrlsClassical, + negCtrlsClassical); +} + +void HybridState::propagateClassicalOperation( + Operation* op, const Value dest, const Value operand1, const Value operand2, + const Value operand3, const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + if (top || !isOperationExecutable(posCtrlsClassical, negCtrlsClassical)) { + return; + } + + if (isa(op->getResult(0).getType())) { + if (!integerValues.contains(operand1) || + (operand2 != nullptr && !integerValues.contains(operand2)) || + (operand3 != nullptr && !integerValues.contains(operand3)) || + !integerValues.contains(dest)) { + throw std::domain_error( + "HybridState needs a classical value for a classical operation that " + "is not existent in current HybridState."); + } + const int64_t opRes = + getArithOpResult(op, integerValues.at(operand1), + operand2 == nullptr ? 0 : integerValues.at(operand2), + operand3 == nullptr ? 0 : integerValues.at(operand3)); + integerValues[dest] = opRes; + } else { + if (!doubleValues.contains(operand1) || + (operand2 != nullptr && !doubleValues.contains(operand2)) || + (operand3 != nullptr && !doubleValues.contains(operand3)) || + !doubleValues.contains(dest)) { + throw std::domain_error( + "HybridState needs a classical value for a classical operation that " + "is not existent in current HybridState."); + } + const double opRes = + getArithOpResult(op, doubleValues.at(operand1), + operand2 == nullptr ? 0 : doubleValues.at(operand2), + operand3 == nullptr ? 0 : doubleValues.at(operand3)); + doubleValues[dest] = opRes; + } +} + +HybridState HybridState::unify(HybridState that) { + auto newHybridState = HybridState(); + try { + newHybridState.qState = + std::make_shared(qState->unify(*that.qState)); + } catch (std::domain_error const&) { + newHybridState.top = true; + return newHybridState; + } + newHybridState.probability *= this->probability; + + auto newIntegerValues = llvm::DenseMap( + integerValues.size() + that.integerValues.size()); + auto newDoubleValues = llvm::DenseMap( + doubleValues.size() + that.doubleValues.size()); + + for (const auto& [v, i] : integerValues) { + newIntegerValues[v] = i; + } + for (const auto& [v, i] : that.integerValues) { + newIntegerValues[v] = i; + } + + for (const auto& [v, d] : doubleValues) { + newDoubleValues[v] = d; + } + for (const auto& [v, d] : that.doubleValues) { + newDoubleValues[v] = d; + } + + newHybridState.integerValues = newIntegerValues; + newHybridState.doubleValues = newDoubleValues; + + return newHybridState; +} + +bool HybridState::isQubitAlwaysOne(unsigned int q) const { + return qState->isQubitAlwaysOne(q); +} + +bool HybridState::isQubitAlwaysZero(unsigned int q) const { + return qState->isQubitAlwaysZero(q); +} + +bool HybridState::isValueTrue(const Value v) const { + if (integerValues.contains(v)) { + return integerValues.at(v) != 0; + } + if (doubleValues.contains(v)) { + return std::norm(doubleValues.at(v)) > 1e-4; + } + throw std::domain_error("Value of a classical value is asked which does not " + "exist in the HybridState."); +} + +bool HybridState::hasAlwaysZeroProbability( + const std::span qubits, const unsigned int qubitValue, + std::span> classicalIntegerValues, + std::span> classicalDoubleValues) const { + for (const auto& [v, i] : classicalIntegerValues) { + if (!integerValues.contains(v)) { + throw std::domain_error("Value of a classical value is asked which does " + "not exist in the HybridState."); + } + if (integerValues.at(v) != i) { + return true; + } + } + + for (const auto& [v, d] : classicalDoubleValues) { + if (!doubleValues.contains(v)) { + throw std::domain_error("Value of a classical value is asked which does " + "not exist in the HybridState."); + } + if (std::norm(doubleValues.at(v) - d) > 1e-4) { + return true; + } + } + + return qState->hasAlwaysZeroAmplitude(qubits, qubitValue); +} + +std::pair, bool> +HybridState::getValueThatIsEquivalentToQubit(const unsigned int qubit) const { + if (integerValues.empty() && doubleValues.empty()) { + return {std::optional(), false}; + } + const bool qubitZero = qState->isQubitAlwaysZero(qubit); + const bool qubitOne = qubitZero ? false : qState->isQubitAlwaysOne(qubit); + if (!qubitZero && !qubitOne) { + return {std::optional(), false}; + } + for (const auto& [v, i] : integerValues) { + if ((qubitZero && i == 0) || (qubitOne && i != 0)) { + return {std::optional(v), true}; + } + return {std::optional(v), false}; + } + for (const auto& [v, d] : doubleValues) { + if ((qubitZero && std::norm(d) < 1e-4) || + (qubitOne && std::norm(d) >= 1e-4)) { + return {std::optional(v), true}; + } + return {std::optional(v), false}; + } + return {std::optional(), false}; +} + +} // namespace mlir::qco + +#endif // MQT_CORE_HYBRIDSTATE From af69c0cd95f8fd1cffc065ef6dcaba5ceb62d052 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 17 Jun 2026 16:18:23 +0200 Subject: [PATCH 080/131] =?UTF-8?q?=F0=9F=9A=A7=20Added=20tests=20for=20Hy?= =?UTF-8?q?bridState?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConstantPropagation/HybridState.hpp | 2 +- .../ConstantPropagation/HybridState.cpp | 33 +- .../ConstantPropagation/QuantumState.cpp | 3 + .../Transforms/Optimizations/CMakeLists.txt | 3 +- .../ConstantPropagation/test_hybridState.cpp | 435 ++++++++++++++++++ .../ConstantPropagation/test_quantumState.cpp | 28 +- 6 files changed, 478 insertions(+), 26 deletions(-) create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 5a2b13e373..35150ce422 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -110,7 +110,7 @@ class HybridState { : qState->measureQubit(quantumTarget); for (int64_t i = 0; i < 2; ++i) { - if (!availableStates.at(i)) { + if (!availableStates.contains(i) || !availableStates.at(i)) { continue; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 6504425a6e..06a8e2faca 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -34,17 +34,30 @@ HybridState::~HybridState() { void HybridState::print(std::ostream& os) const { os << this->toString(); } std::string HybridState::toString() const { + if (top) { + return "TOP"; + } + std::string str = "{" + this->qState->toString() + "}: "; unsigned int i = 0; + bool first = true; for (const auto& key : integerValues.keys()) { + if (!first) { + str += ", "; + } + first = false; str += "integerValue" + std::to_string(i) + " = " + - std::to_string(integerValues.at(key)) + ", "; + std::to_string(integerValues.at(key)); ++i; } unsigned int j = 0; - for (const auto& key : integerValues.keys()) { + for (const auto& key : doubleValues.keys()) { + if (!first) { + str += ", "; + } + first = false; str += "doubleValue" + std::to_string(j) + " = " + - std::format("{:.2f}", doubleValues.at(key)) + ", "; + std::format("{:.2f}", doubleValues.at(key)); ++j; } if (i > 0 || j > 0) { @@ -124,12 +137,12 @@ void HybridState::propagateGate(Operation* gate, } catch (std::domain_error const&) { top = true; } - } - - try { - qState->propagateGate(gate, targets, ctrlsQuantum); - } catch (std::domain_error const&) { - top = true; + } else { + try { + qState->propagateGate(gate, targets, ctrlsQuantum); + } catch (std::domain_error const&) { + top = true; + } } } @@ -199,7 +212,7 @@ HybridState HybridState::unify(HybridState that) { newHybridState.top = true; return newHybridState; } - newHybridState.probability *= this->probability; + newHybridState.probability = this->probability * that.probability; auto newIntegerValues = llvm::DenseMap( integerValues.size() + that.integerValues.size()); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 91f2372473..ad9f4f66c8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -42,6 +42,9 @@ QuantumState::QuantumState(const std::span globalQubitNumber, void QuantumState::print(std::ostream& os) const { os << this->toString(); } std::string QuantumState::toString() const { + if (nQubits == 0) { + return ""; + } std::string str; bool first = true; for (auto ordered = diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 1551c633fe..23235ef5b8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -9,7 +9,8 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable(${target_name} test_qco_hadamard_lifting.cpp test_qco_merge_single_qubit_rotation.cpp - ConstantPropagation/test_quantumState.cpp) + ConstantPropagation/test_quantumState.cpp + ConstantPropagation/test_hybridState.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp new file mode 100644 index 0000000000..e63452aabb --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -0,0 +1,435 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" + +#include +#include +#include + +#include +#include +#include + +using namespace mlir::qco; + +class HybridStateTest : public testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + std::vector fourQubits = {0, 1, 2, 3}; + std::vector vectorZero = {0}; + std::vector vectorOne = {1}; + std::vector vectorTwo = {2}; + std::vector vectorThree = {3}; + std::vector vectorFour = {4}; + std::vector vectorZeroOne = {0, 1}; + std::vector vectorOneThree = {1, 3}; + std::vector vectorTwoOne = {2, 1}; + std::vector vectorZeroTwoFour = {0, 2, 4}; + + HOp hOp; + XOp xOp; + ZOp zOp; + SOp sOp; + UOp uOp; + + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + + HybridStateTest() : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(4); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + sOp = SOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + uOp = UOp::create(programBuilder, programBuilder.getLoc(), {q[0].getType()}, + {q[0], q[1], q[2], q[3]}); + v1 = q[0]; + v2 = q[1]; + v3 = q[2]; + } + + void TearDown() override {} +}; + +TEST_F(HybridStateTest, ApplyHGate) { + auto hState = HybridState(vectorZero, 4); + hState.propagateGate(hOp.getOperation(), vectorZero); + + EXPECT_THAT(hState.toString(), + testing::HasSubstr("{|0> -> 0.71, |1> -> 0.71}: p = 1.00;")); +} + +TEST_F(HybridStateTest, ApplyHGateToThirdQubit) { + auto hState = HybridState(fourQubits, 4, 0.5); + hState.propagateGate(hOp.getOperation(), vectorTwo); + + EXPECT_THAT( + hState.toString(), + testing::HasSubstr("{|0000> -> 0.71, |0100> -> 0.71}: p = 0.50;")); +} + +TEST_F(HybridStateTest, ApplyParametrizedGateToThirdQubit) { + std::vector params = {v1, v2, v3}; + auto hState = HybridState(fourQubits, 4); + hState.addIntegerValue(v1, 1); + hState.addDoubleValue(v2, 0.5); + hState.addDoubleValue(v3, 2.0); + hState.propagateGate(hOp.getOperation(), vectorTwo); + hState.propagateGate(uOp.getOperation(), vectorTwo, {}, {}, {}, params); + + const auto resStr = hState.toString(); + EXPECT_THAT(resStr, testing::HasSubstr( + "{|0000> -> 0.76 - i0.31, |0100> -> -0.20 + i0.53}")); + EXPECT_THAT(resStr, testing::ContainsRegex("integerValue0 = 1")); + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = 2.00"), + testing::HasSubstr("doubleValue1 = 2.00"))); + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = 0.50"), + testing::HasSubstr("doubleValue1 = 0.50"))); +} + +TEST_F(HybridStateTest, ApplyQuantumControlledGate) { + auto hState = HybridState(fourQubits, 4); + hState.propagateGate(hOp.getOperation(), vectorOne); + hState.propagateGate(xOp.getOperation(), vectorThree); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorOne); + + EXPECT_THAT(hState.toString(), + testing::HasSubstr("{|0010> -> 0.71, |1000> -> 0.71}")); +} + +TEST_F(HybridStateTest, ApplyClassicalControlledGateThatsFalse) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrl = {v1}; + hState.addIntegerValue(v1, 0); + hState.propagateGate(xOp.getOperation(), vectorThree); + hState.propagateGate(hOp.getOperation(), vectorOne); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorOne, ctrl); + + EXPECT_THAT( + hState.toString(), + testing::HasSubstr( + "{|1000> -> 0.71, |1010> -> 0.71}: integerValue0 = 0; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyClassicalControlledGateThatsTrue) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrl = {v1}; + hState.addIntegerValue(v1, 1); + hState.propagateGate(xOp.getOperation(), vectorThree); + hState.propagateGate(hOp.getOperation(), vectorOne); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorOne, ctrl); + + EXPECT_THAT( + hState.toString(), + testing::HasSubstr( + "{|0010> -> 0.71, |1000> -> 0.71}: integerValue0 = 1; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyNegClassicalControlledGateThatsFalse) { + auto hState = HybridState(fourQubits, 4); + constexpr auto v1 = mlir::Value(); + std::vector ctrl = {v1}; + hState.addIntegerValue(v1, 0); + hState.propagateGate(xOp.getOperation(), vectorThree); + hState.propagateGate(hOp.getOperation(), vectorOne); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorOne, {}, ctrl); + + EXPECT_THAT( + hState.toString(), + testing::HasSubstr( + "{|0010> -> 0.71, |1000> -> 0.71}: integerValue0 = 0; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyTwoTimesClassicalControlledGate) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrls = {v1, v2}; + hState.addIntegerValue(v1, 1); + hState.addIntegerValue(v2, 3); + hState.propagateGate(xOp.getOperation(), vectorThree); + hState.propagateGate(hOp.getOperation(), vectorOne); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorOne, ctrls); + + const auto resStr = hState.toString(); + EXPECT_THAT(resStr, testing::HasSubstr("{|0010> -> 0.71, |1000> -> 0.71}: ")); + EXPECT_THAT(resStr, testing::HasSubstr("; p = 1.00")); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 3"), + testing::HasSubstr("integerValue1 = 3"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 1"), + testing::HasSubstr("integerValue1 = 1"))); +} + +TEST_F(HybridStateTest, handleErrorIfTwoManyAmplitudesAreNonzero) { + auto hState = HybridState(fourQubits, 2); + hState.propagateGate(hOp.getOperation(), vectorThree); + hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); + // Error occures here + hState.propagateGate(hOp.getOperation(), vectorTwo); + // Should leave state in TOP + hState.propagateGate(sOp.getOperation(), vectorZero); + + EXPECT_TRUE(hState.isHybridStateTop()); +} + +TEST_F(HybridStateTest, doMeasurementWithOneResult) { + auto hState = HybridState(fourQubits, 4, 0.6); + hState.addIntegerValue(v1, 0); + hState.propagateGate(xOp.getOperation(), vectorZero); + const auto resStates = hState.propagateMeasurement(0, v1); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT( + resHybridState.toString(), + testing::HasSubstr("{|0001> -> 1.00}: integerValue0 = 1; p = 0.60")); +} + +TEST_F(HybridStateTest, doMeasurementWithTwoResults) { + auto hState = HybridState(fourQubits, 4, 0.6); + hState.addIntegerValue(v1, 10); + hState.propagateGate(hOp.getOperation(), vectorZero); + hState.propagateGate(xOp.getOperation(), vectorTwo, vectorZero); + const auto resStates = hState.propagateMeasurement(0, v1); + + EXPECT_TRUE(resStates.size() == 2); + const auto resStrings = + resStates.at(0).toString() + resStates.at(1).toString(); + EXPECT_THAT(resStrings, testing::HasSubstr( + "{|0000> -> 1.00}: integerValue0 = 0; p = 0.30")); + EXPECT_THAT(resStrings, testing::HasSubstr( + "{|0101> -> 1.00}: integerValue0 = 1; p = 0.30")); +} + +TEST_F(HybridStateTest, doMeasurementWithNegClassicalCtrl) { + auto hState = HybridState(fourQubits, 4, 0.6); + constexpr auto v1 = mlir::Value(); + hState.addIntegerValue(v1, 0); + std::vector ctrl = {v1}; + hState.propagateGate(hOp.getOperation(), vectorZero); + const auto resStates = hState.propagateMeasurement(0, v1, ctrl); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT( + resHybridState.toString(), + testing::HasSubstr( + "{|0000> -> 0.71, |0001> -> 0.71}: integerValue0 = 0; p = 0.60")); +} + +TEST_F(HybridStateTest, doMeasurementWithPosNegClassicalCtrl) { + auto hState = HybridState(fourQubits, 4, 0.6); + constexpr auto v1 = mlir::Value(); + hState.addIntegerValue(v1, 3); + std::vector ctrl = {v1}; + hState.propagateGate(hOp.getOperation(), vectorZero); + const auto resStates = hState.propagateMeasurement(0, v1, {}, ctrl); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT( + resHybridState.toString(), + testing::HasSubstr( + "{|0000> -> 0.71, |0001> -> 0.71}: integerValue0 = 3; p = 0.60")); +} + +TEST_F(HybridStateTest, doResetWithOneResult) { + auto hState = HybridState(vectorZero, 2); + hState.addIntegerValue(v1, 3); + std::vector ctrl = {v1}; + hState.propagateGate(xOp.getOperation(), vectorZero); + + const auto resStates = hState.propagateReset(0, ctrl); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT(resHybridState.toString(), + testing::HasSubstr("{|0> -> 1.00}: integerValue0 = 3; p = 1.00")); +} + +TEST_F(HybridStateTest, doResetWithTwoResults) { + auto hState = HybridState(fourQubits, 2, 0.6); + hState.addIntegerValue(v1, 0); + std::vector ctrl = {v1}; + hState.propagateGate(hOp.getOperation(), vectorZero); + hState.propagateGate(xOp.getOperation(), vectorThree, vectorZero); + + const auto resStates = hState.propagateReset(0, {}, ctrl); + + EXPECT_TRUE(resStates.size() == 2); + const auto resString = + resStates.at(0).toString() + resStates.at(1).toString(); + EXPECT_THAT(resString, testing::HasSubstr( + "{|0000> -> 1.00}: integerValue0 = 0; p = 0.30")); + EXPECT_THAT(resString, testing::HasSubstr( + "{|1000> -> 1.00}: integerValue0 = 0; p = 0.30")); +} + +TEST_F(HybridStateTest, doResetWithNegClassicalCtrl) { + auto hState = HybridState(fourQubits, 4, 0.6); + constexpr auto v1 = mlir::Value(); + hState.addIntegerValue(v1, 0); + std::vector ctrl = {v1}; + hState.propagateGate(hOp.getOperation(), vectorZero); + const auto resStates = hState.propagateReset(0, ctrl); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT( + resHybridState.toString(), + testing::HasSubstr( + "{|0000> -> 0.71, |0001> -> 0.71}: integerValue0 = 0; p = 0.60")); +} + +TEST_F(HybridStateTest, doResetWithPosNegClassicalCtrl) { + auto hState = HybridState(fourQubits, 4, 0.6); + constexpr auto v1 = mlir::Value(); + hState.addIntegerValue(v1, 3); + std::vector ctrl = {v1}; + hState.propagateGate(hOp.getOperation(), vectorZero); + const auto resStates = hState.propagateReset(0, {}, ctrl); + + EXPECT_TRUE(resStates.size() == 1); + const auto resHybridState = resStates.at(0); + EXPECT_THAT( + resHybridState.toString(), + testing::HasSubstr( + "{|0000> -> 0.71, |0001> -> 0.71}: integerValue0 = 3; p = 0.60")); +} + +TEST_F(HybridStateTest, doMeasurementOnTop) { + auto hState = HybridState(fourQubits, 2); + constexpr auto v1 = mlir::Value(); + hState.propagateGate(hOp.getOperation(), vectorThree); + hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); + // Error occurs here + hState.propagateGate(hOp.getOperation(), vectorTwo); + // Should leave state in TOP + hState.addIntegerValue(v1, 3); + hState.propagateMeasurement(0, v1); + + EXPECT_TRUE(hState.isHybridStateTop()); +} + +TEST_F(HybridStateTest, doResetOnTop) { + auto hState = HybridState(fourQubits, 2); + hState.propagateGate(hOp.getOperation(), vectorThree); + hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); + // Error occures here + hState.propagateGate(hOp.getOperation(), vectorTwo); + // Should leave state in TOP + hState.addIntegerValue(v1, 3); + hState.propagateReset(0); + + EXPECT_TRUE(hState.isHybridStateTop()); +} + +TEST_F(HybridStateTest, unifyTwoHybridStates) { + + auto hState1 = HybridState(vectorZeroTwoFour, 10, 0.8); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorZero, vectorTwo); + hState1.addIntegerValue(v1, 4); + + auto hState2 = HybridState(vectorOneThree, 10, 0.5); + hState2.propagateGate(hOp.getOperation(), vectorThree); + hState2.propagateGate(xOp.getOperation(), vectorOne, vectorThree); + hState1.addIntegerValue(v2, 7); + hState1.addDoubleValue(v3, 4.2); + + const HybridState unified = hState1.unify(hState2); + const auto resStr = unified.toString(); + + EXPECT_THAT(resStr, + testing::HasSubstr("{|00000> -> 0.50, |01010> -> 0.50, " + "|10101> -> 0.50, |11111> -> 0.50}: ")); + EXPECT_THAT(resStr, testing::HasSubstr("doubleValue0 = 4.20; p = 0.40")); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 4"), + testing::HasSubstr("integerValue1 = 4"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 7"), + testing::HasSubstr("integerValue1 = 7"))); +} + +TEST_F(HybridStateTest, unifyHybridStatesOneWithoutQuantum) { + auto hState1 = HybridState(fourQubits, 10, 0.8); + hState1.propagateGate(hOp.getOperation(), vectorOne); + hState1.addIntegerValue(v1, 4); + + const auto hState2 = HybridState({}, 10, 0.5); + hState1.addIntegerValue(v2, 7); + hState1.addDoubleValue(v3, 4.2); + + const HybridState unified = hState1.unify(hState2); + const auto resStr = unified.toString(); + + EXPECT_THAT(resStr, testing::HasSubstr("{|0000> -> 0.71, |0010> -> 0.71}: ")); + EXPECT_THAT(resStr, testing::HasSubstr(", doubleValue0 = 4.20; p = 0.40")); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 7"), + testing::HasSubstr("integerValue1 = 7"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 4"), + testing::HasSubstr("integerValue1 = 4"))); +} + +TEST_F(HybridStateTest, unifyHybridStatesWithoutQuantum) { + auto hState1 = HybridState({}, 10, 0.8); + hState1.addIntegerValue(v1, 4); + + const auto hState2 = HybridState({}, 10, 0.5); + hState1.addIntegerValue(v2, 7); + hState1.addDoubleValue(v3, 4.2); + + const HybridState unified = hState1.unify(hState2); + const auto resStr = unified.toString(); + + EXPECT_THAT(resStr, testing::HasSubstr("{}: ")); + EXPECT_THAT(resStr, testing::HasSubstr(", doubleValue0 = 4.20; p = 0.40")); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 4"), + testing::HasSubstr("integerValue1 = 4"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 7"), + testing::HasSubstr("integerValue1 = 7"))); +} + +TEST_F(HybridStateTest, unifyTooLargeHybridStates) { + auto hState1 = HybridState(vectorZeroTwoFour, 3); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorZero, vectorTwo); + + auto hState2 = HybridState(vectorOneThree, 3); + hState2.propagateGate(hOp.getOperation(), vectorThree); + hState2.propagateGate(xOp.getOperation(), vectorOne, vectorThree); + + const auto hs = hState1.unify(hState2); + + EXPECT_TRUE(hs.isHybridStateTop()); +} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 3d17e46eb8..1314e86c7c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -185,7 +185,7 @@ TEST_F(QuantumStateTest, ApplySwapGate) { testing::HasSubstr("|0000> -> 0.71, |1000> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyControlledGate) { +TEST_F(QuantumStateTest, ApplyControlledGate1) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorOne); qState.propagateGate(xOp.getOperation(), vectorThree); @@ -195,7 +195,7 @@ TEST_F(QuantumStateTest, ApplyControlledGate) { testing::HasSubstr("|0010> -> 0.71, |1000> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyPosNegControlledGate) { +TEST_F(QuantumStateTest, ApplyControlledGate2) { auto qState = QuantumState(fourQubits, 8); qState.propagateGate(hOp.getOperation(), vectorZero); qState.propagateGate(hOp.getOperation(), vectorOne); @@ -232,9 +232,9 @@ TEST_F(QuantumStateTest, propagateGateCheckErrorIfTwoManyAmplitudesAreNonzero) { TEST_F(QuantumStateTest, doMeasurementWithZeroResult) { auto qState = QuantumState(vectorZero, 2); - const auto [states, numberOfStates] = qState.measureQubit(0); + const auto [states, availableStates] = qState.measureQubit(0); - EXPECT_TRUE(numberOfStates == 1); + EXPECT_TRUE(availableStates.size() == 1); auto [probability, qs] = states.at(0); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); @@ -243,9 +243,9 @@ TEST_F(QuantumStateTest, doMeasurementWithZeroResult) { TEST_F(QuantumStateTest, doMeasurementWithOneResult) { auto qState = QuantumState(vectorZero, 2); qState.propagateGate(xOp.getOperation(), vectorZero); - const auto [states, numberOfStates] = qState.measureQubit(0); + const auto [states, availableStates] = qState.measureQubit(0); - EXPECT_TRUE(numberOfStates == 1); + EXPECT_TRUE(availableStates.size() == 1); auto [probability, qs] = states.at(1); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); @@ -255,14 +255,14 @@ TEST_F(QuantumStateTest, doMeasurementWithTwoResults) { auto qState = QuantumState(vectorZeroOne, 2); qState.propagateGate(hOp.getOperation(), vectorZero); qState.propagateGate(xOp.getOperation(), vectorOne, vectorZero); - const auto [states, numberOfStates] = qState.measureQubit(0); + const auto [states, availableStates] = qState.measureQubit(0); auto const zeroReference = QuantumState(vectorZeroOne, 2); auto oneReference = QuantumState(vectorZeroOne, 2); oneReference.propagateGate(xOp.getOperation(), vectorZero); oneReference.propagateGate(xOp.getOperation(), vectorOne); - EXPECT_TRUE(numberOfStates == 2); + EXPECT_TRUE(availableStates.size() == 2); auto [probabilityZero, qsZero] = states.at(0); EXPECT_TRUE(zeroReference == *qsZero.get()); EXPECT_DOUBLE_EQ(probabilityZero, 0.5); @@ -273,9 +273,9 @@ TEST_F(QuantumStateTest, doMeasurementWithTwoResults) { TEST_F(QuantumStateTest, doResetWithOnlyZeros) { auto qState = QuantumState(vectorZero, 2); - const auto [states, numberOfStates] = qState.resetQubit(0); + const auto [states, availableStates] = qState.resetQubit(0); - EXPECT_TRUE(numberOfStates == 1); + EXPECT_TRUE(availableStates.size() == 1); auto [probability, qs] = states.at(0); EXPECT_TRUE(qState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); @@ -284,11 +284,11 @@ TEST_F(QuantumStateTest, doResetWithOnlyZeros) { TEST_F(QuantumStateTest, doResetWithOnlyOnes) { auto qState = QuantumState(vectorZero, 2); qState.propagateGate(xOp.getOperation(), vectorZero); - const auto [states, numberOfStates] = qState.resetQubit(0); + const auto [states, availableStates] = qState.resetQubit(0); auto const refState = QuantumState(vectorZero, 2); - EXPECT_TRUE(numberOfStates == 1); + EXPECT_TRUE(availableStates.size() == 1); auto [probability, qs] = states.at(1); EXPECT_TRUE(refState == *qs.get()); EXPECT_DOUBLE_EQ(probability, 1); @@ -298,13 +298,13 @@ TEST_F(QuantumStateTest, doResetWithZerosAndOnes) { auto qState = QuantumState(vectorZeroOne, 2); qState.propagateGate(hOp.getOperation(), vectorZero); qState.propagateGate(xOp.getOperation(), vectorOne, vectorZero); - const auto [states, numberOfStates] = qState.resetQubit(0); + const auto [states, availableStates] = qState.resetQubit(0); auto const zeroReference = QuantumState(vectorZeroOne, 2); auto oneReference = QuantumState(vectorZeroOne, 2); oneReference.propagateGate(xOp.getOperation(), vectorOne); - EXPECT_TRUE(numberOfStates == 2); + EXPECT_TRUE(availableStates.size() == 2); auto [probabilityZero, qsZero] = states.at(0); EXPECT_DOUBLE_EQ(probabilityZero, 0.5); auto [probabilityOne, qsOne] = states.at(1); From c77bb0654fbbdaf2f352be7007739ea20d783093 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 08:34:08 +0200 Subject: [PATCH 081/131] :white_check_mark: Added tests for classical operations --- .../ClassicalArithOperation.h | 14 +- .../ConstantPropagation/HybridState.cpp | 16 +- .../ConstantPropagation/test_hybridState.cpp | 142 ++++++++++++++++++ 3 files changed, 157 insertions(+), 15 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h index 1c020afb99..15d8f351f4 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -10,7 +10,6 @@ #pragma once -#include #ifndef MQT_CORE_CLASSICALARITHOPERATION_H #define MQT_CORE_CLASSICALARITHOPERATION_H @@ -18,6 +17,8 @@ #include "mlir/Dialect/QCO/Utils/Drivers.h" #include +#include +#include #include #include @@ -27,8 +28,9 @@ * the result of valid arith operations. Operations are only valid with one to * two operands, not if they are applied to sequences. */ -inline int64_t getArithOpResult(mlir::Operation* operation, int64_t value1, - int64_t value2 = 0, int64_t value3 = 0) { +inline int64_t getArithIntegerOpResult(mlir::Operation* operation, + int64_t value1, int64_t value2 = 0, + int64_t value3 = 0) { for (mlir::Value operand : operation->getOperands()) { if (isa(operand.getType())) { @@ -65,15 +67,15 @@ inline int64_t getArithOpResult(mlir::Operation* operation, int64_t value1, .Case([&](auto) { return value1 - value2; }) .Case([&](auto) { return value1 ^ value2; }) .Case( - [&](auto) { return value3 == 0 ? value3 : value2; }) + [&](auto) { return value1 == 0 ? value3 : value2; }) .Default([&](auto) -> int64_t { throw std::runtime_error("Unsupported integer operation in " "mlir::qco::classicalarithoperation"); }); } -inline double getArithOpResult(mlir::Operation* operation, double value1, - double value2 = 0.0) { +inline double getArithDoubleOpResult(mlir::Operation* operation, double value1, + double value2 = 0.0) { for (mlir::Value operand : operation->getOperands()) { if (isa(operand.getType())) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 06a8e2faca..cfdfbabadc 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -181,24 +181,22 @@ void HybridState::propagateClassicalOperation( "HybridState needs a classical value for a classical operation that " "is not existent in current HybridState."); } - const int64_t opRes = - getArithOpResult(op, integerValues.at(operand1), - operand2 == nullptr ? 0 : integerValues.at(operand2), - operand3 == nullptr ? 0 : integerValues.at(operand3)); + const int64_t opRes = getArithIntegerOpResult( + op, integerValues.at(operand1), + operand2 == nullptr ? 0 : integerValues.at(operand2), + operand3 == nullptr ? 0 : integerValues.at(operand3)); integerValues[dest] = opRes; } else { if (!doubleValues.contains(operand1) || (operand2 != nullptr && !doubleValues.contains(operand2)) || - (operand3 != nullptr && !doubleValues.contains(operand3)) || !doubleValues.contains(dest)) { throw std::domain_error( "HybridState needs a classical value for a classical operation that " "is not existent in current HybridState."); } - const double opRes = - getArithOpResult(op, doubleValues.at(operand1), - operand2 == nullptr ? 0 : doubleValues.at(operand2), - operand3 == nullptr ? 0 : doubleValues.at(operand3)); + const double opRes = getArithDoubleOpResult( + op, doubleValues.at(operand1), + operand2 == nullptr ? 0.0 : doubleValues.at(operand2)); doubleValues[dest] = opRes; } } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index e63452aabb..2699151993 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -48,6 +48,7 @@ class HybridStateTest : public testing::Test { mlir::Value v1; mlir::Value v2; mlir::Value v3; + mlir::Value v4; HybridStateTest() : programBuilder(&context), referenceBuilder(&context) {} @@ -75,6 +76,7 @@ class HybridStateTest : public testing::Test { v1 = q[0]; v2 = q[1]; v3 = q[2]; + v4 = q[3]; } void TearDown() override {} @@ -433,3 +435,143 @@ TEST_F(HybridStateTest, unifyTooLargeHybridStates) { EXPECT_TRUE(hs.isHybridStateTop()); } + +TEST_F(HybridStateTest, intOpTwoValueOperation) { + auto hState = HybridState(fourQubits, 3); + const auto i1 = programBuilder.getI64IntegerAttr(3); + const auto i2 = programBuilder.getI64IntegerAttr(9); + const auto i3 = programBuilder.getI64IntegerAttr(0); + + const mlir::Value val1 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i1); + const mlir::Value val2 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i2); + const mlir::Value val3 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i3); + + hState.addIntegerValue(val1, 3); + hState.addIntegerValue(val2, 9); + hState.addIntegerValue(val3, 0); + + const auto subIOp = mlir::arith::SubIOp::create( + programBuilder, programBuilder.getLoc(), val3.getType(), val1, val2); + + hState.propagateClassicalOperation(subIOp, val3, val1, val2); + + const auto resStr = hState.toString(); + + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 3"), + testing::HasSubstr("integerValue1 = 3"), + testing::HasSubstr("integerValue2 = 3"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 9"), + testing::HasSubstr("integerValue1 = 9"), + testing::HasSubstr("integerValue2 = 9"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = -6"), + testing::HasSubstr("integerValue1 = -6"), + testing::HasSubstr("integerValue2 = -6"))); +} + +TEST_F(HybridStateTest, intOpThreeValueOperation) { + auto hState = HybridState(fourQubits, 3); + const auto i0 = programBuilder.getI64IntegerAttr(0); + const auto i1 = programBuilder.getI64IntegerAttr(3); + const auto i2 = programBuilder.getI64IntegerAttr(9); + const auto i3 = programBuilder.getI64IntegerAttr(0); + + const mlir::Value val0 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i0); + const mlir::Value val1 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i1); + const mlir::Value val2 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i2); + const mlir::Value val3 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i3); + + hState.addIntegerValue(val0, 0); + hState.addIntegerValue(val1, 3); + hState.addIntegerValue(val2, 9); + hState.addIntegerValue(val3, 1); + + const auto selectOp = + mlir::arith::SelectOp::create(programBuilder, programBuilder.getLoc(), + val3.getType(), val0, val1, val2); + const auto subIOp = mlir::arith::SubIOp::create( + programBuilder, programBuilder.getLoc(), val3.getType(), val3, val1); + + hState.propagateClassicalOperation(selectOp, val3, val0, val1, val2); + hState.propagateClassicalOperation(subIOp, val3, val3, val1); + + const auto resStr = hState.toString(); + + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 0"), + testing::HasSubstr("integerValue1 = 0"), + testing::HasSubstr("integerValue2 = 0"), + testing::HasSubstr("integerValue3 = 0"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 3"), + testing::HasSubstr("integerValue1 = 3"), + testing::HasSubstr("integerValue2 = 3"), + testing::HasSubstr("integerValue3 = 3"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 9"), + testing::HasSubstr("integerValue1 = 9"), + testing::HasSubstr("integerValue2 = 9"), + testing::HasSubstr("integerValue3 = 9"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("integerValue0 = 6"), + testing::HasSubstr("integerValue1 = 6"), + testing::HasSubstr("integerValue2 = 6"), + testing::HasSubstr("integerValue3 = 6"))); +} + +TEST_F(HybridStateTest, doubleOpOneValueOperation) { + auto hState = HybridState(fourQubits, 3); + const auto i1 = programBuilder.getF64FloatAttr(-2.7); + const auto i2 = programBuilder.getF64FloatAttr(0); + + const mlir::Value val1 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i1); + const mlir::Value val2 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i2); + + hState.addDoubleValue(val1, -2.7); + hState.addDoubleValue(val2, 0); + + const auto negFOp = mlir::arith::NegFOp::create( + programBuilder, programBuilder.getLoc(), val2.getType(), val1); + + hState.propagateClassicalOperation(negFOp, val2, val1); + + const auto resStr = hState.toString(); + + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = -2.7"), + testing::HasSubstr("doubleValue1 = -2.7"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("doubleValue0 = 2.7"), + testing::HasSubstr("doubleValue1 = 2.7"))); +} + +TEST_F(HybridStateTest, doubleOpTwoValueOperation) { + auto hState = HybridState(fourQubits, 3); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(1.3); + + const mlir::Value val1 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i1); + const mlir::Value val2 = mlir::arith::ConstantOp::create( + programBuilder, programBuilder.getLoc(), i2); + + hState.addDoubleValue(val1, -2.5); + hState.addDoubleValue(val2, 1.3); + + const auto mulFOp = mlir::arith::MulFOp::create( + programBuilder, programBuilder.getLoc(), val2.getType(), val1); + + hState.propagateClassicalOperation(mulFOp, val2, val1, val2); + + const auto resStr = hState.toString(); + + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = -2.50"), + testing::HasSubstr("doubleValue1 = -2.50"))); + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = -3.25"), + testing::HasSubstr("doubleValue1 = -3.25"))); +} From 2cbc8ae405e982d97cb27fbc981e901f4e50c00f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 09:47:10 +0200 Subject: [PATCH 082/131] :recycle: Refactored HybridState --- .../ConstantPropagation/HybridState.hpp | 20 ++++++++++++++----- .../ConstantPropagation/HybridState.cpp | 18 ++++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 35150ce422..057bbbe4b4 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -13,11 +13,18 @@ #include "QuantumState.hpp" +#include #include +#include +#include +#include +#include #include #include +#include #include +#include #include namespace mlir::qco { @@ -95,7 +102,8 @@ class HybridState { * outcomes. */ std::vector - propagateMeasurementOrReset(const unsigned int quantumTarget, bool reset, + propagateMeasurementOrReset(const unsigned int quantumTarget, + const bool reset, const Value classicalTarget = nullptr, const std::span posCtrlsClassical = {}, const std::span negCtrlsClassical = {}) { @@ -109,7 +117,7 @@ class HybridState { reset ? qState->resetQubit(quantumTarget) : qState->measureQubit(quantumTarget); - for (int64_t i = 0; i < 2; ++i) { + for (const size_t i : {0, 1}) { if (!availableStates.contains(i) || !availableStates.at(i)) { continue; } @@ -249,7 +257,7 @@ class HybridState { * @throw std::domain_error If the unified QuantumState would exceed * maxNonzeroAmplitudes of this. */ - HybridState unify(HybridState that); + HybridState unify(const HybridState& that); bool operator==(const HybridState& that) const; @@ -300,8 +308,10 @@ class HybridState { * exists and true, if the qubit is equivalent to the value. False, if the * qubit is the inverse of the value. */ - std::pair, bool> - getValueThatIsEquivalentToQubit(unsigned int qubit) const; + [[nodiscard( + "HybridState::getValueThatIsEquivalentToQubit called but ignored")]] std:: + pair, bool> + getValueThatIsEquivalentToQubit(unsigned int qubit) const; }; } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index cfdfbabadc..74541eb3f0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -15,7 +15,18 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" +#include +#include + +#include +#include #include +#include +#include +#include +#include +#include +#include namespace mlir::qco { @@ -71,7 +82,8 @@ bool HybridState::operator==(const HybridState& that) const { if (top) { return that.top; } - if (probability != that.probability || *qState.get() != *that.qState.get()) { + if (std::fabs(probability - that.probability) > 1e-4 || + *qState.get() != *that.qState.get()) { return false; } @@ -88,7 +100,7 @@ bool HybridState::operator==(const HybridState& that) const { for (const auto& [d, v] : doubleValues) { if (!that.doubleValues.contains(d) || - std::norm(that.doubleValues.at(d)) > 1e-4) { + std::fabs(that.doubleValues.at(d)) > 1e-4) { return false; } } @@ -201,7 +213,7 @@ void HybridState::propagateClassicalOperation( } } -HybridState HybridState::unify(HybridState that) { +HybridState HybridState::unify(const HybridState& that) { auto newHybridState = HybridState(); try { newHybridState.qState = From 8b23135bf17a54f33bc3cb1d6a61d6fccda7f287 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 10:59:08 +0200 Subject: [PATCH 083/131] :construction: Added UnionTable infrastructure --- .../ConstantPropagation/UnionTable.hpp | 292 ++++++++++++++++++ .../ConstantPropagation/UnionTable.cpp | 87 ++++++ 2 files changed, 379 insertions(+) create mode 100644 mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp new file mode 100644 index 0000000000..ccaa5dace4 --- /dev/null +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_UNIONTABLE_H +#define MQT_CORE_UNIONTABLE_H +#include "HybridState.hpp" + +#include + +#include + +namespace mlir::qco { + +/** + * @brief Result of the check for superfluous values. + */ +struct SuperfluousResult { + bool completelySuperfluous = false; + std::vector superfluousQubits; + std::vector superfluousClassicalValues; +}; + +/** + * @brief This class represents a union table. + * + * This class holds multiple hybrid states and can propagate operations on the + * values in the states. + */ +class UnionTable { + bool allTop = false; + std::size_t maximumHybridEntries; + llvm::DenseMap globalQubitIndices; + llvm::DenseMap> valuesToEntries; + +public: + explicit UnionTable(std::size_t maxNonzeroAmplitudes, + std::size_t maximumHybridEntries); + + ~UnionTable(); + + void print(std::ostream& os) const; + + [[nodiscard("UnionTable::toString called but ignored")]] + std::string toString() const; + + [[nodiscard("UnionTable::allTop called but ignored")]] + bool areStatesAllTop(); + + /** + * @brief This method applies a gate to the qubits. + * + * This method changes the amplitudes of a QuantumState according to the + * applied gate. + * + * @param gate The gate to be applied. + * @param targets An array of the Values of the target qubits. + * @param newQuantumTargets The value of the qubits after the gate. + * @param ctrlsQuantum An array of the values of the ctrl qubits. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @param params The parameter applied to the gate. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. + */ + void propagateGate(Operation* gate, std::span targets, + std::span newQuantumTargets, + std::span ctrlsQuantum = {}, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}, + std::vector params = {}); + + /** + * @brief This method applies a measurement. + * + * This method applies a measurement, changing the qubits and the classical + * bit corresponding to the measurement. + * + * @param quantumTarget The value of the qubit to be measured. + * @param newQuantumValue The value of the qubit after the measurement. + * @param classicalTarget The value of the bit to save the measurement result + * in. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. This does not hold for the classical target, which can be + * newly created. + */ + void propagateMeasurement(Value quantumTarget, Value newQuantumValue, + Value classicalTarget, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + + /** + * @brief This method propagates a qubit reset. + * + * This method propagates a qubit reset. This means that the qubit is put into + * zero state. It is also put in its own QubitState again if it does not + * correspond to already assigned bit values. + * + * @param quantumTarget The value of the qubit to be reset. + * @param newQuantumValue The value of the qubit after the reset. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. + */ + void propagateReset(Value quantumTarget, Value newQuantumValue, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + + /** + * @brief This method propagates a qubit alloc. + * + * This method propagates a qubit alloc. This means that the qubit is added to + * the UnionTable in zero state. + * + * @param qubit The value of the qubit to be allocated. + */ + void propagateQubitAlloc(Value qubit); + + /** + * @brief This method propagates an int alloc. + * + * This method propagates an int alloc. This means that the int is added to + * the UnionTable as new HybridState. + * + * @param intValue The value of the int to be allocated. + * @param number The number that the int is initialized with. + */ + void propagateIntAlloc(Value intValue, int64_t number); + + /** + * @brief This method propagates a double alloc. + * + * This method propagates a double alloc. This means that the double is added + * to the UnionTable as new HybridState. + * + * @param doubleValue The value of the double to be allocated. + * @param number The number that the double is initialized with. + */ + void propagateDoubleAlloc(Value doubleValue, double number); + + [[nodiscard("UnionTable::isQubitAlwaysOne called but ignored")]] bool + isQubitAlwaysOne(Value q) const; + + [[nodiscard("UnionTable::isQubitAlwaysZero called but ignored")]] bool + isQubitAlwaysZero(Value q) const; + + [[nodiscard( + "UnionTable::isClassicalValueAlwaysTrue called but ignored")]] bool + isClassicalValueAlwaysTrue(Value c) const; + + [[nodiscard( + "UnionTable::isClassicalValueAlwaysFalse called but ignored")]] bool + isClassicalValueAlwaysFalse(Value c) const; + + /** + * @brief Checks if a given combination of values-qubit values has a nonzero + * probability. + * + * This method receives a number of qubit and values and checks whether + * they have for a given value always a zero amplitude. + * The values for the classical values are not the numeric ones, but whether + * they are zero (false) or non-zero (true). + * + * @param qubits The qubits which are being checked. + * @param qubitValue The value for which is tested whether there is a nonzero + * amplitude. + * @param classicalIntegerValues The integer values to check. + * @param classicalDoubleValues The double values to check. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. + * @returns True if the amplitude is always zero, false otherwise. + */ + [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool + hasAlwaysZeroProbability( + std::span qubits, unsigned int qubitValue, + std::span> classicalIntegerValues = {}, + std::span> classicalDoubleValues = {}) const; + + /** + * @brief Returns a classical value that is equivalent to qubit. + * + * Returns a classical value that is always true (=/= 0) when the given qubit + * is 1, and the boolean value true. Alternatively, it can return a value that + * is always false (== 0) if the qubit is 1. In that case, the returned bool + * is false. + * + * @param qubit Index of qubit. + * @returns Classical value that is equivalent or inverse to qubit if it + * exists and true, if the qubit is equivalent to the value. False, if the + * qubit is the inverse of the value. + */ + [[nodiscard( + "UnionTable::getValueThatIsEquivalentToQubit called but ignored")]] std:: + pair, bool> + getValueThatIsEquivalentToQubit(unsigned int qubit) const; + + /** + * @brief This method checks whether a diagonal gate only adds a global phase + * and returns it. + * + * This method receives a diagonal gate and checks, if only a global phase is + * added to the circuit by it under the current configuration. If that is the + * case, the returned optional contains the global phase. + * + * @param diagonalOp The gate to be checked. + * @param targets An array of the Values of the target qubits. + * @param ctrlsQuantum An array of the values of the ctrl qubits. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. + * @returns An optional containing the globally added value, if applicable. + */ + [[nodiscard("UnionTable::globalPhaseThatIsAdded called but ignored")]] + std::optional + globalPhaseThatIsAdded(Operation* diagonalOp, std::span targets, + std::span ctrlsQuantum = {}, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + + /** + * @brief This method checks which qubits and classical values are superfluous + * given a controlled gate. + * + * This method checks which qubits and classical values are superfluous given + * a controlled gate. If the gate can never be executed, the target qubits are + * superfluous. Apart from that, all posCtrl (negCtrl) qubits/values that are + * always true (false) are superfluous. + * + * @param qubitTargets The values of the target qubits. + * @param qubitCtrls The valuess of the positively controlling qubits. + * @param posCtrlsClassical The values of the positively controlling classical + * values. + * @param negCtrlsClassical The values of the negatively controlling classical + * values. + * @returns The superfluous result, i.e. the qubits and classical values that + * are superfluous and whether the whole operation is superfluous. + */ + static SuperfluousResult + getSuperfluousControls(std::span qubitTargets, + std::span qubitCtrls, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + + /** + * @brief This method checks whether there are satisfiable combinations of + * controls. + * + * @param qubitCtrls The values of the controlling qubit values. + * @param posCtrlsClassical The values of the positively controlling classical + * values. + * @param negCtrlsClassical The values of the negatively controlling classical + * values. + * @returns Whether there are satisfiable combinations or not. + */ + static bool + areThereSatisfiableCombinations(std::span qubitCtrls, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + + /** + * @brief Returns the qubits and classical values that imply the given qubit. + * + * This method checks whether in the given list are qubits or classical values + * that imply (are antecedents of) the given qubit. I.e. all qubits and + * classical values are returned for which holds: a -> q. + * + * @param q The qubit for which is checked whether it is implied. + * @param qubits The qubits for which are checked if they imply q. + * @param classicalPositive The values for which are checked if they imply q. + * @param classicalNegative The values for which their negations are checked + * if they imply q. + * @returns A pair of 1. qubits and 2. classical values that are antecedents + * of q. + */ + static std::pair, std::set> + getAntecedentsOfQubit(unsigned int q, std::span qubits, + std::span classicalPositive, + std::span classicalNegative); +}; +} // namespace mlir::qco + +#endif // MQT_CORE_UNIONTABLE_H diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp new file mode 100644 index 0000000000..582d6069e5 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#ifndef MQT_CORE_UNIONTABLE +#define MQT_CORE_UNIONTABLE +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" + +namespace mlir::qco { + +UnionTable::UnionTable(std::size_t maxNonzeroAmplitudes, + std::size_t maximumHybridEntries) {} + +UnionTable::~UnionTable() {} + +void UnionTable::print(std::ostream& os) const {} + +std::string UnionTable::toString() const {} + +bool UnionTable::areStatesAllTop() {} + +void UnionTable::propagateGate(Operation* gate, std::span targets, + std::span newQuantumTargets, + std::span ctrlsQuantum, + std::span posCtrlsClassical, + std::span negCtrlsClassical, + std::vector params) {} + +void UnionTable::propagateMeasurement(Value quantumTarget, + Value newQuantumValue, + Value classicalTarget, + std::span posCtrlsClassical, + std::span negCtrlsClassical) {} + +void UnionTable::propagateReset(Value quantumTarget, Value newQuantumValue, + std::span posCtrlsClassical, + std::span negCtrlsClassical) {} + +void UnionTable::propagateQubitAlloc(Value qubit) {} + +void UnionTable::propagateIntAlloc(Value intValue, int64_t number) {} + +void UnionTable::propagateDoubleAlloc(Value doubleValue, double number) {} + +bool UnionTable::isQubitAlwaysOne(Value q) const {} + +bool UnionTable::isQubitAlwaysZero(Value q) const {} + +bool UnionTable::isClassicalValueAlwaysTrue(Value c) const {} + +bool UnionTable::isClassicalValueAlwaysFalse(Value c) const {} + +bool UnionTable::hasAlwaysZeroProbability( + std::span qubits, unsigned int qubitValue, + std::span> classicalIntegerValues, + std::span> classicalDoubleValues) const {} + +std::pair, bool> +UnionTable::getValueThatIsEquivalentToQubit(unsigned int qubit) const {} + +std::optional UnionTable::globalPhaseThatIsAdded( + Operation* diagonalOp, std::span targets, + std::span ctrlsQuantum, std::span posCtrlsClassical, + std::span negCtrlsClassical) {} + +SuperfluousResult UnionTable::getSuperfluousControls( + std::span qubitTargets, std::span qubitCtrls, + std::span posCtrlsClassical, std::span negCtrlsClassical) {} + +bool UnionTable::areThereSatisfiableCombinations( + std::span qubitCtrls, std::span posCtrlsClassical, + std::span negCtrlsClassical) {} +std::pair, std::set> + +UnionTable::getAntecedentsOfQubit(unsigned int q, std::span qubits, + std::span classicalPositive, + std::span classicalNegative) {} + +} // namespace mlir::qco + +#endif // MQT_CORE_UNIONTABLE From e8b170d507c4c3fb678b75bd7e0fa80fa63f5585 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 18:24:55 +0200 Subject: [PATCH 084/131] :white_check_mark: Added UnionTable tests --- .../Transforms/Optimizations/CMakeLists.txt | 3 +- .../ConstantPropagation/test_unionTable.cpp | 767 ++++++++++++++++++ 2 files changed, 769 insertions(+), 1 deletion(-) create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 23235ef5b8..5d9dce19ba 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -10,7 +10,8 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable(${target_name} test_qco_hadamard_lifting.cpp test_qco_merge_single_qubit_rotation.cpp ConstantPropagation/test_quantumState.cpp - ConstantPropagation/test_hybridState.cpp) + ConstantPropagation/test_hybridState.cpp + ConstantPropagation/test_unionTable.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp new file mode 100644 index 0000000000..2933649309 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" + +#include +#include + +#include + +using namespace mlir::qco; + +class UnionTableTest : public testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + UnionTable ut = UnionTable(4, 4); + + HOp hOp; + XOp xOp; + SWAPOp swapOp; + + mlir::Value v0; + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + mlir::Value v4; + mlir::Value v5; + mlir::Value v6; + mlir::Value v7; + mlir::Value v8; + mlir::Value v9; + mlir::Value i0; + mlir::Value i1; + mlir::Value i2; + + std::vector q0; + std::vector q1; + std::vector q2; + std::vector q3; + std::vector q4; + std::vector q5; + std::vector q6; + std::vector q7; + std::vector q8; + std::vector q9; + + UnionTableTest() : programBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(10); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), + {q[0].getType()}, {q[0], q[1], q[2], q[3]}); + + v0 = q[0]; + v1 = q[1]; + v2 = q[2]; + v3 = q[3]; + v4 = q[4]; + v5 = q[5]; + v6 = q[6]; + v7 = q[7]; + v8 = q[8]; + v9 = q[9]; + + q0 = {v0}; + q1 = {v1}; + q2 = {v2}; + q3 = {v3}; + q4 = {v4}; + q5 = {v5}; + q6 = {v6}; + q7 = {v7}; + q8 = {v8}; + q9 = {v9}; + + const auto iAttr = programBuilder.getI64IntegerAttr(0); + + i0 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i1 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i2 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateQubitAlloc(v3); + } + + void TearDown() override {} +}; + +TEST_F(UnionTableTest, ApplyHGate) { + ut.propagateGate(hOp, q0, q5); + + EXPECT_THAT( + ut.toString(), + testing::HasSubstr( + "Qubits: 0, HybridStates: {{|0> -> 0.71, |1> -> 0.71}: p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyHGateToThirdQubit) { + ut.propagateGate(hOp, q2, q5); + + EXPECT_THAT( + ut.toString(), + testing::HasSubstr( + "Qubits: 2, HybridStates: {{|0> -> 0.71, |1> -> 0.71}: p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyQuantumControlledGate) { + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, q5); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 0, HybridStates: {{|0> -> 1.00}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 2, HybridStates: {{|0> -> 1.00}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 31, HybridStates: {{|01> -> 0.71, " + "|10> -> 0.71}: p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsFalse) { + std::vector classicalControl = {i1}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, {}, classicalControl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|10> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsTrue) { + std::vector classicalControl = {i0}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, {}, classicalControl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|00> " + "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { + std::vector classicalControl = {i0}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, {}, {}, classicalControl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|10> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsFalse) { + std::vector classicalControl = {i1}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, {}, classicalControl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|00> " + "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsFalse) { + std::vector classicalControlZero = {i0}; + std::vector classicalControlOne = {i1}; + ut.propagateIntAlloc(i0, 0); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, {}, classicalControlOne, classicalControlZero); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|10> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsTrue) { + std::vector classicalControlTrue = {i0}; + std::vector classicalControlFalse = {i1}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(xOp, q3, q6); + ut.propagateGate(xOp, q6, q7, classicalControlTrue, classicalControlFalse); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 31, HybridStates: {{|00> " + "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); +} + +TEST_F(UnionTableTest, ApplyControlledTwoBitGate) { + std::vector classicalControlTrue = {i0, i2}; + std::vector classicalControlFalse = {i1}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateIntAlloc(i2, 1); + ut.propagateGate(hOp, q1, q4); + ut.propagateGate(xOp, q3, q5); + ut.propagateGate(xOp, q5, q6, q4, classicalControlTrue, + classicalControlFalse); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 31, HybridStates: {{|01> " + "-> 0.71, |10> -> 0.71}: ")); +} + +TEST_F(UnionTableTest, doMeasurementWithOneResult) { + ut.propagateGate(xOp, q0, q4); + ut.propagateIntAlloc(i0, 10); + ut.propagateMeasurement(v4, v5, i0); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 0, HybridStates: {{|1> " + "-> 1.00}: integerValue0 = 1, p = 1.00;}")); +} + +TEST_F(UnionTableTest, doMeasurementWithTwoResults) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateIntAlloc(i0, 10); + ut.propagateMeasurement(v5, v6, i0); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, HybridStates: {{|00> " + "-> 1.00}: integerValue0 = 0, p = 0.50; {|11> " + "-> 1.00}: integerValue0 = 1, p = 0.50;}")); +} + +TEST_F(UnionTableTest, doMeasurementWithNegPosCtrl) { + std::vector ctrl = {i0}; + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateIntAlloc(i0, 0); + ut.propagateMeasurement(v5, v6, i0, ctrl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 10, HybridStates: {{|00> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); +} + +TEST_F(UnionTableTest, doMeasurementWithPosNegCtrl) { + std::vector ctrl = {i0}; + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateIntAlloc(i0, 10); + ut.propagateMeasurement(v5, v6, i0, {}, ctrl); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 10, HybridStates: {{|00> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 10, p = 1.00;}")); +} + +TEST_F(UnionTableTest, doResetWithOneResult) { + ut.propagateGate(xOp, q0, q4); + ut.propagateReset(v4, v5); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 0, HybridStates: {{|0> " + "-> 1.00}: p = 1.00;}")); +} + +TEST_F(UnionTableTest, doResetWithTwoResults) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateReset(v4, v6); + + EXPECT_THAT( + ut.toString(), + testing::HasSubstr("Qubits: 10, HybridStates: {{|00> " + "-> 1.00}: p = 0.50; {|10> -> 1.00}: p = 0.50;}")); +} + +TEST_F(UnionTableTest, swapGateApplicationDifferentStates) { + std::vector swapTargets = {v6, v7}; + std::vector swapDestinations = {v8, v9}; + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q5, q6); + ut.propagateGate(xOp, q2, q7); + ut.propagateGate(swapOp, swapTargets, swapDestinations); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 20, HybridStates: {{|01> -> 0.71, " + "|10> -> 0.71}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 1, HybridStates: {{|1> -> 1.00}: p = 1.00;}")); +} + +TEST_F(UnionTableTest, swapGateApplicationSameState) { + std::vector swapTargets = {v4, v6}; + std::vector swapDestinations = {v7, v8}; + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(hOp, q1, q5, q4); + ut.propagateGate(xOp, q5, q6); + ut.propagateGate(swapOp, swapTargets, swapDestinations); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, HybridStates: {{|01> -> 0.71, " + "|10> -> 0.71}: p = 1.00;}")); +} + +class UnionTableWithoutSetupAllocationsTest : public testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + + HOp hOp; + XOp xOp; + + mlir::Value v0; + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + mlir::Value v4; + mlir::Value v5; + mlir::Value i0; + + std::vector q0; + std::vector q1; + std::vector q2; + std::vector q3; + std::vector q4; + std::vector q5; + + UnionTableWithoutSetupAllocationsTest() + : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(6); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + v0 = q[0]; + v1 = q[1]; + v2 = q[2]; + v3 = q[3]; + v4 = q[4]; + v5 = q[5]; + + q0 = {v0}; + q1 = {v1}; + q2 = {v2}; + q3 = {v3}; + q4 = {v4}; + q5 = {v5}; + + const auto iAttr = programBuilder.getI64IntegerAttr(0); + + i0 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + } + + void TearDown() override {} +}; + +TEST_F(UnionTableWithoutSetupAllocationsTest, propagateQubitAlloc) { + auto ut = UnionTable(4, 2); + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 0, HybridStates: {{|0> -> 1.00}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr( + "Qubits: 1, HybridStates: {{|0> -> 1.00}: p = 1.00;}")); +} + +TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsAndGetToTop) { + auto ut = UnionTable(4, 1); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateGate(hOp, q1, q3); + ut.propagateMeasurement(v3, v4, i0); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 0, Bits: 0, HybridStates: {TOP}")); +} + +TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsOnTop) { + auto ut = UnionTable(2, 2); + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateGate(hOp, q0, q2); + ut.propagateGate(xOp, q1, q3, q2); + ut.propagateGate(hOp, q3, q4); // State enters TOP + ut.propagateMeasurement(v4, v5, i0); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); +} + +TEST_F(UnionTableWithoutSetupAllocationsTest, doResetOnTop) { + auto ut = UnionTable(2, 2); + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateGate(hOp, q0, q2); + ut.propagateGate(xOp, q1, q3, q2); + ut.propagateGate(hOp, q3, q4); // State enters TOP + ut.propagateReset(v4, v5); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); +} + +TEST_F(UnionTableWithoutSetupAllocationsTest, unifyTooLargeHybridStates) { + auto ut = UnionTable(4, 2); + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateGate(hOp, q0, q3); + ut.propagateMeasurement(v3, v4, i0); + ut.propagateGate(xOp, q1, q5, q4); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); +} + +class UnionTablePropertiesTest : public testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + UnionTable ut = UnionTable(3, 2); + + HOp hOp; + XOp xOp; + ZOp zOp; + SWAPOp swapOp; + + mlir::Value v0; + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + mlir::Value v4; + mlir::Value v5; + mlir::Value v6; + mlir::Value v7; + mlir::Value v8; + mlir::Value v9; + mlir::Value v10; + mlir::Value i0; + mlir::Value i1; + + std::vector q0; + std::vector q1; + std::vector q2; + std::vector q3; + std::vector q4; + std::vector q5; + std::vector q6; + std::vector q7; + std::vector q8; + std::vector q9; + std::vector q10; + + UnionTablePropertiesTest() : programBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(11); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), + {q[0].getType()}, {q[0], q[1], q[2], q[3]}); + + v0 = q[0]; + v1 = q[1]; + v2 = q[2]; + v3 = q[3]; + v4 = q[4]; + v5 = q[5]; + v6 = q[6]; + v7 = q[7]; + v8 = q[8]; + v9 = q[9]; + v10 = q[10]; + + q0 = {v0}; + q1 = {v1}; + q2 = {v2}; + q3 = {v3}; + q4 = {v4}; + q5 = {v5}; + q6 = {v6}; + q7 = {v7}; + q8 = {v8}; + q9 = {v9}; + q10 = {v10}; + + const auto iAttr = programBuilder.getI64IntegerAttr(0); + + i0 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i1 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateIntAlloc(i0, 0); + } + + void TearDown() override {} +}; + +TEST_F(UnionTablePropertiesTest, alwaysZeroOneAreFalse) { + std::vector ctrl = {i0}; + ut.propagateGate(hOp, q0, q3); + ut.propagateGate(xOp, q1, q4, q3); + ut.propagateGate(xOp, q2, q5, q4); + ut.propagateGate(xOp, q3, q6); + ut.propagateMeasurement(v4, v7, i0); + ut.propagateGate(hOp, q6, q8, {}, ctrl); + + EXPECT_FALSE(ut.isQubitAlwaysZero(v5)); + EXPECT_FALSE(ut.isQubitAlwaysOne(v8)); +} + +TEST_F(UnionTablePropertiesTest, alwaysZeroIsTrue) { + std::vector ctrl = {i0}; + ut.propagateGate(hOp, q0, q3); + ut.propagateGate(xOp, q1, q4, q3); + ut.propagateGate(xOp, q2, q5, q4); + ut.propagateGate(xOp, q3, q6); + ut.propagateMeasurement(v4, v7, i0); + ut.propagateGate(xOp, q5, q8, {}, {}, ctrl); + ut.propagateGate(hOp, q6, q9, {}, {}, ctrl); + + EXPECT_TRUE(ut.isQubitAlwaysZero(v8)); +} + +TEST_F(UnionTablePropertiesTest, alwaysOneIsTrue) { + std::vector ctrl = {i0}; + std::vector qCtrl = {v7, v4}; + ut.propagateGate(hOp, q0, q3); + ut.propagateGate(xOp, q1, q4, q3); + ut.propagateGate(xOp, q2, q5, q4); + ut.propagateGate(xOp, q3, q6); + ut.propagateMeasurement(v5, v7, i0); + ut.propagateGate(hOp, q6, q8, {}, ctrl); + ut.propagateGate(zOp, q8, q9, qCtrl); + ut.propagateGate(hOp, q9, q10, {}, ctrl); + + EXPECT_TRUE(ut.isQubitAlwaysOne(v10)); +} + +TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsTrueOneIsFalse) { + std::vector ctrl = {i0}; + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateMeasurement(v4, v6, i0); + ut.propagateGate(xOp, q5, q7, {}, {}, ctrl); + ut.propagateMeasurement(v7, v8, i1); + + EXPECT_FALSE(ut.isClassicalValueAlwaysTrue(v6)); + EXPECT_TRUE(ut.isClassicalValueAlwaysFalse(v8)); +} + +TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { + std::vector ctrl = {i0}; + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateMeasurement(v4, v6, i0); + ut.propagateGate(xOp, q5, q7, {}, {}, {}, ctrl); + ut.propagateMeasurement(v7, v8, i1); + + EXPECT_TRUE(ut.isClassicalValueAlwaysTrue(v8)); + EXPECT_FALSE(ut.isClassicalValueAlwaysFalse(v6)); +} + +TEST_F(UnionTablePropertiesTest, testAllTop) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q2, q6, q5); + ut.propagateMeasurement(v6, v7, i0); + ut.propagateGate(hOp, q4, q8); + EXPECT_FALSE(ut.areStatesAllTop()); + ut.propagateGate(hOp, q5, q9); + EXPECT_FALSE(ut.areStatesAllTop()); + ut.propagateMeasurement(v8, v10, i0); + EXPECT_TRUE(ut.areStatesAllTop()); +} + +TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { + std::vector qCtrl = {v3, v4}; + ut.propagateGate(xOp, q0, q3); + ut.propagateGate(xOp, q1, q4); + ut.propagateGate(xOp, q2, q5); + auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q5, qCtrl); + EXPECT_TRUE(globalPhase.has_value()); + EXPECT_EQ(-1, globalPhase.value()); +} + +TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { + std::vector qCtrl = {v4, v5}; + ut.propagateGate(xOp, q0, q4); + ut.propagateGate(xOp, q1, q5); + auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, q5, q4); + auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q2, qCtrl); + EXPECT_FALSE(emptyGlobalPhase.has_value()); + EXPECT_TRUE(globalPhase.has_value()); + EXPECT_EQ(1, globalPhase.value()); +} + +class SmallUnionTableTest : public ::testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + UnionTable ut = UnionTable(2, 2); + + HOp hOp; + XOp xOp; + + mlir::Value v0; + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + mlir::Value v4; + mlir::Value v5; + mlir::Value v6; + mlir::Value v7; + + std::vector q0; + std::vector q1; + std::vector q2; + std::vector q3; + std::vector q4; + std::vector q5; + std::vector q6; + std::vector q7; + + SmallUnionTableTest() : programBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(8); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + + v0 = q[0]; + v1 = q[1]; + v2 = q[2]; + v3 = q[3]; + v4 = q[4]; + v5 = q[5]; + v6 = q[6]; + v7 = q[7]; + + q0 = {v0}; + q1 = {v1}; + q2 = {v2}; + q3 = {v3}; + q4 = {v4}; + q5 = {v5}; + q6 = {v6}; + q7 = {v7}; + + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateQubitAlloc(v3); + } +}; + +TEST_F(SmallUnionTableTest, handleErrorIfTwoManyAmplitudesAreNonzero) { + ut.propagateGate(hOp, q3, q4); + ut.propagateGate(xOp, q2, q5, q4); + ut.propagateGate(hOp, q5, q6); + ut.propagateGate(hOp, q6, q7); + + EXPECT_THAT( + ut.toString(), + testing::HasSubstr("Qubits: 32, HybridStates: {{TOP}: p = 1.00;}")); +} + +TEST_F(SmallUnionTableTest, applyGatesOnPartiallyTopQState) { + ut.propagateGate(hOp, q2, q4); + ut.propagateGate(hOp, q3, q5); + ut.propagateGate(xOp, q4, q6, q5); // Qubit 2 and 3 enter TOP + ut.propagateGate(xOp, q1, q7); + + EXPECT_THAT( + ut.toString(), + testing::HasSubstr("Qubits: 321, HybridStates: {{TOP}: p = 1.00;}")); +} From e8dabcbccadad9cfdee698f7cfc27fa36de20666 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 11:28:57 +0200 Subject: [PATCH 085/131] :construction: Added UnionTable methods --- .../ConstantPropagation/UnionTable.hpp | 190 +++++++++++++++++- .../ConstantPropagation/UnionTable.cpp | 88 +++++++- .../ConstantPropagation/test_unionTable.cpp | 10 +- 3 files changed, 270 insertions(+), 18 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index ccaa5dace4..2ade05ae78 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -14,6 +14,8 @@ #include +#include +#include #include namespace mlir::qco { @@ -27,6 +29,34 @@ struct SuperfluousResult { std::vector superfluousClassicalValues; }; +/** + * @brief Managing entries of the union table + */ +struct UnionTableEntry { + const unsigned int index; + bool top = false; + std::vector states = {}; + // Values and global indices of the participating qubits + llvm::DenseMap participatingQubits; + llvm::DenseSet participatingClassicalValues = {}; + + bool operator<(const UnionTableEntry& ute) const noexcept { + return index < ute.index; + } + + bool operator==(const UnionTableEntry& ute) const noexcept { + return index == ute.index; + } + + UnionTableEntry() : index(nextId()) {} + +private: + static std::uint64_t nextId() { + static unsigned int counter = 0; + return ++counter; + } +}; + /** * @brief This class represents a union table. * @@ -35,9 +65,163 @@ struct SuperfluousResult { */ class UnionTable { bool allTop = false; + std::size_t maxNonzeroAmplitudes; std::size_t maximumHybridEntries; - llvm::DenseMap globalQubitIndices; - llvm::DenseMap> valuesToEntries; + llvm::DenseMap> valuesToEntries = + llvm::DenseMap>(); + std::set entries; + + /** @brief: Replaces values globally by new values + * + * @param replacedValues Values to be replaced + * @param newValues Values the first values are replaced with. + * @throws runtime_error if the size of the two parameters is not equal. + */ + void replaceValuesGlobally(std::span replacedValues, + std::span newValues) { + if (replacedValues.size() != newValues.size()) { + throw std::domain_error( + "replacedValues and newValues do not have the same size."); + } + + for (unsigned int i = 0; i < replacedValues.size(); ++i) { + const auto rV = replacedValues[i]; + const auto nV = newValues[i]; + const auto ute = valuesToEntries.at(rV); + valuesToEntries.erase(rV); + valuesToEntries[nV] = ute; + if (ute->participatingQubits.contains(rV)) { + ute->participatingQubits[nV] = ute->participatingQubits[rV]; + ute->participatingQubits.erase(rV); + } else { + ute->participatingClassicalValues.insert(nV); + ute->participatingClassicalValues.erase(rV); + } + } + } + + /** @brief: Collects a set of all participating entries. + * + * @param targets An array of the Values of the target qubits. + * @param ctrlsQuantum An array of the values of the ctrl qubits. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @param params The parameter applied to the gate. + */ + std::set collectParticipatingEntries( + const std::span targets, const std::span ctrlsQuantum, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical, const std::span params) { + std::set participatingEntries; + for (auto const q : targets) { + participatingEntries.insert(*valuesToEntries.at(q)); + } + for (auto const q : ctrlsQuantum) { + participatingEntries.insert(*valuesToEntries.at(q)); + } + for (auto const i : posCtrlsClassical) { + participatingEntries.insert(*valuesToEntries.at(i)); + } + for (auto const i : negCtrlsClassical) { + participatingEntries.insert(*valuesToEntries.at(i)); + } + for (auto const i : params) { + participatingEntries.insert(*valuesToEntries.at(i)); + } + return participatingEntries; + } + + /** @brief Puts the given UnionTableEntries to top + * + * @param entriesToTop The UnionTableEntries to become top. + */ + void putEntriesToTop(const std::set& entriesToTop) { + auto topUnionTableEntry = UnionTableEntry(); + topUnionTableEntry.top = true; + for (const auto& e : entriesToTop) { + topUnionTableEntry.participatingQubits.insert( + e.participatingQubits.begin(), e.participatingQubits.end()); + topUnionTableEntry.participatingClassicalValues.insert( + e.participatingClassicalValues.begin(), + e.participatingClassicalValues.end()); + entries.erase(e); + } + entries.insert(topUnionTableEntry); + } + + /** + * @brief This method unifies the given UnionTableEntries. + * + * This method unifies the given UnionTableEntries. If the new states have + * more than maxNonzeroAmplitudes, it throws a domain_error. The same holds + * if the resulting hybridStates are more than maximumHybridEntries. + * + * @param entriesToUnify The UnionTableEntries to be unified. + * @throws domain_error If more than maNonzeroAmplitudes are created in a + * quantumstate or more than maximumHybridEntries are created. + */ + void unifyEntries(const std::set& entriesToUnify) { + if (entriesToUnify.size() == 1) { + return; + } + for (const auto& e : entriesToUnify) { + if (e.top) { + putEntriesToTop(entriesToUnify); + } + } + + // Check if the number of entries would be too large + unsigned int numberOfNewEntries = 1; + for (const auto& e : entriesToUnify) { + numberOfNewEntries *= e.states.size(); + } + if (numberOfNewEntries > maximumHybridEntries) { + throw std::domain_error("Maximum of allowed hybrid entries exceeded."); + } + + // Create new entry + auto newEntry = UnionTableEntry(); + for (auto e : entriesToUnify) { + auto classicalValues = e.participatingClassicalValues; + auto qubits = e.participatingQubits; + newEntry.participatingClassicalValues.insert(classicalValues.begin(), + classicalValues.end()); + newEntry.participatingQubits.insert(qubits.begin(), qubits.end()); + if (newEntry.states.empty()) { + newEntry.states = e.states; + continue; + } + if (e.states.empty()) { + continue; + } + std::vector unifiedHS = {}; + for (auto hs1 : newEntry.states) { + for (auto hs2 : newEntry.states) { + unifiedHS.push_back(hs1.unify(hs2)); + } + } + newEntry.states = unifiedHS; + } + + // Adapt global data structures to new entry + auto valuesToReplace = llvm::DenseSet(); + for (const auto& [v, _] : valuesToEntries) { + if (newEntry.participatingQubits.contains(v) || + newEntry.participatingClassicalValues.contains(v)) { + valuesToReplace.insert(v); + } + } + for (const auto& v : valuesToReplace) { + valuesToEntries[v] = std::make_shared(newEntry); + } + for (const auto& e : entriesToUnify) { + entries.erase(e); + } + entries.insert(newEntry); + } + + void applySwapGate(std::span targets, + std::span newQuantumTargets) {} public: explicit UnionTable(std::size_t maxNonzeroAmplitudes, @@ -51,7 +235,7 @@ class UnionTable { std::string toString() const; [[nodiscard("UnionTable::allTop called but ignored")]] - bool areStatesAllTop(); + bool areStatesAllTop() const; /** * @brief This method applies a gate to the qubits. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 582d6069e5..730e058239 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -12,25 +12,93 @@ #define MQT_CORE_UNIONTABLE #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" + namespace mlir::qco { UnionTable::UnionTable(std::size_t maxNonzeroAmplitudes, - std::size_t maximumHybridEntries) {} - -UnionTable::~UnionTable() {} - -void UnionTable::print(std::ostream& os) const {} - -std::string UnionTable::toString() const {} - -bool UnionTable::areStatesAllTop() {} + std::size_t maximumHybridEntries) + : maxNonzeroAmplitudes(maxNonzeroAmplitudes), + maximumHybridEntries(maximumHybridEntries) {} + +UnionTable::~UnionTable() = default; + +void UnionTable::print(std::ostream& os) const { os << this->toString(); } + +std::string UnionTable::toString() const { + std::string result; + for (const auto& entry : entries) { + std::vector qubitIndices; + for (const auto& [_, q] : entry.participatingQubits) { + qubitIndices.push_back(q); + } + std::ranges::sort(qubitIndices, std::greater()); + result += "Qubits: "; + for (const auto qit : qubitIndices) { + result += std::to_string(qit); + } + result += ", HybridStates: {"; + bool first = true; + for (HybridState const& hs : entry.states) { + if (!first) { + result += " "; + } + first = false; + result += hs.toString(); + } + result += "}\n"; + } + return result; +} + +bool UnionTable::areStatesAllTop() const { return allTop; } void UnionTable::propagateGate(Operation* gate, std::span targets, std::span newQuantumTargets, std::span ctrlsQuantum, std::span posCtrlsClassical, std::span negCtrlsClassical, - std::vector params) {} + std::vector params) { + if (isa(*gate) && ctrlsQuantum.empty() && posCtrlsClassical.empty() && + negCtrlsClassical.empty()) { + applySwapGate(targets, newQuantumTargets); + return; + } + + const std::set participatingEntries = + collectParticipatingEntries(targets, ctrlsQuantum, posCtrlsClassical, + negCtrlsClassical, params); + + try { + unifyEntries(participatingEntries); + } catch (std::domain_error&) { + putEntriesToTop(participatingEntries); + replaceValuesGlobally(targets, newQuantumTargets); + return; + } + + auto ute = valuesToEntries.at(*targets.begin()); + std::vector targetQubitIndices; + std::vector ctrlQubitIndices; + for (auto const q : targets) { + targetQubitIndices.push_back(ute->participatingQubits.at(q)); + } + for (auto const q : ctrlsQuantum) { + ctrlQubitIndices.push_back(ute->participatingQubits.at(q)); + } + + for (auto hs : ute->states) { + try { + hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, + posCtrlsClassical, negCtrlsClassical, params); + } catch (std::domain_error&) { + putEntriesToTop(participatingEntries); + break; + } + } + replaceValuesGlobally(targets, newQuantumTargets); +} void UnionTable::propagateMeasurement(Value quantumTarget, Value newQuantumValue, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 2933649309..a291066e42 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -629,7 +629,7 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsTrueOneIsFalse) { } TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { - std::vector ctrl = {i0}; + const std::vector ctrl = {i0}; ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); @@ -659,7 +659,7 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { ut.propagateGate(xOp, q0, q3); ut.propagateGate(xOp, q1, q4); ut.propagateGate(xOp, q2, q5); - auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q5, qCtrl); + const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q5, qCtrl); EXPECT_TRUE(globalPhase.has_value()); EXPECT_EQ(-1, globalPhase.value()); } @@ -668,14 +668,14 @@ TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { std::vector qCtrl = {v4, v5}; ut.propagateGate(xOp, q0, q4); ut.propagateGate(xOp, q1, q5); - auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, q5, q4); - auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q2, qCtrl); + const auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, q5, q4); + const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q2, qCtrl); EXPECT_FALSE(emptyGlobalPhase.has_value()); EXPECT_TRUE(globalPhase.has_value()); EXPECT_EQ(1, globalPhase.value()); } -class SmallUnionTableTest : public ::testing::Test { +class SmallUnionTableTest : public testing::Test { protected: mlir::MLIRContext context; QCOProgramBuilder programBuilder; From 1607ddb44e8f240f38b24ffa6abb125e00fb8f3f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 12:52:41 +0200 Subject: [PATCH 086/131] :construction: Added UnionTable methods --- .../ConstantPropagation/UnionTable.hpp | 29 +-- .../ConstantPropagation/UnionTable.cpp | 195 +++++++++++++++--- 2 files changed, 182 insertions(+), 42 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 2ade05ae78..0fad133030 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace mlir::qco { @@ -37,7 +38,7 @@ struct UnionTableEntry { bool top = false; std::vector states = {}; // Values and global indices of the participating qubits - llvm::DenseMap participatingQubits; + llvm::DenseSet participatingQubits = {}; llvm::DenseSet participatingClassicalValues = {}; bool operator<(const UnionTableEntry& ute) const noexcept { @@ -70,6 +71,7 @@ class UnionTable { llvm::DenseMap> valuesToEntries = llvm::DenseMap>(); std::set entries; + llvm::DenseMap qubitsToGlobalIndices; /** @brief: Replaces values globally by new values * @@ -77,8 +79,8 @@ class UnionTable { * @param newValues Values the first values are replaced with. * @throws runtime_error if the size of the two parameters is not equal. */ - void replaceValuesGlobally(std::span replacedValues, - std::span newValues) { + void replaceValuesGlobally(const std::span replacedValues, + const std::span newValues) { if (replacedValues.size() != newValues.size()) { throw std::domain_error( "replacedValues and newValues do not have the same size."); @@ -87,11 +89,13 @@ class UnionTable { for (unsigned int i = 0; i < replacedValues.size(); ++i) { const auto rV = replacedValues[i]; const auto nV = newValues[i]; + qubitsToGlobalIndices[nV] = qubitsToGlobalIndices[rV]; + qubitsToGlobalIndices.erase(rV); const auto ute = valuesToEntries.at(rV); valuesToEntries.erase(rV); valuesToEntries[nV] = ute; if (ute->participatingQubits.contains(rV)) { - ute->participatingQubits[nV] = ute->participatingQubits[rV]; + ute->participatingQubits.insert(nV); ute->participatingQubits.erase(rV); } else { ute->participatingClassicalValues.insert(nV); @@ -108,10 +112,12 @@ class UnionTable { * @param negCtrlsClassical An array of the values of the negative ctrl bits. * @param params The parameter applied to the gate. */ - std::set collectParticipatingEntries( - const std::span targets, const std::span ctrlsQuantum, - const std::span posCtrlsClassical, - const std::span negCtrlsClassical, const std::span params) { + std::set + collectParticipatingEntries(const std::span targets, + const std::span ctrlsQuantum, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical, + const std::span params = {}) { std::set participatingEntries; for (auto const q : targets) { participatingEntries.insert(*valuesToEntries.at(q)); @@ -220,9 +226,6 @@ class UnionTable { entries.insert(newEntry); } - void applySwapGate(std::span targets, - std::span newQuantumTargets) {} - public: explicit UnionTable(std::size_t maxNonzeroAmplitudes, std::size_t maximumHybridEntries); @@ -235,7 +238,7 @@ class UnionTable { std::string toString() const; [[nodiscard("UnionTable::allTop called but ignored")]] - bool areStatesAllTop() const; + bool areStatesAllTop(); /** * @brief This method applies a gate to the qubits. @@ -258,7 +261,7 @@ class UnionTable { std::span ctrlsQuantum = {}, std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}, - std::vector params = {}); + std::span params = {}); /** * @brief This method applies a measurement. diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 730e058239..64b22e3522 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -30,8 +30,8 @@ std::string UnionTable::toString() const { std::string result; for (const auto& entry : entries) { std::vector qubitIndices; - for (const auto& [_, q] : entry.participatingQubits) { - qubitIndices.push_back(q); + for (const auto& q : entry.participatingQubits) { + qubitIndices.push_back(qubitsToGlobalIndices.at(q)); } std::ranges::sort(qubitIndices, std::greater()); result += "Qubits: "; @@ -52,17 +52,29 @@ std::string UnionTable::toString() const { return result; } -bool UnionTable::areStatesAllTop() const { return allTop; } +bool UnionTable::areStatesAllTop() { + if (allTop) { + return true; + } + for (const auto& ute : entries) { + if (!ute.top) { + return false; + } + } + allTop = true; + return true; +} -void UnionTable::propagateGate(Operation* gate, std::span targets, - std::span newQuantumTargets, - std::span ctrlsQuantum, - std::span posCtrlsClassical, - std::span negCtrlsClassical, - std::vector params) { +void UnionTable::propagateGate(Operation* gate, const std::span targets, + const std::span newQuantumTargets, + const std::span ctrlsQuantum, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical, + const std::span params) { if (isa(*gate) && ctrlsQuantum.empty() && posCtrlsClassical.empty() && negCtrlsClassical.empty()) { - applySwapGate(targets, newQuantumTargets); + std::ranges::reverse(newQuantumTargets); + replaceValuesGlobally(targets, newQuantumTargets); return; } @@ -78,51 +90,176 @@ void UnionTable::propagateGate(Operation* gate, std::span targets, return; } - auto ute = valuesToEntries.at(*targets.begin()); std::vector targetQubitIndices; std::vector ctrlQubitIndices; for (auto const q : targets) { - targetQubitIndices.push_back(ute->participatingQubits.at(q)); + targetQubitIndices.push_back(qubitsToGlobalIndices.at(q)); } for (auto const q : ctrlsQuantum) { - ctrlQubitIndices.push_back(ute->participatingQubits.at(q)); + ctrlQubitIndices.push_back(qubitsToGlobalIndices.at(q)); } + const auto ute = valuesToEntries.at(*targets.begin()); for (auto hs : ute->states) { try { hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, posCtrlsClassical, negCtrlsClassical, params); } catch (std::domain_error&) { - putEntriesToTop(participatingEntries); + putEntriesToTop({*ute}); break; } } replaceValuesGlobally(targets, newQuantumTargets); } -void UnionTable::propagateMeasurement(Value quantumTarget, - Value newQuantumValue, - Value classicalTarget, - std::span posCtrlsClassical, - std::span negCtrlsClassical) {} +void UnionTable::propagateMeasurement( + const Value quantumTarget, const Value newQuantumValue, + const Value classicalTarget, const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + + std::vector targetVec = {quantumTarget, classicalTarget}; + const std::set participatingEntries = + collectParticipatingEntries(targetVec, {}, posCtrlsClassical, + negCtrlsClassical); + + std::vector quantumTargetVec = {quantumTarget}; + std::vector newQuantumValueVec = {newQuantumValue}; + try { + unifyEntries(participatingEntries); + } catch (std::domain_error&) { + putEntriesToTop(participatingEntries); + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); + return; + } + + const auto ute = valuesToEntries.at(quantumTarget); + + for (auto hs : ute->states) { + try { + hs.propagateMeasurement(qubitsToGlobalIndices.at(quantumTarget), + classicalTarget, posCtrlsClassical, + negCtrlsClassical); + } catch (std::domain_error&) { + putEntriesToTop({*ute}); + break; + } + } + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); +} + +void UnionTable::propagateReset(const Value quantumTarget, + const Value newQuantumValue, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + + std::vector quantumTargetVec = {quantumTarget}; + const std::set participatingEntries = + collectParticipatingEntries(quantumTargetVec, {}, posCtrlsClassical, + negCtrlsClassical); + + std::vector newQuantumValueVec = {newQuantumValue}; + try { + unifyEntries(participatingEntries); + } catch (std::domain_error&) { + putEntriesToTop(participatingEntries); + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); + return; + } + + const auto ute = valuesToEntries.at(quantumTarget); + + for (auto hs : ute->states) { + try { + hs.propagateReset(qubitsToGlobalIndices.at(quantumTarget), + posCtrlsClassical, negCtrlsClassical); + } catch (std::domain_error&) { + putEntriesToTop({*ute}); + break; + } + } + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); +} + +void UnionTable::propagateQubitAlloc(const Value qubit) { + unsigned int maxIndex = 0; + if (!qubitsToGlobalIndices.empty()) { + maxIndex = std::ranges::max(qubitsToGlobalIndices.values()) + 1; + } + std::vector globalQubitIndex = {maxIndex}; + auto hs = HybridState(globalQubitIndex, maxNonzeroAmplitudes); + + qubitsToGlobalIndices[qubit] = maxIndex; + auto ute = UnionTableEntry(); + ute.states.push_back(hs); + ute.participatingQubits.insert(qubit); + entries.insert(ute); + valuesToEntries[qubit] = std::make_shared(ute); +} -void UnionTable::propagateReset(Value quantumTarget, Value newQuantumValue, - std::span posCtrlsClassical, - std::span negCtrlsClassical) {} +void UnionTable::propagateIntAlloc(const Value intValue, const int64_t number) { + auto hs = HybridState({}, maxNonzeroAmplitudes); + hs.addIntegerValue(intValue, number); -void UnionTable::propagateQubitAlloc(Value qubit) {} + auto ute = UnionTableEntry(); + ute.states.push_back(hs); + ute.participatingClassicalValues.insert(intValue); + entries.insert(ute); + valuesToEntries[intValue] = std::make_shared(ute); +} -void UnionTable::propagateIntAlloc(Value intValue, int64_t number) {} +void UnionTable::propagateDoubleAlloc(const Value doubleValue, + const double number) { + auto hs = HybridState({}, maxNonzeroAmplitudes); + hs.addDoubleValue(doubleValue, number); -void UnionTable::propagateDoubleAlloc(Value doubleValue, double number) {} + auto ute = UnionTableEntry(); + ute.states.push_back(hs); + ute.participatingClassicalValues.insert(doubleValue); + entries.insert(ute); + valuesToEntries[doubleValue] = std::make_shared(ute); +} -bool UnionTable::isQubitAlwaysOne(Value q) const {} +bool UnionTable::isQubitAlwaysOne(const Value q) const { + const unsigned int qubitIndex = qubitsToGlobalIndices.at(q); + const auto ute = valuesToEntries.at(q); + for (auto const& hs : ute->states) { + if (!hs.isQubitAlwaysOne(qubitIndex)) { + return false; + } + } + return true; +} -bool UnionTable::isQubitAlwaysZero(Value q) const {} +bool UnionTable::isQubitAlwaysZero(const Value q) const { + const unsigned int qubitIndex = qubitsToGlobalIndices.at(q); + const auto ute = valuesToEntries.at(q); + for (auto const& hs : ute->states) { + if (!hs.isQubitAlwaysZero(qubitIndex)) { + return false; + } + } + return true; +} -bool UnionTable::isClassicalValueAlwaysTrue(Value c) const {} +bool UnionTable::isClassicalValueAlwaysTrue(const Value c) const { + const auto ute = valuesToEntries.at(c); + for (auto const& hs : ute->states) { + if (!hs.isValueTrue(c)) { + return false; + } + } + return true; +} -bool UnionTable::isClassicalValueAlwaysFalse(Value c) const {} +bool UnionTable::isClassicalValueAlwaysFalse(const Value c) const { + const auto ute = valuesToEntries.at(c); + for (auto const& hs : ute->states) { + if (hs.isValueTrue(c)) { + return false; + } + } + return true; +} bool UnionTable::hasAlwaysZeroProbability( std::span qubits, unsigned int qubitValue, From c3fd47c48f7affd748998af5e6f26e6580d99c04 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 14:44:23 +0200 Subject: [PATCH 087/131] :construction: Corrected handling of maps --- .../ConstantPropagation/UnionTable.hpp | 20 +++++++++++++------ .../ConstantPropagation/UnionTable.cpp | 17 ++++++++-------- .../ConstantPropagation/test_unionTable.cpp | 4 ++-- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 0fad133030..7489f8cbcf 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -70,7 +70,7 @@ class UnionTable { std::size_t maximumHybridEntries; llvm::DenseMap> valuesToEntries = llvm::DenseMap>(); - std::set entries; + std::set> entries; llvm::DenseMap qubitsToGlobalIndices; /** @brief: Replaces values globally by new values @@ -89,7 +89,7 @@ class UnionTable { for (unsigned int i = 0; i < replacedValues.size(); ++i) { const auto rV = replacedValues[i]; const auto nV = newValues[i]; - qubitsToGlobalIndices[nV] = qubitsToGlobalIndices[rV]; + qubitsToGlobalIndices[nV] = qubitsToGlobalIndices.at(rV); qubitsToGlobalIndices.erase(rV); const auto ute = valuesToEntries.at(rV); valuesToEntries.erase(rV); @@ -150,9 +150,13 @@ class UnionTable { topUnionTableEntry.participatingClassicalValues.insert( e.participatingClassicalValues.begin(), e.participatingClassicalValues.end()); - entries.erase(e); + auto it = std::ranges::find_if( + entries, [&](auto const& entry) { return *entry == e; }); + if (it != entries.end()) { + entries.erase(it); + } } - entries.insert(topUnionTableEntry); + entries.insert(std::make_shared(topUnionTableEntry)); } /** @@ -221,9 +225,13 @@ class UnionTable { valuesToEntries[v] = std::make_shared(newEntry); } for (const auto& e : entriesToUnify) { - entries.erase(e); + auto it = std::ranges::find_if( + entries, [&](auto const& entry) { return *entry == e; }); + if (it != entries.end()) { + entries.erase(it); + } } - entries.insert(newEntry); + entries.insert(std::make_shared(newEntry)); } public: diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 64b22e3522..ca4d3cb86f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -30,7 +30,7 @@ std::string UnionTable::toString() const { std::string result; for (const auto& entry : entries) { std::vector qubitIndices; - for (const auto& q : entry.participatingQubits) { + for (const auto& q : entry->participatingQubits) { qubitIndices.push_back(qubitsToGlobalIndices.at(q)); } std::ranges::sort(qubitIndices, std::greater()); @@ -40,7 +40,7 @@ std::string UnionTable::toString() const { } result += ", HybridStates: {"; bool first = true; - for (HybridState const& hs : entry.states) { + for (HybridState const& hs : entry->states) { if (!first) { result += " "; } @@ -57,7 +57,7 @@ bool UnionTable::areStatesAllTop() { return true; } for (const auto& ute : entries) { - if (!ute.top) { + if (!ute->top) { return false; } } @@ -186,14 +186,15 @@ void UnionTable::propagateQubitAlloc(const Value qubit) { maxIndex = std::ranges::max(qubitsToGlobalIndices.values()) + 1; } std::vector globalQubitIndex = {maxIndex}; - auto hs = HybridState(globalQubitIndex, maxNonzeroAmplitudes); + const auto hs = HybridState(globalQubitIndex, maxNonzeroAmplitudes); qubitsToGlobalIndices[qubit] = maxIndex; auto ute = UnionTableEntry(); ute.states.push_back(hs); ute.participatingQubits.insert(qubit); - entries.insert(ute); - valuesToEntries[qubit] = std::make_shared(ute); + const auto ptrToUTE = std::make_shared(ute); + entries.insert(ptrToUTE); + valuesToEntries[qubit] = ptrToUTE; } void UnionTable::propagateIntAlloc(const Value intValue, const int64_t number) { @@ -203,7 +204,7 @@ void UnionTable::propagateIntAlloc(const Value intValue, const int64_t number) { auto ute = UnionTableEntry(); ute.states.push_back(hs); ute.participatingClassicalValues.insert(intValue); - entries.insert(ute); + entries.insert(std::make_shared(ute)); valuesToEntries[intValue] = std::make_shared(ute); } @@ -215,7 +216,7 @@ void UnionTable::propagateDoubleAlloc(const Value doubleValue, auto ute = UnionTableEntry(); ute.states.push_back(hs); ute.participatingClassicalValues.insert(doubleValue); - entries.insert(ute); + entries.insert(std::make_shared(ute)); valuesToEntries[doubleValue] = std::make_shared(ute); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index a291066e42..c4630b2de5 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -72,7 +72,7 @@ class UnionTableTest : public testing::Test { xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), - {q[0].getType()}, {q[0], q[1], q[2], q[3]}); + {q[0].getType(), q[1].getType()}, {q[0], q[1]}); v0 = q[0]; v1 = q[1]; @@ -629,7 +629,7 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsTrueOneIsFalse) { } TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { - const std::vector ctrl = {i0}; + std::vector ctrl = {i0}; ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); From 3300fe6baf812e71833cdbcdd5d5ec5317badc5d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 15:12:10 +0200 Subject: [PATCH 088/131] :construction: Corrected handling of maps --- .../Optimizations/ConstantPropagation/UnionTable.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 7489f8cbcf..e0173baeee 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -206,7 +206,7 @@ class UnionTable { } std::vector unifiedHS = {}; for (auto hs1 : newEntry.states) { - for (auto hs2 : newEntry.states) { + for (auto hs2 : e.states) { unifiedHS.push_back(hs1.unify(hs2)); } } @@ -221,8 +221,9 @@ class UnionTable { valuesToReplace.insert(v); } } + const auto ptrUTE = std::make_shared(newEntry); for (const auto& v : valuesToReplace) { - valuesToEntries[v] = std::make_shared(newEntry); + valuesToEntries[v] = ptrUTE; } for (const auto& e : entriesToUnify) { auto it = std::ranges::find_if( @@ -231,7 +232,7 @@ class UnionTable { entries.erase(it); } } - entries.insert(std::make_shared(newEntry)); + entries.insert(ptrUTE); } public: From 056af2e84a6322c8637757d4a11bf9f521b8b34b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 22 Jun 2026 12:49:44 +0200 Subject: [PATCH 089/131] :construction: Corrected several test problems --- .../ConstantPropagation/HybridState.hpp | 15 +++- .../ConstantPropagation/QuantumState.hpp | 8 ++ .../ConstantPropagation/UnionTable.hpp | 52 +++++++++-- .../ConstantPropagation/HybridState.cpp | 6 ++ .../ConstantPropagation/QuantumState.cpp | 10 +++ .../ConstantPropagation/UnionTable.cpp | 87 ++++++++++++++----- .../ConstantPropagation/test_hybridState.cpp | 1 + .../ConstantPropagation/test_unionTable.cpp | 78 +++++++++-------- 8 files changed, 191 insertions(+), 66 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 057bbbe4b4..6054dfb7c1 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -150,7 +151,10 @@ class HybridState { [[nodiscard("HybridState::toString called but ignored")]] std::string toString() const; - bool isHybridStateTop() const { return top; } + [[nodiscard("HybridState::isHybridStateTop called but ignored")]] bool + isHybridStateTop() const { + return top; + } /** * @brief This method adds a classical integer value to the hybrid state. @@ -168,6 +172,15 @@ class HybridState { */ void addDoubleValue(Value value, double number); + /** + * @brief This method changes the global index of a qubit in the quantum + * state. + * + * @param target The old global index of a qubit. + * @param newIndex The new global index for the qubit. + */ + void changeGlobalIndex(unsigned int target, unsigned int newIndex) const; + /** * @brief This method applies a gate to the state. * diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 95cc6329b6..ad2fa768b2 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -248,6 +248,14 @@ class QuantumState { [[nodiscard("QuantumState::unify called but ignored")]] QuantumState unify(const QuantumState& that); + /** + * @brief This method changes the global index of a qubit. + * + * @param target The old global index of a qubit. + * @param newIndex The new global index for the qubit. + */ + void changeGlobalIndex(unsigned int target, unsigned int newIndex); + /** * @brief This method applies a gate to the qubits. * diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index e0173baeee..f463843670 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -13,11 +13,16 @@ #include "HybridState.hpp" #include +#include +#include #include +#include +#include #include #include #include +#include namespace mlir::qco { @@ -156,7 +161,16 @@ class UnionTable { entries.erase(it); } } - entries.insert(std::make_shared(topUnionTableEntry)); + const auto ptrUte = std::make_shared(topUnionTableEntry); + entries.insert(ptrUte); + for (auto q : ptrUte->participatingQubits) { + valuesToEntries.erase(q); + valuesToEntries[q] = ptrUte; + } + for (auto c : ptrUte->participatingClassicalValues) { + valuesToEntries.erase(c); + valuesToEntries[c] = ptrUte; + } } /** @@ -174,10 +188,9 @@ class UnionTable { if (entriesToUnify.size() == 1) { return; } + bool entriesBecomeTop = false; for (const auto& e : entriesToUnify) { - if (e.top) { - putEntriesToTop(entriesToUnify); - } + entriesBecomeTop |= e.top; } // Check if the number of entries would be too large @@ -191,17 +204,18 @@ class UnionTable { // Create new entry auto newEntry = UnionTableEntry(); - for (auto e : entriesToUnify) { + newEntry.top = entriesBecomeTop; + for (const auto& e : entriesToUnify) { auto classicalValues = e.participatingClassicalValues; auto qubits = e.participatingQubits; newEntry.participatingClassicalValues.insert(classicalValues.begin(), classicalValues.end()); newEntry.participatingQubits.insert(qubits.begin(), qubits.end()); - if (newEntry.states.empty()) { + if (!newEntry.top & newEntry.states.empty()) { newEntry.states = e.states; continue; } - if (e.states.empty()) { + if (newEntry.top || e.states.empty()) { continue; } std::vector unifiedHS = {}; @@ -235,6 +249,30 @@ class UnionTable { entries.insert(ptrUTE); } + /** + * @brief This method applies a swap gate by switching two qubits if the + * qubits are in different entries. + * + * @param targets The values that partake in the swap. + * @param newQuantumTargets The values after the swap. + */ + void applySwapGate(const std::span targets, + const std::span newQuantumTargets) { + for (const auto& hs : valuesToEntries.at(targets[0])->states) { + hs.changeGlobalIndex(qubitsToGlobalIndices.at(targets[0]), + qubitsToGlobalIndices.at(targets[1])); + } + for (const auto& hs : valuesToEntries.at(targets[1])->states) { + hs.changeGlobalIndex(qubitsToGlobalIndices.at(targets[1]), + qubitsToGlobalIndices.at(targets[0])); + } + const auto targetOneIndex = qubitsToGlobalIndices.at(targets[0]); + qubitsToGlobalIndices[targets[0]] = qubitsToGlobalIndices.at(targets[1]); + qubitsToGlobalIndices[targets[1]] = targetOneIndex; + std::ranges::reverse(newQuantumTargets); + replaceValuesGlobally(targets, newQuantumTargets); + } + public: explicit UnionTable(std::size_t maxNonzeroAmplitudes, std::size_t maximumHybridEntries); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 74541eb3f0..a31fc88acf 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -116,6 +117,11 @@ void HybridState::addDoubleValue(const Value value, const double number) { doubleValues[value] = number; } +void HybridState::changeGlobalIndex(const unsigned int target, + const unsigned int newIndex) const { + qState->changeGlobalIndex(target, newIndex); +} + void HybridState::propagateGate(Operation* gate, const std::span targets, const std::span ctrlsQuantum, diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index ad9f4f66c8..17fdd45eb7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -14,7 +14,10 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" +#include + #include +#include #include #include #include @@ -168,6 +171,13 @@ QuantumState QuantumState::unify(const QuantumState& that) { return newState; } +void QuantumState::changeGlobalIndex(const unsigned int target, + const unsigned int newIndex) { + const auto localIndex = globalToLocalQubitNumber.at(target); + globalToLocalQubitNumber.erase(target); + globalToLocalQubitNumber[newIndex] = localIndex; +} + void QuantumState::propagateGate(Operation* gate, const std::span targets, const std::span ctrls, diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index ca4d3cb86f..a337245e70 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -15,6 +15,13 @@ #include "mlir/Dialect/QCO/IR/QCOInterfaces.h" #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include + +#include +#include +#include +#include + namespace mlir::qco { UnionTable::UnionTable(std::size_t maxNonzeroAmplitudes, @@ -39,13 +46,17 @@ std::string UnionTable::toString() const { result += std::to_string(qit); } result += ", HybridStates: {"; - bool first = true; - for (HybridState const& hs : entry->states) { - if (!first) { - result += " "; + if (entry->top) { + result += "TOP"; + } else { + bool first = true; + for (HybridState const& hs : entry->states) { + if (!first) { + result += " "; + } + first = false; + result += hs.toString(); } - first = false; - result += hs.toString(); } result += "}\n"; } @@ -71,17 +82,18 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, const std::span posCtrlsClassical, const std::span negCtrlsClassical, const std::span params) { - if (isa(*gate) && ctrlsQuantum.empty() && posCtrlsClassical.empty() && - negCtrlsClassical.empty()) { - std::ranges::reverse(newQuantumTargets); - replaceValuesGlobally(targets, newQuantumTargets); - return; - } - const std::set participatingEntries = collectParticipatingEntries(targets, ctrlsQuantum, posCtrlsClassical, negCtrlsClassical, params); + if (isa(*gate) && ctrlsQuantum.empty() && posCtrlsClassical.empty() && + negCtrlsClassical.empty() && participatingEntries.size() == 2 && + !valuesToEntries.at(targets[0])->top && + !valuesToEntries.at(targets[1])->top) { + applySwapGate(targets, newQuantumTargets); + return; + } + try { unifyEntries(participatingEntries); } catch (std::domain_error&) { @@ -89,6 +101,10 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, replaceValuesGlobally(targets, newQuantumTargets); return; } + if (valuesToEntries.at(targets[0])->top) { + replaceValuesGlobally(targets, newQuantumTargets); + return; + } std::vector targetQubitIndices; std::vector ctrlQubitIndices; @@ -101,10 +117,9 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, const auto ute = valuesToEntries.at(*targets.begin()); for (auto hs : ute->states) { - try { - hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, - posCtrlsClassical, negCtrlsClassical, params); - } catch (std::domain_error&) { + hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, + posCtrlsClassical, negCtrlsClassical, params); + if (hs.isHybridStateTop()) { putEntriesToTop({*ute}); break; } @@ -133,16 +148,29 @@ void UnionTable::propagateMeasurement( } const auto ute = valuesToEntries.at(quantumTarget); + if (ute->top) { + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); + return; + } + + std::vector vecOfNewStates; for (auto hs : ute->states) { - try { - hs.propagateMeasurement(qubitsToGlobalIndices.at(quantumTarget), - classicalTarget, posCtrlsClassical, - negCtrlsClassical); - } catch (std::domain_error&) { + auto newStates = hs.propagateMeasurement( + qubitsToGlobalIndices.at(quantumTarget), classicalTarget, + posCtrlsClassical, negCtrlsClassical); + if (hs.isHybridStateTop()) { putEntriesToTop({*ute}); + vecOfNewStates.clear(); break; } + vecOfNewStates.insert(vecOfNewStates.end(), newStates.begin(), + newStates.end()); + } + if (vecOfNewStates.size() > maximumHybridEntries) { + putEntriesToTop({*ute}); + } else { + ute->states = vecOfNewStates; } replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); } @@ -167,16 +195,27 @@ void UnionTable::propagateReset(const Value quantumTarget, } const auto ute = valuesToEntries.at(quantumTarget); + if (ute->top) { + replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); + return; + } + + std::vector vecOfNewStates; for (auto hs : ute->states) { try { - hs.propagateReset(qubitsToGlobalIndices.at(quantumTarget), - posCtrlsClassical, negCtrlsClassical); + auto newStates = + hs.propagateReset(qubitsToGlobalIndices.at(quantumTarget), + posCtrlsClassical, negCtrlsClassical); + vecOfNewStates.insert(vecOfNewStates.end(), newStates.begin(), + newStates.end()); } catch (std::domain_error&) { putEntriesToTop({*ute}); + vecOfNewStates.clear(); break; } } + ute->states = vecOfNewStates; replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 2699151993..9755844506 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index c4630b2de5..74fe1c057d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -15,6 +15,7 @@ #include #include +#include #include @@ -154,12 +155,12 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( "Qubits: 31, HybridStates: {{|10> " - "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); + "-> 0.71, |11> -> 0.71}: integerValue0 = 0; p = 1.00;}")); } TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsTrue) { @@ -168,12 +169,12 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( - "Qubits: 31, HybridStates: {{|00> " - "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); + "Qubits: 31, HybridStates: {{|01> " + "-> 0.71, |10> -> 0.71}: integerValue0 = 1; p = 1.00;}")); } TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { @@ -182,12 +183,12 @@ TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, {}, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, {}, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( "Qubits: 31, HybridStates: {{|10> " - "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); + "-> 0.71, |11> -> 0.71}: integerValue0 = 1; p = 1.00;}")); } TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsFalse) { @@ -196,12 +197,12 @@ TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, {}, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( - "Qubits: 31, HybridStates: {{|00> " - "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); + "Qubits: 31, HybridStates: {{|01> " + "-> 0.71, |10> -> 0.71}: integerValue0 = 0; p = 1.00;}")); } TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsFalse) { @@ -211,12 +212,12 @@ TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, {}, classicalControlOne, classicalControlZero); + ut.propagateGate(xOp, q6, q7, q5, classicalControlOne, classicalControlZero); EXPECT_THAT(ut.toString(), - testing::HasSubstr( - "Qubits: 31, HybridStates: {{|10> " - "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); + testing::HasSubstr("Qubits: 31, HybridStates: {{|10> " + "-> 0.71, |11> -> 0.71}: integerValue0 = 0, " + "integerValue1 = 0; p = 1.00;}")); } TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsTrue) { @@ -226,12 +227,18 @@ TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, classicalControlTrue, classicalControlFalse); + ut.propagateGate(xOp, q6, q7, q5, classicalControlTrue, + classicalControlFalse); - EXPECT_THAT(ut.toString(), - testing::HasSubstr( - "Qubits: 31, HybridStates: {{|00> " - "-> 0.71, |01> -> 0.71}: integerValue0 = 1, p = 1.00;}")); + EXPECT_THAT( + ut.toString(), + testing::AnyOf( + testing::HasSubstr("Qubits: 31, HybridStates: {{|01> " + "-> 0.71, |10> -> 0.71}: integerValue0 = 1, " + "integerValue1 = 0; p = 1.00;}"), + testing::HasSubstr("Qubits: 31, HybridStates: {{|01> " + "-> 0.71, |10> -> 0.71}: integerValue0 = 0, " + "integerValue1 = 1; p = 1.00;}"))); } TEST_F(UnionTableTest, ApplyControlledTwoBitGate) { @@ -257,7 +264,7 @@ TEST_F(UnionTableTest, doMeasurementWithOneResult) { EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 0, HybridStates: {{|1> " - "-> 1.00}: integerValue0 = 1, p = 1.00;}")); + "-> 1.00}: integerValue0 = 1; p = 1.00;}")); } TEST_F(UnionTableTest, doMeasurementWithTwoResults) { @@ -268,8 +275,8 @@ TEST_F(UnionTableTest, doMeasurementWithTwoResults) { EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {{|00> " - "-> 1.00}: integerValue0 = 0, p = 0.50; {|11> " - "-> 1.00}: integerValue0 = 1, p = 0.50;}")); + "-> 1.00}: integerValue0 = 0; p = 0.50; {|11> " + "-> 1.00}: integerValue0 = 1; p = 0.50;}")); } TEST_F(UnionTableTest, doMeasurementWithNegPosCtrl) { @@ -282,7 +289,7 @@ TEST_F(UnionTableTest, doMeasurementWithNegPosCtrl) { EXPECT_THAT(ut.toString(), testing::HasSubstr( "Qubits: 10, HybridStates: {{|00> " - "-> 0.71, |11> -> 0.71}: integerValue0 = 0, p = 1.00;}")); + "-> 0.71, |11> -> 0.71}: integerValue0 = 0; p = 1.00;}")); } TEST_F(UnionTableTest, doMeasurementWithPosNegCtrl) { @@ -295,7 +302,7 @@ TEST_F(UnionTableTest, doMeasurementWithPosNegCtrl) { EXPECT_THAT(ut.toString(), testing::HasSubstr( "Qubits: 10, HybridStates: {{|00> " - "-> 0.71, |11> -> 0.71}: integerValue0 = 10, p = 1.00;}")); + "-> 0.71, |11> -> 0.71}: integerValue0 = 10; p = 1.00;}")); } TEST_F(UnionTableTest, doResetWithOneResult) { @@ -319,13 +326,13 @@ TEST_F(UnionTableTest, doResetWithTwoResults) { } TEST_F(UnionTableTest, swapGateApplicationDifferentStates) { - std::vector swapTargets = {v6, v7}; - std::vector swapDestinations = {v8, v9}; + std::vector swapTargets = {v6, v2}; + std::vector swapDestinations = {v7, v8}; ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateGate(xOp, q5, q6); - ut.propagateGate(xOp, q2, q7); ut.propagateGate(swapOp, swapTargets, swapDestinations); + ut.propagateGate(xOp, q7, q9); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 20, HybridStates: {{|01> -> 0.71, " @@ -345,7 +352,7 @@ TEST_F(UnionTableTest, swapGateApplicationSameState) { EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {{|01> -> 0.71, " - "|10> -> 0.71}: p = 1.00;}")); + "|10> -> 0.50, |11> -> 0.50}: p = 1.00;}")); } class UnionTableWithoutSetupAllocationsTest : public testing::Test { @@ -430,24 +437,26 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsAndGetToTop) { auto ut = UnionTable(4, 1); ut.propagateQubitAlloc(v1); ut.propagateQubitAlloc(v2); + ut.propagateIntAlloc(i0, 10); ut.propagateGate(hOp, q1, q3); ut.propagateMeasurement(v3, v4, i0); EXPECT_THAT(ut.toString(), - testing::HasSubstr("Qubits: 0, Bits: 0, HybridStates: {TOP}")); + testing::HasSubstr("Qubits: 0, HybridStates: {TOP}")); } TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsOnTop) { auto ut = UnionTable(2, 2); ut.propagateQubitAlloc(v0); ut.propagateQubitAlloc(v1); + ut.propagateIntAlloc(i0, 10); ut.propagateGate(hOp, q0, q2); ut.propagateGate(xOp, q1, q3, q2); ut.propagateGate(hOp, q3, q4); // State enters TOP ut.propagateMeasurement(v4, v5, i0); EXPECT_THAT(ut.toString(), - testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); + testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); } TEST_F(UnionTableWithoutSetupAllocationsTest, doResetOnTop) { @@ -460,20 +469,21 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, doResetOnTop) { ut.propagateReset(v4, v5); EXPECT_THAT(ut.toString(), - testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); + testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); } TEST_F(UnionTableWithoutSetupAllocationsTest, unifyTooLargeHybridStates) { - auto ut = UnionTable(4, 2); + auto ut = UnionTable(4, 1); ut.propagateQubitAlloc(v0); ut.propagateQubitAlloc(v1); ut.propagateQubitAlloc(v2); + ut.propagateIntAlloc(i0, 10); ut.propagateGate(hOp, q0, q3); ut.propagateMeasurement(v3, v4, i0); ut.propagateGate(xOp, q1, q5, q4); EXPECT_THAT(ut.toString(), - testing::HasSubstr("Qubits: 10, Bits: 0, HybridStates: {TOP}")); + testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); } class UnionTablePropertiesTest : public testing::Test { @@ -532,7 +542,7 @@ class UnionTablePropertiesTest : public testing::Test { zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), - {q[0].getType()}, {q[0], q[1], q[2], q[3]}); + {q[0].getType(), q[1].getType()}, {q[0], q[1]}); v0 = q[0]; v1 = q[1]; From b3269ebd62779f70d67e56a29549277469acf561 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 22 Jun 2026 17:57:40 +0200 Subject: [PATCH 090/131] :construction: Added additional tests for UnionTable --- .../ConstantPropagation/HybridState.hpp | 2 +- .../ConstantPropagation/UnionTable.hpp | 23 +- .../ConstantPropagation/QuantumState.cpp | 6 +- .../ConstantPropagation/UnionTable.cpp | 7 +- .../ConstantPropagation/test_unionTable.cpp | 415 +++++++++++++++++- 5 files changed, 421 insertions(+), 32 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 6054dfb7c1..453337e59e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -118,7 +118,7 @@ class HybridState { reset ? qState->resetQubit(quantumTarget) : qState->measureQubit(quantumTarget); - for (const size_t i : {0, 1}) { + for (const long i : {0, 1}) { if (!availableStates.contains(i) || !availableStates.at(i)) { continue; } diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index f463843670..899169243f 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -14,15 +14,19 @@ #include #include +#include #include #include +#include #include +#include #include #include #include #include #include +#include namespace mlir::qco { @@ -31,8 +35,8 @@ namespace mlir::qco { */ struct SuperfluousResult { bool completelySuperfluous = false; - std::vector superfluousQubits; - std::vector superfluousClassicalValues; + llvm::DenseSet superfluousQubits; + llvm::DenseSet superfluousClassicalValues; }; /** @@ -434,8 +438,8 @@ class UnionTable { */ [[nodiscard( "UnionTable::getValueThatIsEquivalentToQubit called but ignored")]] std:: - pair, bool> - getValueThatIsEquivalentToQubit(unsigned int qubit) const; + optional> + getValueThatIsEquivalentToQubit(Value qubit) const; /** * @brief This method checks whether a diagonal gate only adds a global phase @@ -479,7 +483,7 @@ class UnionTable { * @returns The superfluous result, i.e. the qubits and classical values that * are superfluous and whether the whole operation is superfluous. */ - static SuperfluousResult + SuperfluousResult getSuperfluousControls(std::span qubitTargets, std::span qubitCtrls, std::span posCtrlsClassical = {}, @@ -496,10 +500,9 @@ class UnionTable { * values. * @returns Whether there are satisfiable combinations or not. */ - static bool - areThereSatisfiableCombinations(std::span qubitCtrls, - std::span posCtrlsClassical = {}, - std::span negCtrlsClassical = {}); + bool areThereSatisfiableCombinations(std::span qubitCtrls, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); /** * @brief Returns the qubits and classical values that imply the given qubit. @@ -517,7 +520,7 @@ class UnionTable { * of q. */ static std::pair, std::set> - getAntecedentsOfQubit(unsigned int q, std::span qubits, + getAntecedentsOfQubit(Value q, std::span qubits, std::span classicalPositive, std::span classicalNegative); }; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 17fdd45eb7..061a5dfab8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -218,14 +218,16 @@ MeasurementResult QuantumState::resetQubit(const unsigned int target) { } bool QuantumState::isQubitAlwaysOne(const unsigned int q) const { - const auto mask = 1U << q; + const auto localIndex = globalToLocalQubitNumber.at(q); + const auto mask = 1U << localIndex; return std::ranges::all_of( amplitudeMap | std::views::keys, [mask](auto qubits) { return (qubits & mask) == mask; }); } bool QuantumState::isQubitAlwaysZero(const unsigned int q) const { - const auto mask = 1U << q; + const auto localIndex = globalToLocalQubitNumber.at(q); + const auto mask = 1U << localIndex; return std::ranges::all_of( amplitudeMap | std::views::keys, [mask](auto qubits) { return (qubits & mask) == 0; }); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index a337245e70..7a9b2d6c7b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace mlir::qco { @@ -306,8 +307,8 @@ bool UnionTable::hasAlwaysZeroProbability( std::span> classicalIntegerValues, std::span> classicalDoubleValues) const {} -std::pair, bool> -UnionTable::getValueThatIsEquivalentToQubit(unsigned int qubit) const {} +std::optional> +UnionTable::getValueThatIsEquivalentToQubit(Value qubit) const {} std::optional UnionTable::globalPhaseThatIsAdded( Operation* diagonalOp, std::span targets, @@ -323,7 +324,7 @@ bool UnionTable::areThereSatisfiableCombinations( std::span negCtrlsClassical) {} std::pair, std::set> -UnionTable::getAntecedentsOfQubit(unsigned int q, std::span qubits, +UnionTable::getAntecedentsOfQubit(Value q, std::span qubits, std::span classicalPositive, std::span classicalNegative) {} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 74fe1c057d..049f1b8ee1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -604,8 +604,8 @@ TEST_F(UnionTablePropertiesTest, alwaysZeroIsTrue) { ut.propagateGate(xOp, q2, q5, q4); ut.propagateGate(xOp, q3, q6); ut.propagateMeasurement(v4, v7, i0); - ut.propagateGate(xOp, q5, q8, {}, {}, ctrl); - ut.propagateGate(hOp, q6, q9, {}, {}, ctrl); + ut.propagateGate(xOp, q5, q8, {}, ctrl); + ut.propagateGate(hOp, q6, q9, {}, ctrl); EXPECT_TRUE(ut.isQubitAlwaysZero(v8)); } @@ -631,11 +631,11 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsTrueOneIsFalse) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateMeasurement(v4, v6, i0); - ut.propagateGate(xOp, q5, q7, {}, {}, ctrl); + ut.propagateGate(xOp, q5, q7, {}, ctrl); ut.propagateMeasurement(v7, v8, i1); - EXPECT_FALSE(ut.isClassicalValueAlwaysTrue(v6)); - EXPECT_TRUE(ut.isClassicalValueAlwaysFalse(v8)); + EXPECT_FALSE(ut.isClassicalValueAlwaysTrue(i0)); + EXPECT_TRUE(ut.isClassicalValueAlwaysFalse(i1)); } TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { @@ -644,14 +644,14 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateMeasurement(v4, v6, i0); - ut.propagateGate(xOp, q5, q7, {}, {}, {}, ctrl); + ut.propagateGate(xOp, q5, q7, {}, {}, ctrl); ut.propagateMeasurement(v7, v8, i1); - EXPECT_TRUE(ut.isClassicalValueAlwaysTrue(v8)); - EXPECT_FALSE(ut.isClassicalValueAlwaysFalse(v6)); + EXPECT_TRUE(ut.isClassicalValueAlwaysTrue(i1)); + EXPECT_FALSE(ut.isClassicalValueAlwaysFalse(i0)); } -TEST_F(UnionTablePropertiesTest, testAllTop) { +TEST_F(UnionTablePropertiesTest, testAllTopAmplitudes) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateGate(xOp, q2, q6, q5); @@ -659,6 +659,17 @@ TEST_F(UnionTablePropertiesTest, testAllTop) { ut.propagateGate(hOp, q4, q8); EXPECT_FALSE(ut.areStatesAllTop()); ut.propagateGate(hOp, q5, q9); + EXPECT_TRUE(ut.areStatesAllTop()); + ut.propagateMeasurement(v8, v10, i0); + EXPECT_TRUE(ut.areStatesAllTop()); +} + +TEST_F(UnionTablePropertiesTest, testAllTopHybridStates) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q2, q6, q5); + ut.propagateMeasurement(v6, v7, i0); + ut.propagateGate(hOp, q4, q8); EXPECT_FALSE(ut.areStatesAllTop()); ut.propagateMeasurement(v8, v10, i0); EXPECT_TRUE(ut.areStatesAllTop()); @@ -685,6 +696,189 @@ TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { EXPECT_EQ(1, globalPhase.value()); } +TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateMeasurement(v5, v6, i0); + const std::optional> result = + ut.getValueThatIsEquivalentToQubit(v6); + ASSERT_TRUE(result.has_value()); + auto [classicalValue, bitValue] = result.value(); + ASSERT_EQ(classicalValue, i0); + ASSERT_TRUE(bitValue); +} + +TEST_F(UnionTablePropertiesTest, FindEquivalentReversedClassicalValue) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q4, q6); + ut.propagateMeasurement(v5, v7, i0); + const std::optional> result = + ut.getValueThatIsEquivalentToQubit(v6); + ASSERT_TRUE(result.has_value()); + auto [classicalValue, bitValue] = result.value(); + ASSERT_EQ(classicalValue, i0); + ASSERT_FALSE(bitValue); +} + +TEST_F(UnionTablePropertiesTest, FindNoEquivalentClassicalValue) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(hOp, q4, q6); + ut.propagateMeasurement(v5, v7, i0); + const std::optional> result = + ut.getValueThatIsEquivalentToQubit(v6); + ASSERT_FALSE(result.has_value()); +} + +TEST_F(UnionTablePropertiesTest, ZeroIsAlwaysAntecedent) { + std::vector classicalIndexVec = {i0}; + ut.propagateMeasurement(v0, v4, i0); + ut.propagateGate(hOp, q1, q5); + auto [antecedentQubits, antecedentClassical] = + ut.getAntecedentsOfQubit(v5, q4, classicalIndexVec, {}); + ASSERT_EQ(antecedentQubits.size(), 1); + ASSERT_EQ(antecedentClassical.size(), 1); + ASSERT_EQ(*antecedentQubits.begin(), v4); + ASSERT_EQ(*antecedentClassical.begin(), i0); +} + +TEST_F(UnionTablePropertiesTest, ImpliedQubit) { + std::vector classicalIndexVec = {i0}; + ut.propagateGate(hOp, q0, q4); + ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(hOp, q1, q6, q5); + auto [antecedentQubits, antecedentClassical] = + ut.getAntecedentsOfQubit(v6, q5, classicalIndexVec, {}); + auto [antecedentQubitsEmpty, antecedentClassicalEmpty] = + ut.getAntecedentsOfQubit(v5, q6, classicalIndexVec, {}); + ASSERT_EQ(antecedentQubits.size(), 1); + ASSERT_EQ(antecedentClassical.size(), 1); + ASSERT_EQ(*antecedentQubits.begin(), v5); + ASSERT_EQ(*antecedentClassical.begin(), i0); + ASSERT_TRUE(antecedentQubitsEmpty.empty()); + ASSERT_TRUE(antecedentClassicalEmpty.empty()); +} + +TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyQubits) { + ut.propagateGate(hOp, q0, q4); + ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(hOp, q1, q6, q5); + auto [antecedentQubits, antecedentClassical] = + ut.getAntecedentsOfQubit(v6, q5, {}, {}); + ASSERT_EQ(antecedentQubits.size(), 1); + ASSERT_TRUE(antecedentClassical.empty()); + ASSERT_EQ(*antecedentQubits.begin(), v5); +} + +TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { + std::vector classicalIndexVec = {i0}; + ut.propagateGate(hOp, q0, q4); + ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(hOp, q1, q6, q5); + auto [antecedentQubits, antecedentClassical] = + ut.getAntecedentsOfQubit(v6, {}, classicalIndexVec, {}); + ASSERT_TRUE(antecedentQubits.empty()); + ASSERT_EQ(antecedentClassical.size(), 1); + ASSERT_EQ(*antecedentClassical.begin(), i0); +} + +TEST_F(UnionTablePropertiesTest, ImpliedQubitNegClassicalValues) { + std::vector classicalIndexVec = {i0}; + ut.propagateGate(hOp, q0, q4); + ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(xOp, q5, q6); + ut.propagateGate(hOp, q1, q7, q6); + auto [antecedentQubits, antecedentClassical] = + ut.getAntecedentsOfQubit(v7, {}, {}, classicalIndexVec); + ASSERT_TRUE(antecedentQubits.empty()); + ASSERT_EQ(antecedentClassical.size(), 1); + ASSERT_EQ(*antecedentClassical.begin(), i0); +} + +TEST_F(UnionTablePropertiesTest, globalPhaseOneQubit) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5); + + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4); + const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, q5); + + ASSERT_FALSE(globalPhase0.has_value()); + ASSERT_TRUE(globalPhase1.has_value()); + ASSERT_EQ(globalPhase1.value(), -1); +} + +TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsA) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(hOp, q1, q5); + + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4, q5); + + ASSERT_FALSE(globalPhase0.has_value()); +} + +TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsB) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(hOp, q1, q5, q4); + ut.propagateMeasurement(v5, v6, i0); + + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4, q6); + + ASSERT_FALSE(globalPhase0.has_value()); +} + +TEST_F(UnionTablePropertiesTest, globalPhaseTwoQubits) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q2, q5); + ut.propagateGate(xOp, q3, q6); + + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q1, q4); + const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, q5, q6); + + ASSERT_TRUE(globalPhase0.has_value()); + ASSERT_TRUE(globalPhase1.has_value()); + ASSERT_EQ(globalPhase0, 1); + ASSERT_EQ(globalPhase1, -1); +} + +TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsA) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5); + ut.propagateGate(xOp, q5, q6, q4); + std::vector combinations = {v4, v6}; + + ASSERT_FALSE(ut.areThereSatisfiableCombinations(combinations)); +} + +TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsB) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + std::vector combinations = {v4, v5}; + + ASSERT_TRUE(ut.areThereSatisfiableCombinations(combinations)); +} + +TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsC) { + ut.propagateGate(hOp, q0, q4); + ut.propagateGate(xOp, q1, q5, q4); + ut.propagateMeasurement(v4, v6, i0); + ut.propagateMeasurement(v5, v7, i1); + ut.propagateGate(hOp, q6, q8); + ut.propagateGate(hOp, q7, q9, q8); + + std::vector qubitCombinations = {v8, v9}; + std::vector classicalCombinations = {i0, i1}; + std::vector classicalVal0 = {i0}; + std::vector classicalVal1 = {i1}; + + ASSERT_TRUE(ut.areThereSatisfiableCombinations(qubitCombinations, + classicalCombinations)); + ASSERT_FALSE( + ut.areThereSatisfiableCombinations({}, classicalVal0, classicalVal1)); + ASSERT_FALSE(ut.areThereSatisfiableCombinations(qubitCombinations, {}, + classicalCombinations)); +} + class SmallUnionTableTest : public testing::Test { protected: mlir::MLIRContext context; @@ -760,18 +954,207 @@ TEST_F(SmallUnionTableTest, handleErrorIfTwoManyAmplitudesAreNonzero) { ut.propagateGate(hOp, q5, q6); ut.propagateGate(hOp, q6, q7); - EXPECT_THAT( - ut.toString(), - testing::HasSubstr("Qubits: 32, HybridStates: {{TOP}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 32, HybridStates: {TOP}")); } TEST_F(SmallUnionTableTest, applyGatesOnPartiallyTopQState) { ut.propagateGate(hOp, q2, q4); ut.propagateGate(hOp, q3, q5); ut.propagateGate(xOp, q4, q6, q5); // Qubit 2 and 3 enter TOP - ut.propagateGate(xOp, q1, q7); + ut.propagateGate(xOp, q1, q7, q6); - EXPECT_THAT( - ut.toString(), - testing::HasSubstr("Qubits: 321, HybridStates: {{TOP}: p = 1.00;}")); + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 321, HybridStates: {TOP}")); +} + +class UnionTableSuperfluousTest : public testing::Test { +protected: + mlir::MLIRContext context; + QCOProgramBuilder programBuilder; + UnionTable ut = UnionTable(8, 4); + + HOp hOp; + XOp xOp; + ZOp zOp; + + mlir::Value v0; + mlir::Value v1; + mlir::Value v2; + mlir::Value v3; + mlir::Value v4; + mlir::Value v5; + mlir::Value v6; + mlir::Value v7; + mlir::Value v8; + mlir::Value v9; + mlir::Value v10; + mlir::Value v11; + mlir::Value v12; + mlir::Value v13; + mlir::Value v14; + mlir::Value v15; + mlir::Value v16; + mlir::Value v17; + mlir::Value v18; + mlir::Value i0; + mlir::Value i1; + mlir::Value i2; + mlir::Value i3; + + std::vector q0; + std::vector q1; + std::vector q2; + std::vector q3; + std::vector q4; + std::vector q5; + std::vector q6; + std::vector q7; + std::vector q8; + std::vector q9; + std::vector q10; + std::vector q11; + std::vector q12; + std::vector q13; + std::vector q14; + std::vector q15; + std::vector q16; + std::vector q17; + std::vector q18; + + UnionTableSuperfluousTest() : programBuilder(&context) {} + + void SetUp() override { + mlir::DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + + auto q = programBuilder.allocQubitRegister(17); + hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + + v0 = q[0]; + v1 = q[1]; + v2 = q[2]; + v3 = q[3]; + v4 = q[4]; + v5 = q[5]; + v6 = q[6]; + v7 = q[7]; + v8 = q[8]; + v9 = q[9]; + v10 = q[10]; + v11 = q[11]; + v12 = q[12]; + v13 = q[13]; + v14 = q[14]; + v15 = q[15]; + v16 = q[16]; + + q0 = {v0}; + q1 = {v1}; + q2 = {v2}; + q3 = {v3}; + q4 = {v4}; + q5 = {v5}; + q6 = {v6}; + q7 = {v7}; + q8 = {v8}; + q9 = {v9}; + q10 = {v10}; + q11 = {v11}; + q12 = {v12}; + q13 = {v13}; + q14 = {v14}; + q15 = {v15}; + q16 = {v16}; + + const auto iAttr = programBuilder.getI64IntegerAttr(0); + + i0 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i1 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i2 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + i3 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); + + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateQubitAlloc(v2); + ut.propagateQubitAlloc(v3); + ut.propagateIntAlloc(i0, 10); + ut.propagateIntAlloc(i1, 10); + ut.propagateIntAlloc(i2, 10); + ut.propagateIntAlloc(i3, 10); + ut.propagateGate(hOp, q0, q4); + ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(hOp, q5, q6); + ut.propagateMeasurement(v6, v7, i1); + ut.propagateGate(hOp, q7, q8); + + ut.propagateGate(hOp, q1, q9); + ut.propagateGate(hOp, q9, q10); + ut.propagateMeasurement(v10, v11, i2); // classical value 2 = false + + ut.propagateGate(hOp, q2, q12); + + ut.propagateGate(hOp, q3, q13); + ut.propagateGate(zOp, q13, q14); + ut.propagateGate(hOp, q14, q15); + ut.propagateMeasurement(v15, v16, i3); // classical value 3 = true + } + + void TearDown() override {} +}; + +TEST_F(UnionTableSuperfluousTest, oneSuperfluousEach) { + std::vector quantumCtrl = {v12, v16}; + std::vector posClassicalCtrl = {i0, i3}; + std::vector negClassicalCtrl = {i1, i2}; + auto [completelySuperfluous, superfluousQubits, superfluousClassicalValues] = + ut.getSuperfluousControls(q8, quantumCtrl, posClassicalCtrl, + negClassicalCtrl); + ASSERT_EQ(superfluousQubits.size(), 1); + ASSERT_EQ(superfluousClassicalValues.size(), 2); + ASSERT_TRUE(superfluousQubits.contains(v16)); + ASSERT_TRUE(superfluousClassicalValues.contains(i2)); + ASSERT_TRUE(superfluousClassicalValues.contains(i3)); + ASSERT_FALSE(completelySuperfluous); +} + +TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToNegQuantumCtrl) { + std::vector quantumCtrl = {v11, v12, v16}; + std::vector posClassicalCtrl = {i0, i3}; + std::vector negClassicalCtrl = {i1, i2}; + const auto results = ut.getSuperfluousControls( + q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + ASSERT_TRUE(results.completelySuperfluous); +} + +TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToNegClassicalCtrl) { + std::vector quantumCtrl = {v12, v16}; + std::vector posClassicalCtrl = {i0, i2, i3}; + std::vector negClassicalCtrl = {i1}; + const auto results = ut.getSuperfluousControls( + q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + ASSERT_TRUE(results.completelySuperfluous); +} + +TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToPosClassicalCtrl) { + std::vector quantumCtrl = {v12, v16}; + std::vector posClassicalCtrl = {i0}; + std::vector negClassicalCtrl = {i1, i2, i3}; + const auto results = ut.getSuperfluousControls( + q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + ASSERT_TRUE(results.completelySuperfluous); } From aecb3a0994d254b9bbe9ff5d59135b62998be101 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 23 Jun 2026 19:20:07 +0200 Subject: [PATCH 091/131] :construction: Added getValueThatIsEquivalentToQubit --- .../ConstantPropagation/HybridState.hpp | 24 +++-- .../ConstantPropagation/QuantumState.hpp | 9 +- .../ConstantPropagation/UnionTable.hpp | 49 +++++++--- .../ConstantPropagation/HybridState.cpp | 30 +++--- .../ConstantPropagation/QuantumState.cpp | 14 ++- .../ConstantPropagation/UnionTable.cpp | 93 ++++++++++++++++++- .../ConstantPropagation/test_unionTable.cpp | 2 + 7 files changed, 164 insertions(+), 57 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 453337e59e..85df262d45 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -294,9 +294,8 @@ class HybridState { * The values for the classical values are not the numeric ones, but whether * they are zero (false) or non-zero (true). * - * @param qubits The qubits which are being checked. - * @param qubitValue The value for which is tested whether there is a nonzero - * amplitude. + * @param qubitValues Pairs of the qubits that are being checked and the + * values that they are being checked for. * @param classicalIntegerValues The integer values to check. * @param classicalDoubleValues The double values to check. * @throws domain_error If a classical value cannot be found. @@ -304,9 +303,9 @@ class HybridState { */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool hasAlwaysZeroProbability( - std::span qubits, unsigned int qubitValue, - std::span> classicalIntegerValues = {}, - std::span> classicalDoubleValues = {}) const; + const std::unordered_map& qubitValues, + const llvm::DenseMap& classicalIntegerValues, + const llvm::DenseMap& classicalDoubleValues) const; /** * @brief Returns a classical value that is equivalent to qubit. @@ -317,14 +316,13 @@ class HybridState { * is false. * * @param qubit Index of qubit. - * @returns Classical value that is equivalent or inverse to qubit if it - * exists and true, if the qubit is equivalent to the value. False, if the - * qubit is the inverse of the value. + * @returns A map of classical values that are equivalent or inverse to qubit. + * Th emaps value is true, if the qubit is equivalent to the value. False, if + * the qubit is the inverse of the value. */ - [[nodiscard( - "HybridState::getValueThatIsEquivalentToQubit called but ignored")]] std:: - pair, bool> - getValueThatIsEquivalentToQubit(unsigned int qubit) const; + [[nodiscard("HybridState::getValueThatIsEquivalentToQubit called but " + "ignored")]] llvm::DenseMap + getValueThatIsEquivalentToQubit(unsigned int qubit) const; }; } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index ad2fa768b2..07e689db93 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -335,14 +335,13 @@ class QuantumState { * This method receives a number of global qubit indices and checks whether * they have for a given value always a zero amplitude. * - * @param qubits The qubits which are being checked. - * @param value The value for which is tested whether there is a nonzero - * amplitude. + * @param qubitValues Pairs of the qubits that are being checked and the + * values that they are being checked for. * @returns True if the amplitude is always zero, false otherwise. */ [[nodiscard("QuantumState::hasAlwaysZeroAmplitude called but ignored")]] bool - hasAlwaysZeroAmplitude(std::span qubits, - unsigned int value) const; + hasAlwaysZeroAmplitude( + const std::unordered_map& qubitValues) const; }; } // namespace mlir::qco diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 899169243f..6bbde26f9a 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include namespace mlir::qco { @@ -45,7 +46,7 @@ struct SuperfluousResult { struct UnionTableEntry { const unsigned int index; bool top = false; - std::vector states = {}; + std::vector states; // Values and global indices of the participating qubits llvm::DenseSet participatingQubits = {}; llvm::DenseSet participatingClassicalValues = {}; @@ -277,6 +278,31 @@ class UnionTable { replaceValuesGlobally(targets, newQuantumTargets); } + /** + * @brief This method returns classical values which are always either true or + * false. + * + * @return A map of values as keys. The values of th emap say wether the + * classical values are always true or always false. + */ + llvm::DenseMap + getClassicalValuesThatAreAlwaysTrueOrFalse() const { + llvm::DenseMap result; + for (auto const& e : entries) { + for (auto const& v : e->participatingClassicalValues) { + for (auto const& hs : e->states) { + const auto isTrue = hs.isValueTrue(v); + if (result.contains(v) && result.at(v) != isTrue) { + result.erase(v); + break; + } + result[v] = isTrue; + } + } + } + return result; + } + public: explicit UnionTable(std::size_t maxNonzeroAmplitudes, std::size_t maximumHybridEntries); @@ -408,9 +434,8 @@ class UnionTable { * The values for the classical values are not the numeric ones, but whether * they are zero (false) or non-zero (true). * - * @param qubits The qubits which are being checked. - * @param qubitValue The value for which is tested whether there is a nonzero - * amplitude. + * @param qubitValues Pairs of the qubits that are being checked and the + * values that they are being checked for. * @param classicalIntegerValues The integer values to check. * @param classicalDoubleValues The double values to check. * @throws invalid_argument if a value is given, but is not found in the @@ -419,9 +444,9 @@ class UnionTable { */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool hasAlwaysZeroProbability( - std::span qubits, unsigned int qubitValue, - std::span> classicalIntegerValues = {}, - std::span> classicalDoubleValues = {}) const; + const llvm::DenseMap& qubitValues, + const llvm::DenseMap& classicalIntegerValues, + const llvm::DenseMap& classicalDoubleValues) const; /** * @brief Returns a classical value that is equivalent to qubit. @@ -432,13 +457,13 @@ class UnionTable { * is false. * * @param qubit Index of qubit. - * @returns Classical value that is equivalent or inverse to qubit if it - * exists and true, if the qubit is equivalent to the value. False, if the - * qubit is the inverse of the value. + * @returns A map of classical values that are equivalent or inverse to qubit. + * The value of the map is true, if the qubit is equivalent to the value. + * False, if the qubit is the inverse of the value. */ [[nodiscard( - "UnionTable::getValueThatIsEquivalentToQubit called but ignored")]] std:: - optional> + "UnionTable::getValueThatIsEquivalentToQubit called but ignored")]] llvm:: + DenseMap getValueThatIsEquivalentToQubit(Value qubit) const; /** diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index a31fc88acf..ba4208b5e4 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -15,6 +15,7 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" +#include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include #include namespace mlir::qco { @@ -275,9 +277,9 @@ bool HybridState::isValueTrue(const Value v) const { } bool HybridState::hasAlwaysZeroProbability( - const std::span qubits, const unsigned int qubitValue, - std::span> classicalIntegerValues, - std::span> classicalDoubleValues) const { + const std::unordered_map& qubitValues, + const llvm::DenseMap& classicalIntegerValues, + const llvm::DenseMap& classicalDoubleValues) const { for (const auto& [v, i] : classicalIntegerValues) { if (!integerValues.contains(v)) { throw std::domain_error("Value of a classical value is asked which does " @@ -298,33 +300,33 @@ bool HybridState::hasAlwaysZeroProbability( } } - return qState->hasAlwaysZeroAmplitude(qubits, qubitValue); + return qState->hasAlwaysZeroAmplitude(qubitValues); } -std::pair, bool> +llvm::DenseMap HybridState::getValueThatIsEquivalentToQubit(const unsigned int qubit) const { - if (integerValues.empty() && doubleValues.empty()) { - return {std::optional(), false}; - } + llvm::DenseMap result; const bool qubitZero = qState->isQubitAlwaysZero(qubit); const bool qubitOne = qubitZero ? false : qState->isQubitAlwaysOne(qubit); if (!qubitZero && !qubitOne) { - return {std::optional(), false}; + return result; } for (const auto& [v, i] : integerValues) { if ((qubitZero && i == 0) || (qubitOne && i != 0)) { - return {std::optional(v), true}; + result[v] = true; + } else { + result[v] = false; } - return {std::optional(v), false}; } for (const auto& [v, d] : doubleValues) { if ((qubitZero && std::norm(d) < 1e-4) || (qubitOne && std::norm(d) >= 1e-4)) { - return {std::optional(v), true}; + result[v] = true; + } else { + result[v] = false; } - return {std::optional(v), false}; } - return {std::optional(), false}; + return result; } } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 061a5dfab8..b8c0489502 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -232,16 +232,14 @@ bool QuantumState::isQubitAlwaysZero(const unsigned int q) const { amplitudeMap | std::views::keys, [mask](auto qubits) { return (qubits & mask) == 0; }); } -bool QuantumState::hasAlwaysZeroAmplitude(const std::span qubits, - const unsigned int value) const { +bool QuantumState::hasAlwaysZeroAmplitude( + const std::unordered_map& qubitValues) const { unsigned int localValue = 0; unsigned int mask = 0; - for (unsigned int i = 0; i < qubits.size(); ++i) { - const unsigned int bitMask = 1U << i; - const unsigned int qubitMask = 1U << qubits[i]; - mask += qubitMask; - if ((value & bitMask) != 0) { - localValue += qubitMask; + for (const auto& [qubitIndex, qubitOne] : qubitValues) { + mask |= 1U << globalToLocalQubitNumber.at(qubitIndex); + if (qubitOne) { + localValue |= 1U << globalToLocalQubitNumber.at(qubitIndex); } } return std::ranges::all_of( diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 7a9b2d6c7b..8e3537d9da 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -303,12 +303,95 @@ bool UnionTable::isClassicalValueAlwaysFalse(const Value c) const { } bool UnionTable::hasAlwaysZeroProbability( - std::span qubits, unsigned int qubitValue, - std::span> classicalIntegerValues, - std::span> classicalDoubleValues) const {} + const llvm::DenseMap& qubitValues, + const llvm::DenseMap& classicalIntegerValues, + const llvm::DenseMap& classicalDoubleValues) const { + std::set participatingEntries; + for (auto& [qV, _] : qubitValues) { + participatingEntries.insert(*valuesToEntries.at(qV)); + } + for (auto& [iV, _] : classicalIntegerValues) { + participatingEntries.insert(*valuesToEntries.at(iV)); + } + for (auto& [dV, _] : classicalDoubleValues) { + participatingEntries.insert(*valuesToEntries.at(dV)); + } + for (const auto& ute : participatingEntries) { + std::unordered_map qubitValuesThisEntry; + llvm::DenseMap intValuesThisEntry; + llvm::DenseMap doubleValuesThisEntry; + for (const auto& [qV, qBool] : qubitValues) { + if (ute.participatingQubits.contains(qV)) { + qubitValuesThisEntry[qubitsToGlobalIndices.at(qV)] = qBool; + } + } + for (const auto& [iV, number] : classicalIntegerValues) { + if (ute.participatingClassicalValues.contains(iV)) { + intValuesThisEntry[iV] = number; + } + } + for (const auto& [dV, number] : classicalDoubleValues) { + if (ute.participatingClassicalValues.contains(dV)) { + doubleValuesThisEntry[dV] = number; + } + } + bool oneEntryIsNonzero = false; + for (const auto& hs : ute.states) { + if (!hs.hasAlwaysZeroProbability(qubitValuesThisEntry, intValuesThisEntry, + doubleValuesThisEntry)) { + oneEntryIsNonzero = true; + break; + } + } + if (!oneEntryIsNonzero) { + return true; + } + } + return false; +} -std::optional> -UnionTable::getValueThatIsEquivalentToQubit(Value qubit) const {} +llvm::DenseMap +UnionTable::getValueThatIsEquivalentToQubit(Value qubit) const { + const auto uteOfQubit = valuesToEntries.at(qubit); + const auto indexOfQubit = qubitsToGlobalIndices.at(qubit); + llvm::DenseMap result; + bool found = false; + bool alwaysOne = true; + bool alwaysZero = true; + for (const auto& hs : uteOfQubit->states) { + alwaysOne &= hs.isQubitAlwaysOne(indexOfQubit); + alwaysZero &= hs.isQubitAlwaysZero(indexOfQubit); + auto currentResult = hs.getValueThatIsEquivalentToQubit(indexOfQubit); + if (currentResult.empty() || (result.empty() && found)) { + result.clear(); + continue; + } + if (!found) { + result = currentResult; + found = true; + } else { + for (const auto& [value, inverse] : currentResult) { + if (!result.contains(value) || result.at(value) != inverse) { + result.erase(value); + } + } + } + } + if (alwaysOne) { + auto valuesThatAreAlwaysTrue = getClassicalValuesThatAreAlwaysTrueOrFalse(); + result.reserve(valuesThatAreAlwaysTrue.size()); + for (const auto& [k, v] : valuesThatAreAlwaysTrue) { + result[k] = v; + } + } else if (alwaysZero) { + auto valuesThatAreAlwaysTrue = getClassicalValuesThatAreAlwaysTrueOrFalse(); + result.reserve(valuesThatAreAlwaysTrue.size()); + for (const auto& [k, v] : valuesThatAreAlwaysTrue) { + result[k] = !v; + } + } + return result; +} std::optional UnionTable::globalPhaseThatIsAdded( Operation* diagonalOp, std::span targets, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 049f1b8ee1..4b4ea23f62 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -21,6 +21,8 @@ using namespace mlir::qco; +// TODO: Tests for hasAlwaysZeroProbability + class UnionTableTest : public testing::Test { protected: mlir::MLIRContext context; From fefdc7e2032641f27f60f9ce67ac414535727bf2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 24 Jun 2026 17:35:43 +0200 Subject: [PATCH 092/131] :construction: Added UnionTableFunctionality --- .../ConstantPropagation/HybridState.hpp | 8 +- .../ConstantPropagation/UnionTable.hpp | 50 ++-- .../ConstantPropagation/HybridState.cpp | 29 +-- .../ConstantPropagation/UnionTable.cpp | 244 +++++++++++++++--- .../ConstantPropagation/test_unionTable.cpp | 99 +++---- 5 files changed, 283 insertions(+), 147 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 85df262d45..07d5767dce 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -118,7 +118,7 @@ class HybridState { reset ? qState->resetQubit(quantumTarget) : qState->measureQubit(quantumTarget); - for (const long i : {0, 1}) { + for (const int64_t i : {0, 1}) { if (!availableStates.contains(i) || !availableStates.at(i)) { continue; } @@ -296,16 +296,14 @@ class HybridState { * * @param qubitValues Pairs of the qubits that are being checked and the * values that they are being checked for. - * @param classicalIntegerValues The integer values to check. - * @param classicalDoubleValues The double values to check. + * @param classicalValues The classical values to check. * @throws domain_error If a classical value cannot be found. * @returns True if the amplitude is always zero, false otherwise. */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool hasAlwaysZeroProbability( const std::unordered_map& qubitValues, - const llvm::DenseMap& classicalIntegerValues, - const llvm::DenseMap& classicalDoubleValues) const; + const llvm::DenseMap& classicalValues) const; /** * @brief Returns a classical value that is equivalent to qubit. diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 6bbde26f9a..48945ec564 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -17,8 +17,8 @@ #include #include +#include #include -#include #include #include #include @@ -436,8 +436,7 @@ class UnionTable { * * @param qubitValues Pairs of the qubits that are being checked and the * values that they are being checked for. - * @param classicalIntegerValues The integer values to check. - * @param classicalDoubleValues The double values to check. + * @param classicalValues The classical values to check. * @throws invalid_argument if a value is given, but is not found in the * existing ones. * @returns True if the amplitude is always zero, false otherwise. @@ -445,8 +444,7 @@ class UnionTable { [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool hasAlwaysZeroProbability( const llvm::DenseMap& qubitValues, - const llvm::DenseMap& classicalIntegerValues, - const llvm::DenseMap& classicalDoubleValues) const; + const llvm::DenseMap& classicalValues) const; /** * @brief Returns a classical value that is equivalent to qubit. @@ -472,10 +470,11 @@ class UnionTable { * * This method receives a diagonal gate and checks, if only a global phase is * added to the circuit by it under the current configuration. If that is the - * case, the returned optional contains the global phase. + * case, the returned optional contains the global phase. Only works with + * 1-qubit gates without parameters. * - * @param diagonalOp The gate to be checked. - * @param targets An array of the Values of the target qubits. + * @param op The gate to be checked. + * @param target The Values of the target qubits. * @param ctrlsQuantum An array of the values of the ctrl qubits. * @param posCtrlsClassical An array of the values of the ctrl bits. * @param negCtrlsClassical An array of the values of the negative ctrl bits. @@ -484,8 +483,8 @@ class UnionTable { * @returns An optional containing the globally added value, if applicable. */ [[nodiscard("UnionTable::globalPhaseThatIsAdded called but ignored")]] - std::optional - globalPhaseThatIsAdded(Operation* diagonalOp, std::span targets, + std::optional> + globalPhaseThatIsAdded(Operation* op, Value target, std::span ctrlsQuantum = {}, std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}); @@ -495,11 +494,11 @@ class UnionTable { * given a controlled gate. * * This method checks which qubits and classical values are superfluous given - * a controlled gate. If the gate can never be executed, the target qubits are - * superfluous. Apart from that, all posCtrl (negCtrl) qubits/values that are - * always true (false) are superfluous. + * a controlled gate. If the gate can never be executed, the SuperfluousResult + * will indicate that the gate is completely superfluous. Apart from that, all + * posCtrl (negCtrl) qubits/values that are always true (false) are + * superfluous. * - * @param qubitTargets The values of the target qubits. * @param qubitCtrls The valuess of the positively controlling qubits. * @param posCtrlsClassical The values of the positively controlling classical * values. @@ -509,8 +508,7 @@ class UnionTable { * are superfluous and whether the whole operation is superfluous. */ SuperfluousResult - getSuperfluousControls(std::span qubitTargets, - std::span qubitCtrls, + getSuperfluousControls(std::span qubitCtrls, std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}); @@ -525,16 +523,17 @@ class UnionTable { * values. * @returns Whether there are satisfiable combinations or not. */ - bool areThereSatisfiableCombinations(std::span qubitCtrls, - std::span posCtrlsClassical = {}, - std::span negCtrlsClassical = {}); + bool areThereSatisfiableCombinations( + std::span qubitCtrls, std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}) const; /** - * @brief Returns the qubits and classical values that imply the given qubit. + * @brief Returns whether the given qubits and classical values that imply the + * given qubit. * * This method checks whether in the given list are qubits or classical values - * that imply (are antecedents of) the given qubit. I.e. all qubits and - * classical values are returned for which holds: a -> q. + * that imply (are antecedents of) the given qubit. I.e. if there are + * qubits/values a for which holds: a -> q. * * @param q The qubit for which is checked whether it is implied. * @param qubits The qubits for which are checked if they imply q. @@ -544,10 +543,9 @@ class UnionTable { * @returns A pair of 1. qubits and 2. classical values that are antecedents * of q. */ - static std::pair, std::set> - getAntecedentsOfQubit(Value q, std::span qubits, - std::span classicalPositive, - std::span classicalNegative); + bool isQubitImplied(Value q, std::span qubits, + std::span classicalPositive, + std::span classicalNegative) const; }; } // namespace mlir::qco diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index ba4208b5e4..5bb7bde127 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -278,26 +278,21 @@ bool HybridState::isValueTrue(const Value v) const { bool HybridState::hasAlwaysZeroProbability( const std::unordered_map& qubitValues, - const llvm::DenseMap& classicalIntegerValues, - const llvm::DenseMap& classicalDoubleValues) const { - for (const auto& [v, i] : classicalIntegerValues) { - if (!integerValues.contains(v)) { - throw std::domain_error("Value of a classical value is asked which does " - "not exist in the HybridState."); - } - if (integerValues.at(v) != i) { - return true; - } - } - - for (const auto& [v, d] : classicalDoubleValues) { - if (!doubleValues.contains(v)) { + const llvm::DenseMap& classicalValues) const { + for (const auto& [v, i] : classicalValues) { + if (integerValues.contains(v)) { + if ((integerValues.at(v) == 0 && i) || (integerValues.at(v) != 0 && !i)) { + return true; + } + } else if (doubleValues.contains(v)) { + const bool zeroDouble = std::norm(doubleValues.at(v)) < 1e-4; + if ((zeroDouble && i) || (!zeroDouble && !i)) { + return true; + } + } else { throw std::domain_error("Value of a classical value is asked which does " "not exist in the HybridState."); } - if (std::norm(doubleValues.at(v) - d) > 1e-4) { - return true; - } } return qState->hasAlwaysZeroAmplitude(qubitValues); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 8e3537d9da..4ad2c54332 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -17,6 +17,8 @@ #include +#include +#include #include #include #include @@ -25,8 +27,8 @@ namespace mlir::qco { -UnionTable::UnionTable(std::size_t maxNonzeroAmplitudes, - std::size_t maximumHybridEntries) +UnionTable::UnionTable(const std::size_t maxNonzeroAmplitudes, + const std::size_t maximumHybridEntries) : maxNonzeroAmplitudes(maxNonzeroAmplitudes), maximumHybridEntries(maximumHybridEntries) {} @@ -304,41 +306,31 @@ bool UnionTable::isClassicalValueAlwaysFalse(const Value c) const { bool UnionTable::hasAlwaysZeroProbability( const llvm::DenseMap& qubitValues, - const llvm::DenseMap& classicalIntegerValues, - const llvm::DenseMap& classicalDoubleValues) const { + const llvm::DenseMap& classicalValues) const { std::set participatingEntries; for (auto& [qV, _] : qubitValues) { participatingEntries.insert(*valuesToEntries.at(qV)); } - for (auto& [iV, _] : classicalIntegerValues) { - participatingEntries.insert(*valuesToEntries.at(iV)); - } - for (auto& [dV, _] : classicalDoubleValues) { - participatingEntries.insert(*valuesToEntries.at(dV)); + for (auto& [cV, _] : classicalValues) { + participatingEntries.insert(*valuesToEntries.at(cV)); } for (const auto& ute : participatingEntries) { std::unordered_map qubitValuesThisEntry; - llvm::DenseMap intValuesThisEntry; - llvm::DenseMap doubleValuesThisEntry; + llvm::DenseMap classicalValuesThisEntry; for (const auto& [qV, qBool] : qubitValues) { if (ute.participatingQubits.contains(qV)) { qubitValuesThisEntry[qubitsToGlobalIndices.at(qV)] = qBool; } } - for (const auto& [iV, number] : classicalIntegerValues) { - if (ute.participatingClassicalValues.contains(iV)) { - intValuesThisEntry[iV] = number; - } - } - for (const auto& [dV, number] : classicalDoubleValues) { - if (ute.participatingClassicalValues.contains(dV)) { - doubleValuesThisEntry[dV] = number; + for (const auto& [v, vBool] : classicalValues) { + if (ute.participatingClassicalValues.contains(v)) { + classicalValuesThisEntry[v] = vBool; } } bool oneEntryIsNonzero = false; for (const auto& hs : ute.states) { - if (!hs.hasAlwaysZeroProbability(qubitValuesThisEntry, intValuesThisEntry, - doubleValuesThisEntry)) { + if (!hs.hasAlwaysZeroProbability(qubitValuesThisEntry, + classicalValuesThisEntry)) { oneEntryIsNonzero = true; break; } @@ -351,7 +343,7 @@ bool UnionTable::hasAlwaysZeroProbability( } llvm::DenseMap -UnionTable::getValueThatIsEquivalentToQubit(Value qubit) const { +UnionTable::getValueThatIsEquivalentToQubit(const Value qubit) const { const auto uteOfQubit = valuesToEntries.at(qubit); const auto indexOfQubit = qubitsToGlobalIndices.at(qubit); llvm::DenseMap result; @@ -393,23 +385,205 @@ UnionTable::getValueThatIsEquivalentToQubit(Value qubit) const { return result; } -std::optional UnionTable::globalPhaseThatIsAdded( - Operation* diagonalOp, std::span targets, - std::span ctrlsQuantum, std::span posCtrlsClassical, - std::span negCtrlsClassical) {} +std::optional> +UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, + const std::span ctrlsQuantum, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + // Diagonal gates w/o parameters: IdOp, ZOp, SOp, SdgOp, TOp, TdgOp + if (isa(op)) { + return std::optional(std::complex(1.0, 0.0)); + } + if (!(isa(op) || isa(op) || isa(op) || isa(op) || + isa(op))) { + return std::optional>(); + } + const auto targetIndex = qubitsToGlobalIndices.at(target); + const auto targetUte = valuesToEntries.at(target); + bool alwaysOne = true; + bool alwaysZero = true; + + for (const auto& hs : targetUte->states) { + alwaysOne &= hs.isQubitAlwaysOne(targetIndex); + alwaysZero &= hs.isQubitAlwaysZero(targetIndex); + if (!alwaysOne && !alwaysZero) { + break; + } + } + + if (alwaysZero) { + return std::optional(std::complex(1.0, 0.0)); + } + + const auto participatingEntries = collectParticipatingEntries( + {}, ctrlsQuantum, posCtrlsClassical, negCtrlsClassical, {}); + + // Check if state |11...11> can be reached + bool highestStateReachable = false; + bool highestStateAlwaysReached = alwaysOne; + for (const auto& ute : participatingEntries) { + std::unordered_map qubitCtrlThisEntry; + llvm::DenseMap classicalCtrlThisEntry; + for (const auto q : ctrlsQuantum) { + if (ute.participatingQubits.contains((q))) { + qubitCtrlThisEntry[qubitsToGlobalIndices.at(q)] = true; + } + } + for (const auto c : posCtrlsClassical) { + if (ute.participatingClassicalValues.contains((c))) { + classicalCtrlThisEntry[c] = true; + } + } + for (const auto c : negCtrlsClassical) { + if (ute.participatingClassicalValues.contains((c))) { + classicalCtrlThisEntry[c] = false; + } + } + for (const auto& hs : ute.states) { + const auto ctrlsZeroProbability = hs.hasAlwaysZeroProbability( + qubitCtrlThisEntry, classicalCtrlThisEntry); + highestStateReachable |= ctrlsZeroProbability; + highestStateAlwaysReached &= ctrlsZeroProbability; + } + if (highestStateReachable && !highestStateAlwaysReached) { + return std::optional>(); + } + } + if (!highestStateReachable) { + return std::optional(std::complex(1.0, 0.0)); + } + if (!highestStateAlwaysReached) { + return std::optional>(); + } + + // Only highest state reachable, return respective phase + if (isa(op)) { + return std::optional(std::complex(-1.0, 0.0)); + } + if (isa(op)) { + return std::optional(std::complex(0.0, 1.0)); + } + if (isa(op)) { + return std::optional(std::complex(0.0, -1.0)); + } + constexpr auto inv_sqrt2 = 1.0 / std::numbers::sqrt2; + if (isa(op)) { + return std::optional(std::complex(inv_sqrt2, inv_sqrt2)); + } + // Tdg Op + return std::optional(std::complex(inv_sqrt2, -inv_sqrt2)); +} -SuperfluousResult UnionTable::getSuperfluousControls( - std::span qubitTargets, std::span qubitCtrls, - std::span posCtrlsClassical, std::span negCtrlsClassical) {} +SuperfluousResult +UnionTable::getSuperfluousControls(const std::span qubitCtrls, + const std::span posCtrlsClassical, + const std::span negCtrlsClassical) { + SuperfluousResult res; + for (const auto& qCtrl : qubitCtrls) { + const auto qIndex = qubitsToGlobalIndices.at(qCtrl); + bool alwaysOne = true; + for (const auto& hs : valuesToEntries.at(qCtrl)->states) { + if (!alwaysOne) { + break; + } + if (hs.isQubitAlwaysZero(qIndex)) { + res.completelySuperfluous = true; + return res; + } + alwaysOne &= hs.isQubitAlwaysOne(qIndex); + } + if (alwaysOne) { + res.superfluousQubits.insert(qCtrl); + } + } + for (const auto& posCtrl : posCtrlsClassical) { + bool alwaysTrue = true; + bool alwaysFalse = true; + for (const auto& hs : valuesToEntries.at(posCtrl)->states) { + if (!alwaysTrue) { + break; + } + const bool valueTrue = hs.isValueTrue(posCtrl); + alwaysTrue &= valueTrue; + alwaysFalse &= !valueTrue; + } + if (alwaysFalse) { + res.completelySuperfluous = true; + return res; + } + if (alwaysTrue) { + res.superfluousClassicalValues.insert(posCtrl); + } + } + for (const auto& negCtrl : negCtrlsClassical) { + bool alwaysTrue = true; + bool alwaysFalse = true; + for (const auto& hs : valuesToEntries.at(negCtrl)->states) { + if (!alwaysTrue) { + break; + } + const bool valueTrue = hs.isValueTrue(negCtrl); + alwaysTrue &= valueTrue; + alwaysFalse &= !valueTrue; + } + if (alwaysTrue) { + res.completelySuperfluous = true; + return res; + } + if (alwaysFalse) { + res.superfluousClassicalValues.insert(negCtrl); + } + } + return res; +} bool UnionTable::areThereSatisfiableCombinations( - std::span qubitCtrls, std::span posCtrlsClassical, - std::span negCtrlsClassical) {} -std::pair, std::set> + const std::span qubitCtrls, const std::span posCtrlsClassical, + const std::span negCtrlsClassical) const { + llvm::DenseMap qubitValues; + llvm::DenseMap classicalValues; + for (const auto& q : qubitCtrls) { + qubitValues[q] = true; + } + for (const auto& v : posCtrlsClassical) { + classicalValues[v] = true; + } + for (const auto& v : negCtrlsClassical) { + classicalValues[v] = false; + } + return !hasAlwaysZeroProbability(qubitValues, classicalValues); +} -UnionTable::getAntecedentsOfQubit(Value q, std::span qubits, - std::span classicalPositive, - std::span classicalNegative) {} +bool UnionTable::isQubitImplied( + const Value q, const std::span qubits, + const std::span classicalPositive, + const std::span classicalNegative) const { + llvm::DenseMap qMap; + llvm::DenseMap cPMap; + qMap[q] = false; + for (const auto& qV : qubits) { + qMap[qV] = true; + if (hasAlwaysZeroProbability(qMap, cPMap)) { + return true; + } + qMap.erase(qV); + } + for (const auto& cP : classicalPositive) { + cPMap[cP] = true; + if (hasAlwaysZeroProbability(qMap, cPMap)) { + return true; + } + cPMap.erase(cP); + } + for (const auto& cP : classicalNegative) { + cPMap[cP] = false; + if (hasAlwaysZeroProbability(qMap, cPMap)) { + return true; + } + cPMap.erase(cP); + } + return false; +} } // namespace mlir::qco diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 4b4ea23f62..87eda6a0d4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -682,32 +682,30 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { ut.propagateGate(xOp, q0, q3); ut.propagateGate(xOp, q1, q4); ut.propagateGate(xOp, q2, q5); - const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q5, qCtrl); + const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, v5, qCtrl); EXPECT_TRUE(globalPhase.has_value()); - EXPECT_EQ(-1, globalPhase.value()); + EXPECT_EQ(std::complex(-1, 0), globalPhase.value()); } TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { std::vector qCtrl = {v4, v5}; ut.propagateGate(xOp, q0, q4); ut.propagateGate(xOp, q1, q5); - const auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, q5, q4); - const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, q2, qCtrl); + const auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, v5, q4); + const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, v2, qCtrl); EXPECT_FALSE(emptyGlobalPhase.has_value()); EXPECT_TRUE(globalPhase.has_value()); - EXPECT_EQ(1, globalPhase.value()); + EXPECT_EQ(std::complex(1, 0), globalPhase.value()); } TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateMeasurement(v5, v6, i0); - const std::optional> result = + const llvm::DenseMap result = ut.getValueThatIsEquivalentToQubit(v6); - ASSERT_TRUE(result.has_value()); - auto [classicalValue, bitValue] = result.value(); - ASSERT_EQ(classicalValue, i0); - ASSERT_TRUE(bitValue); + ASSERT_FALSE(result.empty()); + ASSERT_TRUE(result.at(i0)); } TEST_F(UnionTablePropertiesTest, FindEquivalentReversedClassicalValue) { @@ -715,12 +713,10 @@ TEST_F(UnionTablePropertiesTest, FindEquivalentReversedClassicalValue) { ut.propagateGate(xOp, q1, q5, q4); ut.propagateGate(xOp, q4, q6); ut.propagateMeasurement(v5, v7, i0); - const std::optional> result = + const llvm::DenseMap result = ut.getValueThatIsEquivalentToQubit(v6); - ASSERT_TRUE(result.has_value()); - auto [classicalValue, bitValue] = result.value(); - ASSERT_EQ(classicalValue, i0); - ASSERT_FALSE(bitValue); + ASSERT_FALSE(result.empty()); + ASSERT_FALSE(result.at(i0)); } TEST_F(UnionTablePropertiesTest, FindNoEquivalentClassicalValue) { @@ -728,21 +724,16 @@ TEST_F(UnionTablePropertiesTest, FindNoEquivalentClassicalValue) { ut.propagateGate(xOp, q1, q5, q4); ut.propagateGate(hOp, q4, q6); ut.propagateMeasurement(v5, v7, i0); - const std::optional> result = + const llvm::DenseMap result = ut.getValueThatIsEquivalentToQubit(v6); - ASSERT_FALSE(result.has_value()); + ASSERT_TRUE(result.empty()); } TEST_F(UnionTablePropertiesTest, ZeroIsAlwaysAntecedent) { std::vector classicalIndexVec = {i0}; ut.propagateMeasurement(v0, v4, i0); ut.propagateGate(hOp, q1, q5); - auto [antecedentQubits, antecedentClassical] = - ut.getAntecedentsOfQubit(v5, q4, classicalIndexVec, {}); - ASSERT_EQ(antecedentQubits.size(), 1); - ASSERT_EQ(antecedentClassical.size(), 1); - ASSERT_EQ(*antecedentQubits.begin(), v4); - ASSERT_EQ(*antecedentClassical.begin(), i0); + ASSERT_TRUE(ut.isQubitImplied(v5, q4, classicalIndexVec, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubit) { @@ -750,27 +741,15 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubit) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(hOp, q1, q6, q5); - auto [antecedentQubits, antecedentClassical] = - ut.getAntecedentsOfQubit(v6, q5, classicalIndexVec, {}); - auto [antecedentQubitsEmpty, antecedentClassicalEmpty] = - ut.getAntecedentsOfQubit(v5, q6, classicalIndexVec, {}); - ASSERT_EQ(antecedentQubits.size(), 1); - ASSERT_EQ(antecedentClassical.size(), 1); - ASSERT_EQ(*antecedentQubits.begin(), v5); - ASSERT_EQ(*antecedentClassical.begin(), i0); - ASSERT_TRUE(antecedentQubitsEmpty.empty()); - ASSERT_TRUE(antecedentClassicalEmpty.empty()); + ASSERT_TRUE(ut.isQubitImplied(v6, q5, classicalIndexVec, {})); + ASSERT_FALSE(ut.isQubitImplied(v5, q6, classicalIndexVec, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyQubits) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(hOp, q1, q6, q5); - auto [antecedentQubits, antecedentClassical] = - ut.getAntecedentsOfQubit(v6, q5, {}, {}); - ASSERT_EQ(antecedentQubits.size(), 1); - ASSERT_TRUE(antecedentClassical.empty()); - ASSERT_EQ(*antecedentQubits.begin(), v5); + ASSERT_TRUE(ut.isQubitImplied(v6, q5, {}, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { @@ -778,11 +757,7 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(hOp, q1, q6, q5); - auto [antecedentQubits, antecedentClassical] = - ut.getAntecedentsOfQubit(v6, {}, classicalIndexVec, {}); - ASSERT_TRUE(antecedentQubits.empty()); - ASSERT_EQ(antecedentClassical.size(), 1); - ASSERT_EQ(*antecedentClassical.begin(), i0); + ASSERT_TRUE(ut.isQubitImplied(v6, {}, classicalIndexVec, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitNegClassicalValues) { @@ -791,30 +766,26 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubitNegClassicalValues) { ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(xOp, q5, q6); ut.propagateGate(hOp, q1, q7, q6); - auto [antecedentQubits, antecedentClassical] = - ut.getAntecedentsOfQubit(v7, {}, {}, classicalIndexVec); - ASSERT_TRUE(antecedentQubits.empty()); - ASSERT_EQ(antecedentClassical.size(), 1); - ASSERT_EQ(*antecedentClassical.begin(), i0); + ASSERT_TRUE(ut.isQubitImplied(v7, {}, {}, classicalIndexVec)); } TEST_F(UnionTablePropertiesTest, globalPhaseOneQubit) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5); - const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4); - const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, q5); + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v4); + const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, v5); ASSERT_FALSE(globalPhase0.has_value()); ASSERT_TRUE(globalPhase1.has_value()); - ASSERT_EQ(globalPhase1.value(), -1); + ASSERT_EQ(std::complex(-1, 0), globalPhase1.value()); } TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsA) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(hOp, q1, q5); - const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4, q5); + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v4, q5); ASSERT_FALSE(globalPhase0.has_value()); } @@ -824,7 +795,7 @@ TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsB) { ut.propagateGate(hOp, q1, q5, q4); ut.propagateMeasurement(v5, v6, i0); - const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q4, q6); + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v4, q6); ASSERT_FALSE(globalPhase0.has_value()); } @@ -834,13 +805,13 @@ TEST_F(UnionTablePropertiesTest, globalPhaseTwoQubits) { ut.propagateGate(xOp, q2, q5); ut.propagateGate(xOp, q3, q6); - const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, q1, q4); - const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, q5, q6); + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v1, q4); + const auto globalPhase1 = ut.globalPhaseThatIsAdded(zOp, v5, q6); ASSERT_TRUE(globalPhase0.has_value()); ASSERT_TRUE(globalPhase1.has_value()); - ASSERT_EQ(globalPhase0, 1); - ASSERT_EQ(globalPhase1, -1); + ASSERT_EQ(std::complex(1, 0), globalPhase0); + ASSERT_EQ(std::complex(-1, 0), globalPhase1); } TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsA) { @@ -1124,7 +1095,7 @@ TEST_F(UnionTableSuperfluousTest, oneSuperfluousEach) { std::vector posClassicalCtrl = {i0, i3}; std::vector negClassicalCtrl = {i1, i2}; auto [completelySuperfluous, superfluousQubits, superfluousClassicalValues] = - ut.getSuperfluousControls(q8, quantumCtrl, posClassicalCtrl, + ut.getSuperfluousControls(quantumCtrl, posClassicalCtrl, negClassicalCtrl); ASSERT_EQ(superfluousQubits.size(), 1); ASSERT_EQ(superfluousClassicalValues.size(), 2); @@ -1138,8 +1109,8 @@ TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToNegQuantumCtrl) { std::vector quantumCtrl = {v11, v12, v16}; std::vector posClassicalCtrl = {i0, i3}; std::vector negClassicalCtrl = {i1, i2}; - const auto results = ut.getSuperfluousControls( - q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + const auto results = ut.getSuperfluousControls(quantumCtrl, posClassicalCtrl, + negClassicalCtrl); ASSERT_TRUE(results.completelySuperfluous); } @@ -1147,8 +1118,8 @@ TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToNegClassicalCtrl) { std::vector quantumCtrl = {v12, v16}; std::vector posClassicalCtrl = {i0, i2, i3}; std::vector negClassicalCtrl = {i1}; - const auto results = ut.getSuperfluousControls( - q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + const auto results = ut.getSuperfluousControls(quantumCtrl, posClassicalCtrl, + negClassicalCtrl); ASSERT_TRUE(results.completelySuperfluous); } @@ -1156,7 +1127,7 @@ TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToPosClassicalCtrl) { std::vector quantumCtrl = {v12, v16}; std::vector posClassicalCtrl = {i0}; std::vector negClassicalCtrl = {i1, i2, i3}; - const auto results = ut.getSuperfluousControls( - q8, quantumCtrl, posClassicalCtrl, negClassicalCtrl); + const auto results = ut.getSuperfluousControls(quantumCtrl, posClassicalCtrl, + negClassicalCtrl); ASSERT_TRUE(results.completelySuperfluous); } From 1a93171220f5b89f6c51f47c4ed9aabd5f81388e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 24 Jun 2026 20:10:33 +0200 Subject: [PATCH 093/131] :construction: Fixed test results --- .../ConstantPropagation/QuantumState.hpp | 2 +- .../ConstantPropagation/UnionTable.cpp | 26 ++++++++++++------- .../ConstantPropagation/test_unionTable.cpp | 14 +++++----- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index 07e689db93..dcbcd420eb 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -164,7 +164,7 @@ class QuantumState { */ MeasurementResult measureOrResetQubit(const unsigned int target, const bool reset) { - const auto qubitMask = 1U << target; + const auto qubitMask = 1U << globalToLocalQubitNumber.at(target); double probabilityZero = 0.0; double probabilityOne = 0.0; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 4ad2c54332..578be6829d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -414,12 +414,16 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, if (alwaysZero) { return std::optional(std::complex(1.0, 0.0)); } + if (!alwaysOne && ctrlsQuantum.empty() && posCtrlsClassical.empty() && + negCtrlsClassical.empty()) { + return std::optional>(); + } const auto participatingEntries = collectParticipatingEntries( {}, ctrlsQuantum, posCtrlsClassical, negCtrlsClassical, {}); // Check if state |11...11> can be reached - bool highestStateReachable = false; + bool highestStateReachable = alwaysOne; bool highestStateAlwaysReached = alwaysOne; for (const auto& ute : participatingEntries) { std::unordered_map qubitCtrlThisEntry; @@ -442,8 +446,8 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, for (const auto& hs : ute.states) { const auto ctrlsZeroProbability = hs.hasAlwaysZeroProbability( qubitCtrlThisEntry, classicalCtrlThisEntry); - highestStateReachable |= ctrlsZeroProbability; - highestStateAlwaysReached &= ctrlsZeroProbability; + highestStateReachable |= !ctrlsZeroProbability; + highestStateAlwaysReached &= !ctrlsZeroProbability; } if (highestStateReachable && !highestStateAlwaysReached) { return std::optional>(); @@ -482,15 +486,19 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, for (const auto& qCtrl : qubitCtrls) { const auto qIndex = qubitsToGlobalIndices.at(qCtrl); bool alwaysOne = true; + bool alwaysZero = true; for (const auto& hs : valuesToEntries.at(qCtrl)->states) { if (!alwaysOne) { break; } - if (hs.isQubitAlwaysZero(qIndex)) { - res.completelySuperfluous = true; - return res; + if (alwaysZero && !hs.isQubitAlwaysZero(qIndex)) { + alwaysZero = false; } - alwaysOne &= hs.isQubitAlwaysOne(qIndex); + alwaysOne &= !alwaysZero && hs.isQubitAlwaysOne(qIndex); + } + if (alwaysZero) { + res.completelySuperfluous = true; + return res; } if (alwaysOne) { res.superfluousQubits.insert(qCtrl); @@ -500,7 +508,7 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, bool alwaysTrue = true; bool alwaysFalse = true; for (const auto& hs : valuesToEntries.at(posCtrl)->states) { - if (!alwaysTrue) { + if (!alwaysTrue && !alwaysFalse) { break; } const bool valueTrue = hs.isValueTrue(posCtrl); @@ -519,7 +527,7 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, bool alwaysTrue = true; bool alwaysFalse = true; for (const auto& hs : valuesToEntries.at(negCtrl)->states) { - if (!alwaysTrue) { + if (!alwaysTrue && !alwaysFalse) { break; } const bool valueTrue = hs.isValueTrue(negCtrl); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 87eda6a0d4..6c31ae94aa 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -677,7 +677,7 @@ TEST_F(UnionTablePropertiesTest, testAllTopHybridStates) { EXPECT_TRUE(ut.areStatesAllTop()); } -TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { +TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { std::vector qCtrl = {v3, v4}; ut.propagateGate(xOp, q0, q3); ut.propagateGate(xOp, q1, q4); @@ -687,10 +687,10 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { EXPECT_EQ(std::complex(-1, 0), globalPhase.value()); } -TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { +TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { std::vector qCtrl = {v4, v5}; ut.propagateGate(xOp, q0, q4); - ut.propagateGate(xOp, q1, q5); + ut.propagateGate(hOp, q1, q5); const auto emptyGlobalPhase = ut.globalPhaseThatIsAdded(zOp, v5, q4); const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, v2, qCtrl); EXPECT_FALSE(emptyGlobalPhase.has_value()); @@ -741,15 +741,15 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubit) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(hOp, q1, q6, q5); - ASSERT_TRUE(ut.isQubitImplied(v6, q5, classicalIndexVec, {})); - ASSERT_FALSE(ut.isQubitImplied(v5, q6, classicalIndexVec, {})); + ASSERT_TRUE(ut.isQubitImplied(v5, q6, classicalIndexVec, {})); + ASSERT_FALSE(ut.isQubitImplied(v6, q5, classicalIndexVec, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyQubits) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); ut.propagateGate(hOp, q1, q6, q5); - ASSERT_TRUE(ut.isQubitImplied(v6, q5, {}, {})); + ASSERT_TRUE(ut.isQubitImplied(v5, q6, {}, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { @@ -801,6 +801,7 @@ TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsB) { } TEST_F(UnionTablePropertiesTest, globalPhaseTwoQubits) { + ut.propagateQubitAlloc(v3); ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q2, q5); ut.propagateGate(xOp, q3, q6); @@ -832,6 +833,7 @@ TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsB) { } TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsC) { + ut.propagateIntAlloc(i1, 2); ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4); ut.propagateMeasurement(v4, v6, i0); From d7bfb199f74be1b68e8bc3f790cafd1da2c8fbb6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 25 Jun 2026 07:35:02 +0200 Subject: [PATCH 094/131] :construction: Fixed tests --- .../ConstantPropagation/test_unionTable.cpp | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 6c31ae94aa..1bdb98c2cb 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -21,8 +21,6 @@ using namespace mlir::qco; -// TODO: Tests for hasAlwaysZeroProbability - class UnionTableTest : public testing::Test { protected: mlir::MLIRContext context; @@ -729,6 +727,31 @@ TEST_F(UnionTablePropertiesTest, FindNoEquivalentClassicalValue) { ASSERT_TRUE(result.empty()); } +TEST_F(UnionTablePropertiesTest, hasAlwaysZeroProbabilityTest) { + std::vector classicalIndexVec = {i0}; + ut.propagateIntAlloc(i1, 20); + ut.propagateGate(hOp, q0, q3); + ut.propagateGate(xOp, q1, q4, q3); + ut.propagateMeasurement(v4, v5, i0); + + llvm::DenseMap qubits0; + llvm::DenseMap classicals0; + llvm::DenseMap qubits1; + llvm::DenseMap classicals1; + qubits0[v3] = true; + qubits0[v5] = true; + qubits0[v2] = false; + classicals0[i0] = true; + classicals0[i1] = true; + qubits1[v3] = false; + qubits1[v5] = false; + qubits1[v2] = true; + classicals1[i1] = false; + + ASSERT_FALSE(ut.hasAlwaysZeroProbability(qubits0, classicals0)); + ASSERT_TRUE(ut.hasAlwaysZeroProbability(qubits1, classicals1)); +} + TEST_F(UnionTablePropertiesTest, ZeroIsAlwaysAntecedent) { std::vector classicalIndexVec = {i0}; ut.propagateMeasurement(v0, v4, i0); @@ -756,7 +779,7 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { std::vector classicalIndexVec = {i0}; ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(hOp, q1, q6, q5); + ut.propagateGate(hOp, q5, q6, {}, {}, classicalIndexVec); ASSERT_TRUE(ut.isQubitImplied(v6, {}, classicalIndexVec, {})); } @@ -764,8 +787,8 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubitNegClassicalValues) { std::vector classicalIndexVec = {i0}; ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(xOp, q5, q6); - ut.propagateGate(hOp, q1, q7, q6); + ut.propagateGate(hOp, q5, q6, {}, classicalIndexVec); + ut.propagateGate(xOp, q6, q7); ASSERT_TRUE(ut.isQubitImplied(v7, {}, {}, classicalIndexVec)); } @@ -850,8 +873,8 @@ TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsC) { classicalCombinations)); ASSERT_FALSE( ut.areThereSatisfiableCombinations({}, classicalVal0, classicalVal1)); - ASSERT_FALSE(ut.areThereSatisfiableCombinations(qubitCombinations, {}, - classicalCombinations)); + ASSERT_TRUE(ut.areThereSatisfiableCombinations(qubitCombinations, {}, + classicalCombinations)); } class SmallUnionTableTest : public testing::Test { From 2cc054587531dfc03d279d92ef41257d056dfbe3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 16:32:16 +0200 Subject: [PATCH 095/131] :construction: Added infrastructure for constant propagation passes --- mlir/include/mlir/Compiler/CompilerPipeline.h | 3 + .../mlir/Dialect/QCO/Transforms/Passes.td | 52 ++++++++++++++ mlir/lib/Compiler/CompilerPipeline.cpp | 3 + .../Optimizations/ConstantPropagation.cpp | 67 +++++++++++++++++++ mlir/tools/mqt-cc/mqt-cc.cpp | 4 ++ .../Compiler/test_compiler_pipeline.cpp | 8 ++- 6 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp diff --git a/mlir/include/mlir/Compiler/CompilerPipeline.h b/mlir/include/mlir/Compiler/CompilerPipeline.h index 5e6f8cd77d..4468672651 100644 --- a/mlir/include/mlir/Compiler/CompilerPipeline.h +++ b/mlir/include/mlir/Compiler/CompilerPipeline.h @@ -46,6 +46,9 @@ struct QuantumCompilerConfig { /// Enable Hadamard lifting bool enableHadamardLifting = false; + + /// Enable constant propagation + bool enableConstantPropagation = false; }; /** diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 54fccffdc7..78cc11e6bc 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -158,4 +158,56 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { }]; } +def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect"]; + let summary = "This pass applies constant propagation to a circuit. It " + "assumes that all input qubits are |0>. It propagates the " + "state of the qubits up to a given complexity threshold and " + "removes gates which are superfluous considering the current " + "state. It also replaces quantum control with classical control " + "if possible and moves measurements as far to the front as possible."; + let description = [{ + This pass applies quantum constant propagation. This optimization routines assumes that the input qubits of the + circuits are |0>. It propagates the qubit states and the state of additional classical values through the circuit. + All quantum instructions are removed which are superfluous considering the current state. Additionaly, quantum + controls can be replaced by equivalent classical control. To do this most efficiently, the measurements are moved + as far to the front of a circuit as possible. + + The qubit states and classical values are stored in hybrid states. Hybrid states are stored in a union table to + reduce the amount of complex amplitudes and classical values to track. There is a maximum number of non zero + amplitudes that is saved per union table entry. Additionally, there is also a maximum of hybrid states that can be + propagated. If the maximum number of amplitudes or the maximum number of hybrid states is exceeded, the propagated + state reaches top and no optimization routines are further applied. + + The applied optimization routines are: + + **General Control Reduction** + If a controlling qubit or classical value is always true, the control is removed. Classical vales are considered + true if they are not zero. + If a controlling qubit or classical value is alaways false, the complete gate is removed. + + **Unsatisfiable Controls** + If a combination of controls (both quantum and classical) cannot be satisfied, the complete controlled gate is + removed. + + **Quantum Control Reduction** + If a controlling qubit is true if and only if a classical value is true (false), the quantum control is replaced by a + classical one. If the classical value is false if and only if the qubit is true, the classical value becomes a + negative control. + + **Implied Qubits** + If a controlling qubit is implied by other controlling qubits/classical values, the implied qubit is removed from + the controls. + + **Phase Gate Reduction** + If a phase gate would only apply a global phase (e.g. if the quantum state is |11> and a Z-gate is applied), the + phase gate is removed and replaced by a global phase gate instead. + + }]; + let options = [Option<"maximumNonzeroAmplitudes", "maximumNonzeroAmplitudes", "std::size_t", "4", + "The maximum number of non-zero amplitudes in the tracted quantum states before reaching top.">, + Option<"maximumHybridStates", "maximumHybridStates", "std::size_t", "4", + "The maximum number of hybrid states which have a non-zero probabilty.">]; +} + #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/lib/Compiler/CompilerPipeline.cpp b/mlir/lib/Compiler/CompilerPipeline.cpp index 311152b41a..494821f4c3 100644 --- a/mlir/lib/Compiler/CompilerPipeline.cpp +++ b/mlir/lib/Compiler/CompilerPipeline.cpp @@ -143,6 +143,9 @@ QuantumCompilerPipeline::runPipeline(ModuleOp module, if (config_.enableHadamardLifting) { pm.addPass(qco::createHadamardLifting()); } + if (config_.enableConstantPropagation) { + pm.addPass(qco::createConstantPropagation()); + } }))) { return failure(); } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp new file mode 100644 index 0000000000..dee6e20e16 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" + +#include +#include + +#include +namespace mlir::qco { + +#define GEN_PASS_DEF_CONSTANTPROPAGATION +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +namespace { + +/** + * This method moves all measurements as far to the front as possible, in order + * to execute QCP more efficiently. + */ +bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { + bool changed = false; + // PatternRewriter rewriter(ctx); + // module.walk([&](MeasureOp op) { + // Operation* previousInstruction = op.getInQubit().getDefiningOp(); + // Operation* previousNode = op->getPrevNode(); + // while (llvm::dyn_cast(previousNode) && + // previousInstruction != previousNode) { + // previousNode = previousNode->getPrevNode(); + // } + // if (previousNode != previousInstruction) { + // rewriter.moveOpAfter(op, previousInstruction); + // changed = true; + // } + // }); + + return changed; +} + +/** + * @brief This pass applies constant propagation to a circuit. It assumes that + * all states start in |0> and removes quantum instructions that are superfluous + * when the current state is considered. It also replaces quantum resources by + * classical resources. + */ +struct ConstantPropagation final + : impl::ConstantPropagationBase { + using ConstantPropagationBase::ConstantPropagationBase; + + void runOnOperation() override { + // if (failed(route(getOperation(), &getContext()))) { + // signalPassFailure(); + // } + } +}; + +} // namespace + +} // namespace mlir::qco diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 8637294b0c..9616462e79 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -81,6 +81,10 @@ static cl::opt disableMergeSingleQubitRotationGates( static cl::opt enableHadamardLifting( "hadamard-lifting", cl::desc("Apply Hadamard lifting during optimization"), cl::init(false)); +static cl::opt enableHadamardLifting( + "constant-propagation", + cl::desc("Apply constant propagation during optimization"), + cl::init(false)); /** * @brief Load and parse a .qasm file diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index 44618eedae..cdf01202cd 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -119,12 +119,14 @@ class CompilerPipelineTest static void runPipeline(const mlir::ModuleOp module, const bool convertToQIR, const bool disableMergeSingleQubitRotationGates, const bool enableHadamardLifting, + const bool enableConstantPropagation, mlir::CompilationRecord& record) { mlir::QuantumCompilerConfig config; config.convertToQIR = convertToQIR; config.disableMergeSingleQubitRotationGates = disableMergeSingleQubitRotationGates; config.enableHadamardLifting = enableHadamardLifting; + config.enableConstantPropagation = enableConstantPropagation; config.recordIntermediates = true; config.printIRAfterAllStages = true; @@ -168,7 +170,7 @@ TEST_P(CompilerPipelineTest, EndToEndPipeline) { EXPECT_TRUE(mlir::verify(*module).succeeded()); mlir::CompilationRecord record; - runPipeline(module.get(), testCase.convertToQIR, false, false, record); + runPipeline(module.get(), testCase.convertToQIR, false, false, false, record); ASSERT_TRUE(testCase.qcReferenceBuilder); auto qcReference = buildQCReference(testCase.qcReferenceBuilder); @@ -215,7 +217,7 @@ TEST_F(CompilerPipelineTest, RotationGateMergingPass) { ASSERT_TRUE(module); mlir::CompilationRecord record; - runPipeline(module.get(), false, false, false, record); + runPipeline(module.get(), false, false, false, false, record); // The outputs must differ, proving the pass ran and transformed the IR EXPECT_NE(record.afterQCOCanon, record.afterOptimization); @@ -238,7 +240,7 @@ TEST_F(CompilerPipelineTest, HadamardLiftingPass) { ASSERT_TRUE(module); mlir::CompilationRecord record; - runPipeline(module.get(), false, true, true, record); + runPipeline(module.get(), false, true, true, false, record); // The outputs must differ, proving the pass ran and transformed the IR EXPECT_NE(record.afterQCOCanon, record.afterOptimization); From 500ada9c47f2d97c27265cc49a871eb1f64792ae Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 16:32:46 +0200 Subject: [PATCH 096/131] :test_tube: Added first test for constant propagation --- .../Transforms/Optimizations/CMakeLists.txt | 1 + .../test_qco_constant_propagation.cpp | 116 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 5d9dce19ba..5c4da52d9a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -9,6 +9,7 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable(${target_name} test_qco_hadamard_lifting.cpp test_qco_merge_single_qubit_rotation.cpp + test_qco_constant_propagation.cpp ConstantPropagation/test_quantumState.cpp ConstantPropagation/test_hybridState.cpp ConstantPropagation/test_unionTable.cpp) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp new file mode 100644 index 0000000000..eede190718 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/QCO/Builder/QCOProgramBuilder.h" +#include "mlir/Dialect/QCO/IR/QCODialect.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Support/IRVerification.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +class QCOConstantPropagationTest : public testing::Test { +protected: + MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + OwningOpRef module; + OwningOpRef reference; + + QCOConstantPropagationTest() + : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + // Register all necessary dialects + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + programBuilder.initialize(); + referenceBuilder.initialize(); + } + + /** + * @brief Adds the hadamardLiftingPass to the current context and runs it. + */ + static LogicalResult runConstantPropagationPass(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createConstantPropagation()); + return pm.run(module); + } + + /** + * @brief Adds the canonicalizerPass to the current context and runs it. + */ + static LogicalResult runCanonicalizerPass(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createCanonicalizerPass()); + return pm.run(module); + } +}; + +} // namespace + +/** + * @brief Test: This test checks if CNOTs or the controls of CNOTs are removed + * if we can classically determine the ctrls value. + */ +TEST_F(QCOConstantPropagationTest, reducePosCtrls) { + const auto iAttr = programBuilder.getF64FloatAttr(-0.3926991); + Value i0 = + arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); + auto q = programBuilder.allocQubitRegister(4); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.x(q[0]); + q[0] = programBuilder.h(q[0]); + programBuilder.cx(q[0], q[1]); + q[2] = programBuilder.h(q[2]); + q[2] = programBuilder.z(q[2]); + q[2] = programBuilder.h(q[2]); + auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); + programBuilder.cry(0.3, q2, q3); + module = programBuilder.finalize(); + + const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); + Value i0Ref = arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getLoc(), iAttrRef); + auto qRef = referenceBuilder.allocQubitRegister(4); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.x(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[2] = referenceBuilder.z(qRef[2]); + qRef[2] = referenceBuilder.h(qRef[2]); + auto [q2Ref, q3Ref] = referenceBuilder.crx(i0Ref, qRef[2], qRef[3]); + referenceBuilder.cry(0.3, q2Ref, q3Ref); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} \ No newline at end of file From 9c47ebf9f3af56bb16e94b8e6bf4feb4641b5fb2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 17:10:27 +0200 Subject: [PATCH 097/131] :construction: Continued constant propagation infrastructure --- .../Optimizations/ConstantPropagation.cpp | 198 ++++++++++++++++-- 1 file changed, 180 insertions(+), 18 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index dee6e20e16..755635afaf 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -9,12 +9,15 @@ */ #include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include +#include #include #include -#include +#include namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION @@ -22,29 +25,188 @@ namespace mlir::qco { namespace { +bool isEntryPoint(const func::FuncOp op) { + const auto passthroughAttr = op->getAttrOfType("passthrough"); + if (!passthroughAttr) { + return false; + } + + return llvm::any_of(passthroughAttr, [](const Attribute attr) { + return mlir::isa(attr) && + mlir::cast(attr) == "entry_point"; + }); +} + /** * This method moves all measurements as far to the front as possible, in order - * to execute QCP more efficiently. + * to execute constant propagation more efficiently. */ bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { bool changed = false; - // PatternRewriter rewriter(ctx); - // module.walk([&](MeasureOp op) { - // Operation* previousInstruction = op.getInQubit().getDefiningOp(); - // Operation* previousNode = op->getPrevNode(); - // while (llvm::dyn_cast(previousNode) && - // previousInstruction != previousNode) { - // previousNode = previousNode->getPrevNode(); - // } - // if (previousNode != previousInstruction) { - // rewriter.moveOpAfter(op, previousInstruction); - // changed = true; - // } - // }); + PatternRewriter rewriter(ctx); + module.walk([&](MeasureOp op) { + Operation* previousInstruction = op.getQubitIn().getDefiningOp(); + Operation* previousNode = op->getPrevNode(); + while (isa(previousNode) && + previousInstruction != previousNode) { + previousNode = previousNode->getPrevNode(); + } + if (previousNode != previousInstruction) { + rewriter.moveOpAfter(op, previousInstruction); + changed = true; + } + }); return changed; } +LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, + std::span worklist, + const std::span quantumCtrls, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls) { + /// Iterate work-list. + bool addedAtLeastOneQubit = false; + for (Operation* curr : worklist) { + if (addedAtLeastOneQubit && ut->areStatesAllTop()) { + return success(); + } + if (curr == nullptr) { + continue; // Skip erased ops. + } + + rewriter.setInsertionPoint(curr); + + // const auto res = + // TypeSwitch(curr) + // /// mqtopt Dialect + // .Case([&](const UnitaryOpInterface op) { + // return handleUnitary(ut, op, posClassicalCtrls, + // negClassicalCtrls, + // rewriter); + // }) + // .Case([&](const ResetOp op) { + // return handleReset(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // .Case([&](const MeasureOp op) { + // return handleMeasure(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // .Case([&](const AllocOp op) { + // addedAtLeastOneQubit = true; + // return handleQubitAlloc(ut, op); + // }) + // .Case( + // [&](const StaticOp op) { return handleStaticOp(ut, op); }) + // .Case([&]([[maybe_unused]] SinkOp op) { + // return WalkResult::advance(); + // }) + // /// built-in Dialect + // .Case([&]([[maybe_unused]] ModuleOp op) { + // return WalkResult::advance(); + // }) + // /// memref Dialect + // .Case( + // [&](const memref::AllocOp op) { return handleAlloc(ut, op); + // }) + // .Case([&](const memref::AllocaOp op) { + // return handleAlloca(ut, op); + // }) + // .Case( + // [&]([[maybe_unused]] const memref::DeallocOp op) { + // return WalkResult::advance(); + // }) + // .Case([&](const memref::LoadOp op) { + // addedAtLeastOneQubit = true; + // return handleLoad(ut, op); + // }) + // .Case([&](const memref::StoreOp op) { + // return handleStore(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // // arith dialect + // .Case([&](const arith::ConstantOp op) { + // return handleConstant(qcp, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // .Case( + // [&](const arith::XOrIOp op) { return handleXOrIOp(qcp, op); + // }) + // .Case( + // [&](const arith::AndIOp op) { return handleAndIOp(qcp, op); + // }) + // /// func Dialect + // .Case([&](const func::FuncOp op) { + // return handleFunc(qcp, op, rewriter); + // }) + // .Case([&]([[maybe_unused]] func::ReturnOp op) { + // return WalkResult::advance(); + // }) + // /// scf Dialect + // .Case([&](scf::ForOp) { return handleFor(); }) + // .Case([&](const scf::IfOp op) { + // return handleIf(qcp, op, worklist, posClassicalCtrls, + // negClassicalCtrls, rewriter); + // }) + // .Case([&]([[maybe_unused]] scf::YieldOp op) { + // return WalkResult::advance(); + // }) + // /// Skip the rest. + // .Default([](auto) { + // throw std::runtime_error("Unsupported operation"); + // return WalkResult::interrupt(); + // }); + + // if (res.wasInterrupted()) { + // return failure(); + // } + } + return success(); +} + +/** + * @brief Do constant propagation. + * + * @details + * Collects all functions marked with the 'entry_point' attribute, builds + * a preorder worklist of their operations, and processes that list. + * + * @note + * We consciously avoid MLIR pattern drivers: Idiomatic MLIR + * transformation patterns are independent and order-agnostic. Since we + * require state-sharing between patterns for the transformation we + * violate this assumption. Essentially this is also the reason why we + * can't utilize MLIR's `applyPatternsGreedily` function. Moreover, we + * require pre-order traversal which current drivers of MLIR don't + * support. However, even if such a driver would exist, it would probably + * not return logical results which we require for error-handling + * (similarly to `walkAndApplyPatterns`). Consequently, a custom driver + * would be required in any case, which adds unnecessary code to maintain. + * + * @return Success if constant propagation has been applied successfully + */ +LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { + PatternRewriter rewriter(ctx); + + /// Prepare work-list. + std::vector worklist; + + for (const auto func : module.getOps()) { + + if (!isEntryPoint(func)) { + continue; // Ignore non entry_point functions for now. + } + func->walk( + [&](Operation* op) { worklist.push_back(op); }); + } + + // TODO: Take maximum from params + auto ut = UnionTable(16, 4); + + return iterateThroughWorklist(rewriter, &ut, worklist, {}, {}, {}); +} + /** * @brief This pass applies constant propagation to a circuit. It assumes that * all states start in |0> and removes quantum instructions that are superfluous @@ -56,9 +218,9 @@ struct ConstantPropagation final using ConstantPropagationBase::ConstantPropagationBase; void runOnOperation() override { - // if (failed(route(getOperation(), &getContext()))) { - // signalPassFailure(); - // } + if (failed(applyCP(getOperation(), &getContext()))) { + signalPassFailure(); + } } }; From ab7ba5b800b83200c4b8e92c4bcf68cb9d337867 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 13:34:39 +0200 Subject: [PATCH 098/131] :test_tube: Added constant propagation test --- .../test_qco_constant_propagation.cpp | 464 ++++++++++++++++++ 1 file changed, 464 insertions(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index eede190718..2443a71555 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -111,6 +111,470 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that CNOTs are not changed if the target is not + * in |0> or |1>. + */ +TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + programBuilder.cx(q[0], q[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.cx(qRef[0], qRef[1]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that implied Qubits are removed from a + * controlled gate. + */ +TEST_F(QCOConstantPropagationTest, testRemoveImpliedQubits) { + auto q = programBuilder.allocQubitRegister(4); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.h(q[1]); + auto [q01, q2] = + programBuilder.ctrl({q[0], q[1]}, {q[2]}, [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + programBuilder.ctrl({q01[0], q01[1], q2[0]}, {q[3]}, + [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(4); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + auto [qRef01, qRef2] = referenceBuilder.ctrl( + {qRef[0], qRef[1]}, {qRef[2]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + referenceBuilder.cx(qRef2[0], qRef[3]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that gates whose quantum controls cannot be + * satisfied are removed. + */ +TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.x(q[1]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + programBuilder.ctrl({q0, q1}, {q[2]}, [&](const ValueRange target) { + return SmallVector{programBuilder.s(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.x(qRef[1]); + referenceBuilder.cx(qRef[0], qRef[1]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that gates whose quantum and classical controls + * cannot be satisfied are removed. + */ +TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.x(q[1]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q01, b0] = programBuilder.measure(q0); + programBuilder.qcoIf(b0, {q01, q1}, [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); + return SmallVector{qi0, qi1}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.x(qRef[1]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + referenceBuilder.measure(qRef0); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that gates are unconditionally applied if the + * bit they depend on is always zero. + */ +TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsZero) { + auto q = programBuilder.allocQubitRegister(1); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.h(q[0]); + auto [q0, b0] = programBuilder.measure(q[0]); + programBuilder.qcoIf( + b0, {q0}, + [&](const ValueRange args) { + const auto qi0 = programBuilder.x(args[0]); + return SmallVector{qi0}; + }, + [&](const ValueRange args) { + const auto qi0 = programBuilder.h(args[0]); + return SmallVector{qi0}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); + referenceBuilder.h(qRef0); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that gates are unconditionally applied if the + * bit they depend on is always one. + */ +TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsOne) { + auto q = programBuilder.allocQubitRegister(1); + q[0] = programBuilder.x(q[0]); + auto [q0, b0] = programBuilder.measure(q[0]); + programBuilder.qcoIf( + b0, {q0}, + [&](const ValueRange args) { + const auto qi0 = programBuilder.x(args[0]); + return SmallVector{qi0}; + }, + [&](const ValueRange args) { + const auto qi0 = programBuilder.h(args[0]); + return SmallVector{qi0}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + qRef[0] = referenceBuilder.x(qRef[0]); + auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); + referenceBuilder.x(qRef0); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that conditionals are not changed if we cannot + * tell the bits value. + */ +TEST_F(QCOConstantPropagationTest, testDoNotRemoveClassicalConditional) { + auto q = programBuilder.allocQubitRegister(1); + q[0] = programBuilder.h(q[0]); + auto [q0, b0] = programBuilder.measure(q[0]); + programBuilder.qcoIf( + b0, {q0}, + [&](const ValueRange args) { + const auto qi0 = programBuilder.x(args[0]); + return SmallVector{qi0}; + }, + [&](const ValueRange args) { + const auto qi0 = programBuilder.h(args[0]); + return SmallVector{qi0}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + qRef[0] = referenceBuilder.x(qRef[0]); + auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); + referenceBuilder.qcoIf( + bRef0, {qRef0}, + [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }, + [&](const ValueRange args) { + const auto qi0 = referenceBuilder.h(args[0]); + return SmallVector{qi0}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that a quantum conditional is replaced by a + * classical if a qubit and a classical bit are equivalent. + */ +TEST_F(QCOConstantPropagationTest, + testEquivalentPositiveClassicalAndQuantumControl) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + programBuilder.measure(q0); + programBuilder.cx(q1, q[2]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + referenceBuilder.qcoIf(bRef0, {qRef[2]}, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that multiple quantum conditionals are replaced + * by a classical if a qubit and a classical bit are equivalent. + */ +TEST_F(QCOConstantPropagationTest, testEquivalentClassicalAndQuantumControl) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + programBuilder.measure(q0); + auto [q11, q2] = programBuilder.cx(q1, q[2]); + q[1] = programBuilder.x(q11); + programBuilder.cy(q[1], q2); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + const auto qRange2 = + referenceBuilder.qcoIf(bRef0, {qRef[2]}, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }); + referenceBuilder.x(qRef1); + referenceBuilder.qcoIf( + bRef0, qRange2, + [&](const ValueRange args) { return SmallVector{args[0]}; }, + [&](const ValueRange args) { + const auto qi0 = referenceBuilder.y(args[0]); + return SmallVector{qi0}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a quantum control is removed if the + * classical control implies the quantum one. + */ +TEST_F(QCOConstantPropagationTest, testClassicalImpliesQuantum) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + auto [q0, b0] = programBuilder.measure(q[0]); + q[1] = programBuilder.x(q[1]); + auto [q01, q1] = programBuilder.cx(q0, q[1]); + auto [q11, q02] = programBuilder.ch(q1, q01); + programBuilder.qcoIf(b0, {q02, q11}, [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); + return SmallVector{qi0, qi1}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); + qRef[1] = referenceBuilder.x(qRef[1]); + const auto qRefRange1 = + referenceBuilder.qcoIf(bRef0, {qRef[1]}, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }); + referenceBuilder.qcoIf( + bRef0, {qRef0}, + [&](const ValueRange args) { return SmallVector{args[0]}; }, + [&](const ValueRange args) { + const auto qi0 = referenceBuilder.h(args[0]); + return SmallVector{qi0}; + }); + referenceBuilder.qcoIf(bRef0, qRefRange1, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a phase gate is removed if it only adds a + * global phase = 1. + */ +TEST_F(QCOConstantPropagationTest, testReplaceSingleQubitPhaseGatePlusOne) { + auto q = programBuilder.allocQubitRegister(1); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.z(q[0]); + q[0] = programBuilder.z(q[0]); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.z(q[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a phase gate is replaced by a global phase + * gate if it only adds a global phase. + */ +TEST_F(QCOConstantPropagationTest, testReplaceSingleQubitPhaseGateMinusOne) { + auto q = programBuilder.allocQubitRegister(1); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.z(q[0]); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.z(q[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.gphase(-1.0); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a multi-qubit phase gate is removed if it + * only adds a global phase that is one. + */ +TEST_F(QCOConstantPropagationTest, testRemoveMultiQubitPhaseGatePlusOne) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + programBuilder.cz(q[0], q[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a multi-qubit phase gate is replaced if it + * only adds a global phase. + */ +TEST_F(QCOConstantPropagationTest, testRemoveMultiQubitPhaseGateMinusOne) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.x(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + programBuilder.cz(q0, q1); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.x(qRef[0]); + referenceBuilder.cx(qRef[0], qRef[1]); + referenceBuilder.gphase(-1.0); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks if a multi-qubit phase gate is not removed if + * it adds different global phases depending on the actual state. + */ +TEST_F(QCOConstantPropagationTest, testDoNotRemoveMultiQubitPhaseGate) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q01, b0] = programBuilder.measure(q0); + programBuilder.cz(q01, q1); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + referenceBuilder.cz(qRef01, qRef1); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } \ No newline at end of file From a0341a7737f4a078d1035905c201b45813789f97 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 17:44:21 +0200 Subject: [PATCH 099/131] :construction: Global phase as double --- .../ConstantPropagation/UnionTable.hpp | 2 +- .../ConstantPropagation/UnionTable.cpp | 27 +++++++++---------- .../ConstantPropagation/test_unionTable.cpp | 10 +++---- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 48945ec564..769dd7dff6 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -483,7 +483,7 @@ class UnionTable { * @returns An optional containing the globally added value, if applicable. */ [[nodiscard("UnionTable::globalPhaseThatIsAdded called but ignored")]] - std::optional> + std::optional globalPhaseThatIsAdded(Operation* op, Value target, std::span ctrlsQuantum = {}, std::span posCtrlsClassical = {}, diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 578be6829d..3602b4e415 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -385,18 +385,18 @@ UnionTable::getValueThatIsEquivalentToQubit(const Value qubit) const { return result; } -std::optional> +std::optional UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, const std::span ctrlsQuantum, const std::span posCtrlsClassical, const std::span negCtrlsClassical) { // Diagonal gates w/o parameters: IdOp, ZOp, SOp, SdgOp, TOp, TdgOp if (isa(op)) { - return std::optional(std::complex(1.0, 0.0)); + return std::optional(0.0); } if (!(isa(op) || isa(op) || isa(op) || isa(op) || isa(op))) { - return std::optional>(); + return std::optional(); } const auto targetIndex = qubitsToGlobalIndices.at(target); const auto targetUte = valuesToEntries.at(target); @@ -412,11 +412,11 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, } if (alwaysZero) { - return std::optional(std::complex(1.0, 0.0)); + return std::optional(0.0); } if (!alwaysOne && ctrlsQuantum.empty() && posCtrlsClassical.empty() && negCtrlsClassical.empty()) { - return std::optional>(); + return std::optional(); } const auto participatingEntries = collectParticipatingEntries( @@ -450,32 +450,31 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, highestStateAlwaysReached &= !ctrlsZeroProbability; } if (highestStateReachable && !highestStateAlwaysReached) { - return std::optional>(); + return std::optional(); } } if (!highestStateReachable) { - return std::optional(std::complex(1.0, 0.0)); + return std::optional(0.0); } if (!highestStateAlwaysReached) { - return std::optional>(); + return std::optional(); } // Only highest state reachable, return respective phase if (isa(op)) { - return std::optional(std::complex(-1.0, 0.0)); + return std::optional(std::numbers::pi); } if (isa(op)) { - return std::optional(std::complex(0.0, 1.0)); + return std::optional(std::numbers::pi / 2); } if (isa(op)) { - return std::optional(std::complex(0.0, -1.0)); + return std::optional(3.0 * std::numbers::pi / 2); } - constexpr auto inv_sqrt2 = 1.0 / std::numbers::sqrt2; if (isa(op)) { - return std::optional(std::complex(inv_sqrt2, inv_sqrt2)); + return std::optional(std::numbers::pi / 4); } // Tdg Op - return std::optional(std::complex(inv_sqrt2, -inv_sqrt2)); + return std::optional(-std::numbers::pi / 2); } SuperfluousResult diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 1bdb98c2cb..1ad57c4468 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -682,7 +682,7 @@ TEST_F(UnionTablePropertiesTest, testMinusOneGlobalPhase) { ut.propagateGate(xOp, q2, q5); const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, v5, qCtrl); EXPECT_TRUE(globalPhase.has_value()); - EXPECT_EQ(std::complex(-1, 0), globalPhase.value()); + EXPECT_EQ(std::numbers::pi, globalPhase.value()); } TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { @@ -693,7 +693,7 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { const auto globalPhase = ut.globalPhaseThatIsAdded(zOp, v2, qCtrl); EXPECT_FALSE(emptyGlobalPhase.has_value()); EXPECT_TRUE(globalPhase.has_value()); - EXPECT_EQ(std::complex(1, 0), globalPhase.value()); + EXPECT_EQ(0.0, globalPhase.value()); } TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { @@ -801,7 +801,7 @@ TEST_F(UnionTablePropertiesTest, globalPhaseOneQubit) { ASSERT_FALSE(globalPhase0.has_value()); ASSERT_TRUE(globalPhase1.has_value()); - ASSERT_EQ(std::complex(-1, 0), globalPhase1.value()); + ASSERT_EQ(std::numbers::pi, globalPhase1.value()); } TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsA) { @@ -834,8 +834,8 @@ TEST_F(UnionTablePropertiesTest, globalPhaseTwoQubits) { ASSERT_TRUE(globalPhase0.has_value()); ASSERT_TRUE(globalPhase1.has_value()); - ASSERT_EQ(std::complex(1, 0), globalPhase0); - ASSERT_EQ(std::complex(-1, 0), globalPhase1); + ASSERT_EQ(0.0, globalPhase0); + ASSERT_EQ(std::numbers::pi, globalPhase1); } TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsA) { From e0f9a9849eee2c2f5374e5ea2246503c1ab603ac Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 17:45:10 +0200 Subject: [PATCH 100/131] :construction: Handling of global phases without control --- .../Optimizations/ConstantPropagation.cpp | 272 ++++++++++++------ .../test_qco_constant_propagation.cpp | 29 +- 2 files changed, 186 insertions(+), 115 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 755635afaf..a585f0c858 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -11,13 +11,14 @@ #include "mlir/Dialect/QCO/IR/QCOOps.h" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" #include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include #include #include #include #include -#include namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION @@ -60,9 +61,74 @@ bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { return changed; } +void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { + for (const auto outQubit : op->getOutputQubits()) { + rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); + } + rewriter.eraseOp(*op); +} + +WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls) { + if (!posClassicalCtrls.empty() || !negClassicalCtrls.empty()) { + throw std::logic_error("Cannot handle constant operation in conditional " + "branches during constant propagation."); + } + + Value const res = op.getResult(); + auto attr = op.getValue(); + if (const auto intAttr = dyn_cast(attr)) { + ut->propagateIntAlloc(res, intAttr.getInt()); + } + if (const auto doubleAttr = dyn_cast(attr)) { + ut->propagateDoubleAlloc(res, doubleAttr.getValueAsDouble()); + } + if (const auto boolAttr = dyn_cast(attr)) { + const bool v = boolAttr.getValue(); + if (v) { + ut->propagateIntAlloc(res, 1); + } else { + ut->propagateIntAlloc(res, 0); + } + } + return WalkResult::advance(); +} + +WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, + const std::span ctrlsQuantum, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist) { + if (isa(op) || isa(op) || isa(op) || isa(op) || + isa(op) || isa(op)) { + const auto addedGlobalPhase = + ut->globalPhaseThatIsAdded(*op, op->getInputQubit(0), ctrlsQuantum, + posClassicalCtrls, negClassicalCtrls); + if (addedGlobalPhase.has_value()) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + const auto phase = addedGlobalPhase.value(); + if (std::norm(phase) > 1e-4) { + GPhaseOp::create(rewriter, op->getLoc(), phase); + } + removeOperation(op, rewriter); + } + } + + const auto targets = op->getInputTargets(); + const auto results = op->getOutputTargets(); + std::vector targetValues = {targets.begin(), targets.end()}; + std::vector resultValues = {results.begin(), results.end()}; + ut->propagateGate(*op, targetValues, resultValues, ctrlsQuantum, + posClassicalCtrls, negClassicalCtrls); + + return WalkResult::advance(); +} + LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, - std::span worklist, - const std::span quantumCtrls, + std::span& worklist, + const std::span ctrlsQuantum, const std::span posClassicalCtrls, const std::span negClassicalCtrls) { /// Iterate work-list. @@ -74,93 +140,121 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, if (curr == nullptr) { continue; // Skip erased ops. } + auto n = curr->getName().stripDialect().str(); + std::string oName = + "Op: " + curr->getName().getStringRef().str() + + " dialect: " + curr->getName().getDialectNamespace().str(); rewriter.setInsertionPoint(curr); - // const auto res = - // TypeSwitch(curr) - // /// mqtopt Dialect - // .Case([&](const UnitaryOpInterface op) { - // return handleUnitary(ut, op, posClassicalCtrls, - // negClassicalCtrls, - // rewriter); - // }) - // .Case([&](const ResetOp op) { - // return handleReset(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) - // .Case([&](const MeasureOp op) { - // return handleMeasure(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) - // .Case([&](const AllocOp op) { - // addedAtLeastOneQubit = true; - // return handleQubitAlloc(ut, op); - // }) - // .Case( - // [&](const StaticOp op) { return handleStaticOp(ut, op); }) - // .Case([&]([[maybe_unused]] SinkOp op) { - // return WalkResult::advance(); - // }) - // /// built-in Dialect - // .Case([&]([[maybe_unused]] ModuleOp op) { - // return WalkResult::advance(); - // }) - // /// memref Dialect - // .Case( - // [&](const memref::AllocOp op) { return handleAlloc(ut, op); - // }) - // .Case([&](const memref::AllocaOp op) { - // return handleAlloca(ut, op); - // }) - // .Case( - // [&]([[maybe_unused]] const memref::DeallocOp op) { - // return WalkResult::advance(); - // }) - // .Case([&](const memref::LoadOp op) { - // addedAtLeastOneQubit = true; - // return handleLoad(ut, op); - // }) - // .Case([&](const memref::StoreOp op) { - // return handleStore(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) - // // arith dialect - // .Case([&](const arith::ConstantOp op) { - // return handleConstant(qcp, op, posClassicalCtrls, - // negClassicalCtrls); - // }) - // .Case( - // [&](const arith::XOrIOp op) { return handleXOrIOp(qcp, op); - // }) - // .Case( - // [&](const arith::AndIOp op) { return handleAndIOp(qcp, op); - // }) - // /// func Dialect - // .Case([&](const func::FuncOp op) { - // return handleFunc(qcp, op, rewriter); - // }) - // .Case([&]([[maybe_unused]] func::ReturnOp op) { - // return WalkResult::advance(); - // }) - // /// scf Dialect - // .Case([&](scf::ForOp) { return handleFor(); }) - // .Case([&](const scf::IfOp op) { - // return handleIf(qcp, op, worklist, posClassicalCtrls, - // negClassicalCtrls, rewriter); - // }) - // .Case([&]([[maybe_unused]] scf::YieldOp op) { - // return WalkResult::advance(); - // }) - // /// Skip the rest. - // .Default([](auto) { - // throw std::runtime_error("Unsupported operation"); - // return WalkResult::interrupt(); - // }); - - // if (res.wasInterrupted()) { - // return failure(); - // } + const auto res = + TypeSwitch(curr) + /// qco Dialect + .Case([&](UnitaryOpInterface op) { + return handleUnitary(ut, &op, {}, posClassicalCtrls, + negClassicalCtrls, rewriter, worklist); + }) + // .Case([&](const ResetOp op) { + // return handleReset(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // .Case([&](const MeasureOp op) { + // return handleMeasure(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + .Case([&](const AllocOp op) { + addedAtLeastOneQubit = true; + ut->propagateQubitAlloc(op->getOperand(0)); + return WalkResult::advance(); + }) + // .Case( + // [&](const StaticOp op) { return handleStaticOp(ut, + // op); }) + .Case([&]([[maybe_unused]] SinkOp op) { + return WalkResult::advance(); + }) + // .Case([&](const IfOp op) { + // return handleIf(qcp, op, worklist, posClassicalCtrls, + // negClassicalCtrls, rewriter); + // }) + // .Case([&]([[maybe_unused]] YieldOp op) { + // return WalkResult::advance(); + // }) + // /// built-in Dialect + // .Case([&]([[maybe_unused]] ModuleOp op) { + // return WalkResult::advance(); + // }) + // qtensor dialect + .Case([&]([[maybe_unused]] qtensor::AllocOp op) { + return WalkResult::advance(); + }) + .Case( + [&]([[maybe_unused]] qtensor::DeallocOp op) { + return WalkResult::advance(); + }) + .Case([&](const qtensor::ExtractOp op) { + addedAtLeastOneQubit = true; + ut->propagateQubitAlloc(op->getResult(1)); + return WalkResult::advance(); + }) + .Case( + [&]([[maybe_unused]] qtensor::InsertOp op) { + return WalkResult::advance(); + }) + // memref Dialect + // .Case([&](const memref::AllocOp op) { + // addedAtLeastOneQubit = true; + // ut->propagateQubitAlloc(op->getOpResult(0)); + // return WalkResult::advance(); + // }) + // .Case([&](const memref::AllocaOp op) { + // return handleAlloca(ut, op); + // }) + // .Case( + // [&]([[maybe_unused]] const memref::DeallocOp op) { + // return WalkResult::advance(); + // }) + // .Case([&](const memref::LoadOp op) { + // addedAtLeastOneQubit = true; + // return handleLoad(ut, op); + // }) + // .Case([&](const memref::StoreOp op) { + // return handleStore(ut, op, posClassicalCtrls, + // negClassicalCtrls); + // }) + // arith dialect + .Case([&](const arith::ConstantOp op) { + return handleConstant(ut, op, posClassicalCtrls, + negClassicalCtrls); + }) + // .Case( + // [&](const arith::XOrIOp op) { return + // handleXOrIOp(qcp, op); + // }) + // .Case( + // [&](const arith::AndIOp op) { return + // handleAndIOp(qcp, op); + // }) + // func Dialect + .Case([&](const func::FuncOp op) { + if (!isEntryPoint(op)) { + throw std::domain_error( + "Constant propagation does not support nested functions."); + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }) + .Case([&]([[maybe_unused]] func::ReturnOp op) { + return WalkResult::advance(); + }) + .Default([](auto) { + throw std::runtime_error("Unsupported operation"); + return WalkResult::interrupt(); + }); + + if (res.wasInterrupted()) { + return failure(); + } } return success(); } @@ -204,7 +298,9 @@ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { // TODO: Take maximum from params auto ut = UnionTable(16, 4); - return iterateThroughWorklist(rewriter, &ut, worklist, {}, {}, {}); + std::span wl = {worklist.begin(), worklist.end()}; + + return iterateThroughWorklist(rewriter, &ut, wl, {}, {}, {}); } /** diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 2443a71555..4a579b51f6 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -61,15 +61,6 @@ class QCOConstantPropagationTest : public testing::Test { pm.addPass(createConstantPropagation()); return pm.run(module); } - - /** - * @brief Adds the canonicalizerPass to the current context and runs it. - */ - static LogicalResult runCanonicalizerPass(ModuleOp module) { - PassManager pm(module.getContext()); - pm.addPass(createCanonicalizerPass()); - return pm.run(module); - } }; } // namespace @@ -109,7 +100,6 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -131,7 +121,6 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -166,7 +155,6 @@ TEST_F(QCOConstantPropagationTest, testRemoveImpliedQubits) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -193,7 +181,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -223,7 +210,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -258,7 +244,6 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsZero) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -291,7 +276,6 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsOne) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -333,7 +317,6 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveClassicalConditional) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -363,7 +346,6 @@ TEST_F(QCOConstantPropagationTest, reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -403,7 +385,6 @@ TEST_F(QCOConstantPropagationTest, testEquivalentClassicalAndQuantumControl) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -449,7 +430,6 @@ TEST_F(QCOConstantPropagationTest, testClassicalImpliesQuantum) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -476,7 +456,6 @@ TEST_F(QCOConstantPropagationTest, testReplaceSingleQubitPhaseGatePlusOne) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -498,11 +477,10 @@ TEST_F(QCOConstantPropagationTest, testReplaceSingleQubitPhaseGateMinusOne) { qRef[0] = referenceBuilder.h(qRef[0]); qRef[0] = referenceBuilder.z(qRef[0]); qRef[0] = referenceBuilder.h(qRef[0]); - referenceBuilder.gphase(-1.0); + referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -523,7 +501,6 @@ TEST_F(QCOConstantPropagationTest, testRemoveMultiQubitPhaseGatePlusOne) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -543,11 +520,10 @@ TEST_F(QCOConstantPropagationTest, testRemoveMultiQubitPhaseGateMinusOne) { auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.x(qRef[0]); referenceBuilder.cx(qRef[0], qRef[1]); - referenceBuilder.gphase(-1.0); + referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -573,7 +549,6 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveMultiQubitPhaseGate) { reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); From d4690b1db07cffe2db2115cb8a89ab308eea217e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 19:07:03 +0200 Subject: [PATCH 101/131] :construction: Ctrl qubits changed after gate --- .../ConstantPropagation/HybridState.hpp | 5 - .../ConstantPropagation/UnionTable.hpp | 4 +- .../ConstantPropagation/UnionTable.cpp | 2 + .../ConstantPropagation/test_unionTable.cpp | 272 ++++++++++-------- 4 files changed, 155 insertions(+), 128 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp index 07d5767dce..853b65bae7 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -18,14 +18,9 @@ #include #include -#include -#include #include -#include -#include #include #include -#include #include namespace mlir::qco { diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 769dd7dff6..36fb11b212 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -17,11 +17,9 @@ #include #include -#include #include #include #include -#include #include #include #include @@ -327,6 +325,7 @@ class UnionTable { * @param targets An array of the Values of the target qubits. * @param newQuantumTargets The value of the qubits after the gate. * @param ctrlsQuantum An array of the values of the ctrl qubits. + * @param newCtrlsQuantum An values of the ctrl qubits after the gate. * @param posCtrlsClassical An array of the values of the ctrl bits. * @param negCtrlsClassical An array of the values of the negative ctrl bits. * @param params The parameter applied to the gate. @@ -336,6 +335,7 @@ class UnionTable { void propagateGate(Operation* gate, std::span targets, std::span newQuantumTargets, std::span ctrlsQuantum = {}, + std::span newCtrlsQuantum = {}, std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}, std::span params = {}); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 3602b4e415..09c1d02107 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -82,6 +82,7 @@ bool UnionTable::areStatesAllTop() { void UnionTable::propagateGate(Operation* gate, const std::span targets, const std::span newQuantumTargets, const std::span ctrlsQuantum, + const std::span newCtrlsQuantum, const std::span posCtrlsClassical, const std::span negCtrlsClassical, const std::span params) { @@ -128,6 +129,7 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, } } replaceValuesGlobally(targets, newQuantumTargets); + replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); } void UnionTable::propagateMeasurement( diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 1ad57c4468..0263df5eb4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -41,6 +41,7 @@ class UnionTableTest : public testing::Test { mlir::Value v7; mlir::Value v8; mlir::Value v9; + mlir::Value v10; mlir::Value i0; mlir::Value i1; mlir::Value i2; @@ -55,6 +56,7 @@ class UnionTableTest : public testing::Test { std::vector q7; std::vector q8; std::vector q9; + std::vector q10; UnionTableTest() : programBuilder(&context) {} @@ -67,7 +69,7 @@ class UnionTableTest : public testing::Test { programBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(10); + auto q = programBuilder.allocQubitRegister(11); hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), @@ -85,6 +87,7 @@ class UnionTableTest : public testing::Test { v7 = q[7]; v8 = q[8]; v9 = q[9]; + v10 = q[10]; q0 = {v0}; q1 = {v1}; @@ -96,6 +99,7 @@ class UnionTableTest : public testing::Test { q7 = {v7}; q8 = {v8}; q9 = {v9}; + q10 = {v10}; const auto iAttr = programBuilder.getI64IntegerAttr(0); @@ -136,7 +140,7 @@ TEST_F(UnionTableTest, ApplyHGateToThirdQubit) { TEST_F(UnionTableTest, ApplyQuantumControlledGate) { ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5); + ut.propagateGate(xOp, q6, q7, q5, q8); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -155,7 +159,7 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, q8, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -169,7 +173,7 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, q8, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -183,7 +187,7 @@ TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, q8, {}, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -197,7 +201,7 @@ TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, {}, classicalControl); + ut.propagateGate(xOp, q6, q7, q5, q8, {}, classicalControl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -212,7 +216,8 @@ TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsFalse) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, classicalControlOne, classicalControlZero); + ut.propagateGate(xOp, q6, q7, q5, q8, classicalControlOne, + classicalControlZero); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 31, HybridStates: {{|10> " @@ -227,7 +232,7 @@ TEST_F(UnionTableTest, ApplyPosNegClassicalControlledGateThatsTrue) { ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); ut.propagateGate(xOp, q3, q6); - ut.propagateGate(xOp, q6, q7, q5, classicalControlTrue, + ut.propagateGate(xOp, q6, q7, q5, q8, classicalControlTrue, classicalControlFalse); EXPECT_THAT( @@ -249,7 +254,7 @@ TEST_F(UnionTableTest, ApplyControlledTwoBitGate) { ut.propagateIntAlloc(i2, 1); ut.propagateGate(hOp, q1, q4); ut.propagateGate(xOp, q3, q5); - ut.propagateGate(xOp, q5, q6, q4, classicalControlTrue, + ut.propagateGate(xOp, q5, q6, q4, q7, classicalControlTrue, classicalControlFalse); EXPECT_THAT(ut.toString(), @@ -269,9 +274,9 @@ TEST_F(UnionTableTest, doMeasurementWithOneResult) { TEST_F(UnionTableTest, doMeasurementWithTwoResults) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q1, q5, q4, q6); ut.propagateIntAlloc(i0, 10); - ut.propagateMeasurement(v5, v6, i0); + ut.propagateMeasurement(v5, v7, i0); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {{|00> " @@ -282,9 +287,9 @@ TEST_F(UnionTableTest, doMeasurementWithTwoResults) { TEST_F(UnionTableTest, doMeasurementWithNegPosCtrl) { std::vector ctrl = {i0}; ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q1, q5, q4, q6); ut.propagateIntAlloc(i0, 0); - ut.propagateMeasurement(v5, v6, i0, ctrl); + ut.propagateMeasurement(v5, v7, i0, ctrl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -295,9 +300,9 @@ TEST_F(UnionTableTest, doMeasurementWithNegPosCtrl) { TEST_F(UnionTableTest, doMeasurementWithPosNegCtrl) { std::vector ctrl = {i0}; ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q1, q5, q4, q6); ut.propagateIntAlloc(i0, 10); - ut.propagateMeasurement(v5, v6, i0, {}, ctrl); + ut.propagateMeasurement(v5, v7, i0, {}, ctrl); EXPECT_THAT(ut.toString(), testing::HasSubstr( @@ -316,8 +321,8 @@ TEST_F(UnionTableTest, doResetWithOneResult) { TEST_F(UnionTableTest, doResetWithTwoResults) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateReset(v4, v6); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateReset(v6, v7); EXPECT_THAT( ut.toString(), @@ -326,13 +331,13 @@ TEST_F(UnionTableTest, doResetWithTwoResults) { } TEST_F(UnionTableTest, swapGateApplicationDifferentStates) { - std::vector swapTargets = {v6, v2}; - std::vector swapDestinations = {v7, v8}; + std::vector swapTargets = {v7, v2}; + std::vector swapDestinations = {v8, v9}; ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateGate(xOp, q5, q6); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateGate(xOp, q5, q7); ut.propagateGate(swapOp, swapTargets, swapDestinations); - ut.propagateGate(xOp, q7, q9); + ut.propagateGate(xOp, q8, q10); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 20, HybridStates: {{|01> -> 0.71, " @@ -343,11 +348,11 @@ TEST_F(UnionTableTest, swapGateApplicationDifferentStates) { } TEST_F(UnionTableTest, swapGateApplicationSameState) { - std::vector swapTargets = {v4, v6}; - std::vector swapDestinations = {v7, v8}; + std::vector swapTargets = {v6, v7}; + std::vector swapDestinations = {v8, v9}; ut.propagateGate(hOp, q0, q4); - ut.propagateGate(hOp, q1, q5, q4); - ut.propagateGate(xOp, q5, q6); + ut.propagateGate(hOp, q1, q5, q4, q6); + ut.propagateGate(xOp, q5, q7); ut.propagateGate(swapOp, swapTargets, swapDestinations); EXPECT_THAT(ut.toString(), @@ -370,6 +375,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { mlir::Value v3; mlir::Value v4; mlir::Value v5; + mlir::Value v6; mlir::Value i0; std::vector q0; @@ -378,6 +384,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { std::vector q3; std::vector q4; std::vector q5; + std::vector q6; UnionTableWithoutSetupAllocationsTest() : programBuilder(&context), referenceBuilder(&context) {} @@ -392,7 +399,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { programBuilder.initialize(); referenceBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(6); + auto q = programBuilder.allocQubitRegister(7); hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), @@ -403,6 +410,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { v3 = q[3]; v4 = q[4]; v5 = q[5]; + v6 = q[6]; q0 = {v0}; q1 = {v1}; @@ -410,6 +418,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { q3 = {v3}; q4 = {v4}; q5 = {v5}; + q6 = {v6}; const auto iAttr = programBuilder.getI64IntegerAttr(0); @@ -451,9 +460,9 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsOnTop) { ut.propagateQubitAlloc(v1); ut.propagateIntAlloc(i0, 10); ut.propagateGate(hOp, q0, q2); - ut.propagateGate(xOp, q1, q3, q2); - ut.propagateGate(hOp, q3, q4); // State enters TOP - ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(xOp, q1, q3, q2, q4); + ut.propagateGate(hOp, q3, q5); // State enters TOP + ut.propagateMeasurement(v5, v6, i0); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); @@ -464,9 +473,9 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, doResetOnTop) { ut.propagateQubitAlloc(v0); ut.propagateQubitAlloc(v1); ut.propagateGate(hOp, q0, q2); - ut.propagateGate(xOp, q1, q3, q2); - ut.propagateGate(hOp, q3, q4); // State enters TOP - ut.propagateReset(v4, v5); + ut.propagateGate(xOp, q1, q3, q2, q4); + ut.propagateGate(hOp, q3, q5); // State enters TOP + ut.propagateReset(v5, v6); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); @@ -480,7 +489,7 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, unifyTooLargeHybridStates) { ut.propagateIntAlloc(i0, 10); ut.propagateGate(hOp, q0, q3); ut.propagateMeasurement(v3, v4, i0); - ut.propagateGate(xOp, q1, q5, q4); + ut.propagateGate(xOp, q1, q5, q4, q6); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); @@ -508,6 +517,10 @@ class UnionTablePropertiesTest : public testing::Test { mlir::Value v8; mlir::Value v9; mlir::Value v10; + mlir::Value v11; + mlir::Value v12; + mlir::Value v13; + mlir::Value v14; mlir::Value i0; mlir::Value i1; @@ -522,6 +535,10 @@ class UnionTablePropertiesTest : public testing::Test { std::vector q8; std::vector q9; std::vector q10; + std::vector q11; + std::vector q12; + std::vector q13; + std::vector q14; UnionTablePropertiesTest() : programBuilder(&context) {} @@ -534,7 +551,7 @@ class UnionTablePropertiesTest : public testing::Test { programBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(11); + auto q = programBuilder.allocQubitRegister(15); hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), @@ -555,6 +572,10 @@ class UnionTablePropertiesTest : public testing::Test { v8 = q[8]; v9 = q[9]; v10 = q[10]; + v11 = q[11]; + v12 = q[12]; + v13 = q[13]; + v14 = q[14]; q0 = {v0}; q1 = {v1}; @@ -567,6 +588,10 @@ class UnionTablePropertiesTest : public testing::Test { q8 = {v8}; q9 = {v9}; q10 = {v10}; + q11 = {v11}; + q12 = {v12}; + q13 = {v13}; + q14 = {v14}; const auto iAttr = programBuilder.getI64IntegerAttr(0); @@ -587,52 +612,53 @@ class UnionTablePropertiesTest : public testing::Test { TEST_F(UnionTablePropertiesTest, alwaysZeroOneAreFalse) { std::vector ctrl = {i0}; ut.propagateGate(hOp, q0, q3); - ut.propagateGate(xOp, q1, q4, q3); - ut.propagateGate(xOp, q2, q5, q4); - ut.propagateGate(xOp, q3, q6); - ut.propagateMeasurement(v4, v7, i0); - ut.propagateGate(hOp, q6, q8, {}, ctrl); - - EXPECT_FALSE(ut.isQubitAlwaysZero(v5)); - EXPECT_FALSE(ut.isQubitAlwaysOne(v8)); + ut.propagateGate(xOp, q1, q4, q3, q5); + ut.propagateGate(xOp, q2, q6, q4, q7); + ut.propagateGate(xOp, q5, q8); + ut.propagateMeasurement(v7, v9, i0); + ut.propagateGate(hOp, q8, q10, {}, {}, ctrl); + + EXPECT_FALSE(ut.isQubitAlwaysZero(v6)); + EXPECT_FALSE(ut.isQubitAlwaysOne(v10)); } TEST_F(UnionTablePropertiesTest, alwaysZeroIsTrue) { std::vector ctrl = {i0}; ut.propagateGate(hOp, q0, q3); - ut.propagateGate(xOp, q1, q4, q3); - ut.propagateGate(xOp, q2, q5, q4); - ut.propagateGate(xOp, q3, q6); - ut.propagateMeasurement(v4, v7, i0); - ut.propagateGate(xOp, q5, q8, {}, ctrl); - ut.propagateGate(hOp, q6, q9, {}, ctrl); - - EXPECT_TRUE(ut.isQubitAlwaysZero(v8)); + ut.propagateGate(xOp, q1, q4, q3, q5); + ut.propagateGate(xOp, q2, q6, q4, q7); + ut.propagateGate(xOp, q5, q8); + ut.propagateMeasurement(v7, v9, i0); + ut.propagateGate(xOp, q6, q10, {}, {}, ctrl); + ut.propagateGate(hOp, q8, q11, {}, {}, ctrl); + + EXPECT_TRUE(ut.isQubitAlwaysZero(v10)); } TEST_F(UnionTablePropertiesTest, alwaysOneIsTrue) { std::vector ctrl = {i0}; - std::vector qCtrl = {v7, v4}; + std::vector qCtrl = {v7, v9}; + std::vector qCtrlNew = {v12, v13}; ut.propagateGate(hOp, q0, q3); - ut.propagateGate(xOp, q1, q4, q3); - ut.propagateGate(xOp, q2, q5, q4); - ut.propagateGate(xOp, q3, q6); - ut.propagateMeasurement(v5, v7, i0); - ut.propagateGate(hOp, q6, q8, {}, ctrl); - ut.propagateGate(zOp, q8, q9, qCtrl); - ut.propagateGate(hOp, q9, q10, {}, ctrl); - - EXPECT_TRUE(ut.isQubitAlwaysOne(v10)); + ut.propagateGate(xOp, q1, q4, q3, q5); + ut.propagateGate(xOp, q2, q6, q4, q7); + ut.propagateGate(xOp, q5, q8); + ut.propagateMeasurement(v6, v9, i0); + ut.propagateGate(hOp, q8, q10, {}, {}, ctrl); + ut.propagateGate(zOp, q10, q11, qCtrl, qCtrlNew); + ut.propagateGate(hOp, q11, q14, {}, {}, ctrl); + + EXPECT_TRUE(ut.isQubitAlwaysOne(v14)); } TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsTrueOneIsFalse) { std::vector ctrl = {i0}; ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateMeasurement(v4, v6, i0); - ut.propagateGate(xOp, q5, q7, {}, ctrl); - ut.propagateMeasurement(v7, v8, i1); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateMeasurement(v6, v7, i0); + ut.propagateGate(xOp, q5, q8, {}, {}, ctrl); + ut.propagateMeasurement(v8, v9, i1); EXPECT_FALSE(ut.isClassicalValueAlwaysTrue(i0)); EXPECT_TRUE(ut.isClassicalValueAlwaysFalse(i1)); @@ -642,10 +668,10 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { std::vector ctrl = {i0}; ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateMeasurement(v4, v6, i0); - ut.propagateGate(xOp, q5, q7, {}, {}, ctrl); - ut.propagateMeasurement(v7, v8, i1); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateMeasurement(v6, v7, i0); + ut.propagateGate(xOp, q5, q8, {}, {}, {}, ctrl); + ut.propagateMeasurement(v8, v9, i1); EXPECT_TRUE(ut.isClassicalValueAlwaysTrue(i1)); EXPECT_FALSE(ut.isClassicalValueAlwaysFalse(i0)); @@ -653,25 +679,25 @@ TEST_F(UnionTablePropertiesTest, bitAlwaysZeroIsFalseOneIsTrue) { TEST_F(UnionTablePropertiesTest, testAllTopAmplitudes) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateGate(xOp, q2, q6, q5); - ut.propagateMeasurement(v6, v7, i0); - ut.propagateGate(hOp, q4, q8); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateGate(xOp, q2, q7, q5, q8); + ut.propagateMeasurement(v7, v9, i0); + ut.propagateGate(hOp, q6, q10); EXPECT_FALSE(ut.areStatesAllTop()); - ut.propagateGate(hOp, q5, q9); + ut.propagateGate(hOp, q8, q11); EXPECT_TRUE(ut.areStatesAllTop()); - ut.propagateMeasurement(v8, v10, i0); + ut.propagateMeasurement(v10, v12, i0); EXPECT_TRUE(ut.areStatesAllTop()); } TEST_F(UnionTablePropertiesTest, testAllTopHybridStates) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateGate(xOp, q2, q6, q5); - ut.propagateMeasurement(v6, v7, i0); - ut.propagateGate(hOp, q4, q8); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateGate(xOp, q2, q7, q5, q8); + ut.propagateMeasurement(v7, v9, i0); + ut.propagateGate(hOp, q6, q10); EXPECT_FALSE(ut.areStatesAllTop()); - ut.propagateMeasurement(v8, v10, i0); + ut.propagateMeasurement(v10, v11, i0); EXPECT_TRUE(ut.areStatesAllTop()); } @@ -698,32 +724,32 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateMeasurement(v5, v6, i0); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateMeasurement(v5, v7, i0); const llvm::DenseMap result = - ut.getValueThatIsEquivalentToQubit(v6); + ut.getValueThatIsEquivalentToQubit(v7); ASSERT_FALSE(result.empty()); ASSERT_TRUE(result.at(i0)); } TEST_F(UnionTablePropertiesTest, FindEquivalentReversedClassicalValue) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateGate(xOp, q4, q6); - ut.propagateMeasurement(v5, v7, i0); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateGate(xOp, q6, q7); + ut.propagateMeasurement(v5, v8, i0); const llvm::DenseMap result = - ut.getValueThatIsEquivalentToQubit(v6); + ut.getValueThatIsEquivalentToQubit(v7); ASSERT_FALSE(result.empty()); ASSERT_FALSE(result.at(i0)); } TEST_F(UnionTablePropertiesTest, FindNoEquivalentClassicalValue) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateGate(hOp, q4, q6); - ut.propagateMeasurement(v5, v7, i0); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateGate(hOp, q6, q7); + ut.propagateMeasurement(v5, v8, i0); const llvm::DenseMap result = - ut.getValueThatIsEquivalentToQubit(v6); + ut.getValueThatIsEquivalentToQubit(v7); ASSERT_TRUE(result.empty()); } @@ -731,20 +757,20 @@ TEST_F(UnionTablePropertiesTest, hasAlwaysZeroProbabilityTest) { std::vector classicalIndexVec = {i0}; ut.propagateIntAlloc(i1, 20); ut.propagateGate(hOp, q0, q3); - ut.propagateGate(xOp, q1, q4, q3); - ut.propagateMeasurement(v4, v5, i0); + ut.propagateGate(xOp, q1, q4, q3, q5); + ut.propagateMeasurement(v4, v6, i0); llvm::DenseMap qubits0; llvm::DenseMap classicals0; llvm::DenseMap qubits1; llvm::DenseMap classicals1; - qubits0[v3] = true; qubits0[v5] = true; + qubits0[v6] = true; qubits0[v2] = false; classicals0[i0] = true; classicals0[i1] = true; - qubits1[v3] = false; qubits1[v5] = false; + qubits1[v6] = false; qubits1[v2] = true; classicals1[i1] = false; @@ -763,23 +789,23 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubit) { std::vector classicalIndexVec = {i0}; ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(hOp, q1, q6, q5); - ASSERT_TRUE(ut.isQubitImplied(v5, q6, classicalIndexVec, {})); - ASSERT_FALSE(ut.isQubitImplied(v6, q5, classicalIndexVec, {})); + ut.propagateGate(hOp, q1, q6, q5, q7); + ASSERT_TRUE(ut.isQubitImplied(v7, q6, classicalIndexVec, {})); + ASSERT_FALSE(ut.isQubitImplied(v6, q7, classicalIndexVec, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyQubits) { ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(hOp, q1, q6, q5); - ASSERT_TRUE(ut.isQubitImplied(v5, q6, {}, {})); + ut.propagateGate(hOp, q1, q6, q5, q7); + ASSERT_TRUE(ut.isQubitImplied(v7, q6, {}, {})); } TEST_F(UnionTablePropertiesTest, ImpliedQubitOnlyClassicalValues) { std::vector classicalIndexVec = {i0}; ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(hOp, q5, q6, {}, {}, classicalIndexVec); + ut.propagateGate(hOp, q5, q6, {}, {}, {}, classicalIndexVec); ASSERT_TRUE(ut.isQubitImplied(v6, {}, classicalIndexVec, {})); } @@ -787,7 +813,7 @@ TEST_F(UnionTablePropertiesTest, ImpliedQubitNegClassicalValues) { std::vector classicalIndexVec = {i0}; ut.propagateGate(hOp, q0, q4); ut.propagateMeasurement(v4, v5, i0); - ut.propagateGate(hOp, q5, q6, {}, classicalIndexVec); + ut.propagateGate(hOp, q5, q6, {}, {}, classicalIndexVec); ut.propagateGate(xOp, q6, q7); ASSERT_TRUE(ut.isQubitImplied(v7, {}, {}, classicalIndexVec)); } @@ -815,10 +841,10 @@ TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsA) { TEST_F(UnionTablePropertiesTest, noGlobalPhaseTwoQubitsB) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(hOp, q1, q5, q4); - ut.propagateMeasurement(v5, v6, i0); + ut.propagateGate(hOp, q1, q5, q4, q6); + ut.propagateMeasurement(v5, v7, i0); - const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v4, q6); + const auto globalPhase0 = ut.globalPhaseThatIsAdded(zOp, v6, q7); ASSERT_FALSE(globalPhase0.has_value()); } @@ -841,16 +867,16 @@ TEST_F(UnionTablePropertiesTest, globalPhaseTwoQubits) { TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsA) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5); - ut.propagateGate(xOp, q5, q6, q4); - std::vector combinations = {v4, v6}; + ut.propagateGate(xOp, q5, q6, q4, q7); + std::vector combinations = {v6, v7}; ASSERT_FALSE(ut.areThereSatisfiableCombinations(combinations)); } TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsB) { ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - std::vector combinations = {v4, v5}; + ut.propagateGate(xOp, q1, q5, q4, q6); + std::vector combinations = {v5, v6}; ASSERT_TRUE(ut.areThereSatisfiableCombinations(combinations)); } @@ -858,13 +884,13 @@ TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsB) { TEST_F(UnionTablePropertiesTest, findNonSatisfiableCombinationsC) { ut.propagateIntAlloc(i1, 2); ut.propagateGate(hOp, q0, q4); - ut.propagateGate(xOp, q1, q5, q4); - ut.propagateMeasurement(v4, v6, i0); - ut.propagateMeasurement(v5, v7, i1); - ut.propagateGate(hOp, q6, q8); - ut.propagateGate(hOp, q7, q9, q8); + ut.propagateGate(xOp, q1, q5, q4, q6); + ut.propagateMeasurement(v6, v7, i0); + ut.propagateMeasurement(v5, v8, i1); + ut.propagateGate(hOp, q7, q9); + ut.propagateGate(hOp, q8, q10, q9, q11); - std::vector qubitCombinations = {v8, v9}; + std::vector qubitCombinations = {v10, v11}; std::vector classicalCombinations = {i0, i1}; std::vector classicalVal0 = {i0}; std::vector classicalVal1 = {i1}; @@ -894,6 +920,7 @@ class SmallUnionTableTest : public testing::Test { mlir::Value v5; mlir::Value v6; mlir::Value v7; + mlir::Value v8; std::vector q0; std::vector q1; @@ -903,6 +930,7 @@ class SmallUnionTableTest : public testing::Test { std::vector q5; std::vector q6; std::vector q7; + std::vector q8; SmallUnionTableTest() : programBuilder(&context) {} @@ -915,7 +943,7 @@ class SmallUnionTableTest : public testing::Test { programBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(8); + auto q = programBuilder.allocQubitRegister(9); hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), @@ -929,6 +957,7 @@ class SmallUnionTableTest : public testing::Test { v5 = q[5]; v6 = q[6]; v7 = q[7]; + v8 = q[8]; q0 = {v0}; q1 = {v1}; @@ -938,6 +967,7 @@ class SmallUnionTableTest : public testing::Test { q5 = {v5}; q6 = {v6}; q7 = {v7}; + q8 = {v8}; ut.propagateQubitAlloc(v0); ut.propagateQubitAlloc(v1); @@ -948,9 +978,9 @@ class SmallUnionTableTest : public testing::Test { TEST_F(SmallUnionTableTest, handleErrorIfTwoManyAmplitudesAreNonzero) { ut.propagateGate(hOp, q3, q4); - ut.propagateGate(xOp, q2, q5, q4); - ut.propagateGate(hOp, q5, q6); - ut.propagateGate(hOp, q6, q7); + ut.propagateGate(xOp, q2, q5, q4, q6); + ut.propagateGate(hOp, q5, q7); + ut.propagateGate(hOp, q7, q8); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 32, HybridStates: {TOP}")); @@ -959,8 +989,8 @@ TEST_F(SmallUnionTableTest, handleErrorIfTwoManyAmplitudesAreNonzero) { TEST_F(SmallUnionTableTest, applyGatesOnPartiallyTopQState) { ut.propagateGate(hOp, q2, q4); ut.propagateGate(hOp, q3, q5); - ut.propagateGate(xOp, q4, q6, q5); // Qubit 2 and 3 enter TOP - ut.propagateGate(xOp, q1, q7, q6); + ut.propagateGate(xOp, q4, q6, q5, q7); // Qubit 2 and 3 enter TOP + ut.propagateGate(xOp, q1, q8, q6); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 321, HybridStates: {TOP}")); From 32dde575e440a4e9fb44bf10770d0e67fb941ece Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 19:16:06 +0200 Subject: [PATCH 102/131] :construction: Start of ctrl handling --- .../Optimizations/ConstantPropagation.cpp | 30 ++++++++++++++++--- .../test_qco_constant_propagation.cpp | 1 - 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index a585f0c858..9622b12750 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -97,6 +97,7 @@ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, const std::span ctrlsQuantum, + const std::span newCtrlsQuantum, const std::span posClassicalCtrls, const std::span negClassicalCtrls, PatternRewriter& rewriter, @@ -118,17 +119,38 @@ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, const auto targets = op->getInputTargets(); const auto results = op->getOutputTargets(); + const auto params = op->getParameters(); std::vector targetValues = {targets.begin(), targets.end()}; std::vector resultValues = {results.begin(), results.end()}; + std::vector paramValues = {params.begin(), params.end()}; ut->propagateGate(*op, targetValues, resultValues, ctrlsQuantum, - posClassicalCtrls, negClassicalCtrls); + newCtrlsQuantum, posClassicalCtrls, negClassicalCtrls, + paramValues); return WalkResult::advance(); } +WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist) { + const auto inputCtrs = op->getInputControls(); + std::vector inCtrlValues = {inputCtrs.begin(), inputCtrs.end()}; + // TODO: Check if gate is executable + // TODO: Work on the right qubits in body + + const auto outputCtrls = op->getOutputControls(); + std::vector outCtrlValues = {outputCtrls.begin(), outputCtrls.end()}; + + auto body = op->getBodyUnitary(); + return handleUnitary(ut, &body, inCtrlValues, outCtrlValues, + posClassicalCtrls, negClassicalCtrls, rewriter, + worklist); +} + LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, std::span& worklist, - const std::span ctrlsQuantum, const std::span posClassicalCtrls, const std::span negClassicalCtrls) { /// Iterate work-list. @@ -151,7 +173,7 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, TypeSwitch(curr) /// qco Dialect .Case([&](UnitaryOpInterface op) { - return handleUnitary(ut, &op, {}, posClassicalCtrls, + return handleUnitary(ut, &op, {}, {}, posClassicalCtrls, negClassicalCtrls, rewriter, worklist); }) // .Case([&](const ResetOp op) { @@ -300,7 +322,7 @@ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { std::span wl = {worklist.begin(), worklist.end()}; - return iterateThroughWorklist(rewriter, &ut, wl, {}, {}, {}); + return iterateThroughWorklist(rewriter, &ut, wl, {}, {}); } /** diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 4a579b51f6..7671800706 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include From 4e8463d99e0f8c3229394cd8fa4b47b180a88fac Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 09:38:34 +0200 Subject: [PATCH 103/131] :construction: Handling of ctrl continued --- .../Optimizations/ConstantPropagation.cpp | 127 +++++++++++++++--- 1 file changed, 106 insertions(+), 21 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 9622b12750..ba401ebe0c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -19,6 +19,8 @@ #include #include +#include + namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION @@ -68,6 +70,17 @@ void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { rewriter.eraseOp(*op); } +void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter, + std::span& worklist) { + op->walk([&](Operation* bodyOp) { + std::ranges::replace(worklist, bodyOp, static_cast(nullptr)); + }); + for (const auto outQubit : op->getOutputQubits()) { + rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); + } + rewriter.eraseOp(*op); +} + WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, const std::span posClassicalCtrls, const std::span negClassicalCtrls) { @@ -95,37 +108,67 @@ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, return WalkResult::advance(); } -WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, +bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, const std::span ctrlsQuantum, - const std::span newCtrlsQuantum, const std::span posClassicalCtrls, const std::span negClassicalCtrls, PatternRewriter& rewriter, - std::span& worklist) { + const std::span targetValues) { + bool addsGlobalPhase = false; if (isa(op) || isa(op) || isa(op) || isa(op) || isa(op) || isa(op)) { - const auto addedGlobalPhase = - ut->globalPhaseThatIsAdded(*op, op->getInputQubit(0), ctrlsQuantum, - posClassicalCtrls, negClassicalCtrls); + const auto inputQubit = + targetValues.empty() ? op->getInputQubit(0) : targetValues[0]; + const auto addedGlobalPhase = ut->globalPhaseThatIsAdded( + *op, inputQubit, ctrlsQuantum, posClassicalCtrls, negClassicalCtrls); if (addedGlobalPhase.has_value()) { - std::ranges::replace(worklist, *op, static_cast(nullptr)); + addsGlobalPhase = true; const auto phase = addedGlobalPhase.value(); if (std::norm(phase) > 1e-4) { GPhaseOp::create(rewriter, op->getLoc(), phase); } - removeOperation(op, rewriter); } } + return addsGlobalPhase; +} + +WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, + const std::span ctrlsQuantum, + const std::span newCtrlsQuantum, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist, + const std::span targetValues = {}, + const std::span resultValues = {}) { + // Check if a diagonal gate only adds a global phase + const bool addsGlobalPhase = + addsOnlyGlobalPhase(ut, op, ctrlsQuantum, posClassicalCtrls, + negClassicalCtrls, rewriter, targetValues); + if (addsGlobalPhase) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + removeOperation(op, rewriter); + return WalkResult::advance(); + } - const auto targets = op->getInputTargets(); - const auto results = op->getOutputTargets(); const auto params = op->getParameters(); - std::vector targetValues = {targets.begin(), targets.end()}; - std::vector resultValues = {results.begin(), results.end()}; std::vector paramValues = {params.begin(), params.end()}; - ut->propagateGate(*op, targetValues, resultValues, ctrlsQuantum, - newCtrlsQuantum, posClassicalCtrls, negClassicalCtrls, - paramValues); + if (targetValues.empty() && resultValues.empty()) { + const auto targets = op->getInputTargets(); + const auto results = op->getOutputTargets(); + std::vector targetVecs = {targets.begin(), targets.end()}; + std::vector resultVecs = {results.begin(), results.end()}; + ut->propagateGate(*op, targetVecs, resultVecs, ctrlsQuantum, + newCtrlsQuantum, posClassicalCtrls, negClassicalCtrls, + paramValues); + } else if (targetValues.size() == resultValues.size()) { + ut->propagateGate(*op, targetValues, resultValues, ctrlsQuantum, + newCtrlsQuantum, posClassicalCtrls, negClassicalCtrls, + paramValues); + } else { + throw std::invalid_argument( + "Given targetValues and resultValues need to be of same size."); + } return WalkResult::advance(); } @@ -135,18 +178,56 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, const std::span negClassicalCtrls, PatternRewriter& rewriter, std::span& worklist) { - const auto inputCtrs = op->getInputControls(); - std::vector inCtrlValues = {inputCtrs.begin(), inputCtrs.end()}; + const auto inputCtrls = op->getInputControls(); + std::vector inCtrlValues = {inputCtrls.begin(), inputCtrls.end()}; // TODO: Check if gate is executable - // TODO: Work on the right qubits in body + + auto body = op->getBodyUnitary(); + // Make sure that the right qubits in the right order are passed to the + // propagation of the body unitary + std::vector targetQubits; + const auto numTargets = op->getNumTargets(); + targetQubits.reserve(numTargets); + const auto arguments = op->getRegion().getArguments(); + for (unsigned int argIndex = 0; argIndex < arguments.size(); ++argIndex) { + for (unsigned int i = 0; i < numTargets; ++i) { + if (arguments[i] == body.getInputTarget(i)) { + targetQubits.insert(targetQubits.begin() + i, + op->getInputTarget(argIndex)); + break; + } + } + } + + std::vector resultQubits; + resultQubits.reserve(numTargets); + const auto yieldOP = cast(*(op->getBody()->rbegin())); + for (unsigned int uOpOutIndex = 0; uOpOutIndex < numTargets; ++uOpOutIndex) { + for (unsigned int i = 0; i < numTargets; ++i) { + if (yieldOP->getOperand(i) == body.getOutputTarget(uOpOutIndex)) { + resultQubits.insert(resultQubits.begin() + i, + op->getOutputTarget(uOpOutIndex)); + break; + } + } + } const auto outputCtrls = op->getOutputControls(); std::vector outCtrlValues = {outputCtrls.begin(), outputCtrls.end()}; - auto body = op->getBodyUnitary(); + // Check if a diagonal gate only adds a global phase + const bool addsGlobalPhase = + addsOnlyGlobalPhase(ut, &body, inCtrlValues, posClassicalCtrls, + negClassicalCtrls, rewriter, targetQubits); + if (addsGlobalPhase) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + removeCtrlOperation(op, rewriter, worklist); + return WalkResult::advance(); + } + return handleUnitary(ut, &body, inCtrlValues, outCtrlValues, - posClassicalCtrls, negClassicalCtrls, rewriter, - worklist); + posClassicalCtrls, negClassicalCtrls, rewriter, worklist, + targetQubits, resultQubits); } LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, @@ -172,6 +253,10 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, const auto res = TypeSwitch(curr) /// qco Dialect + .Case([&](CtrlOp op) { + return handleCtrlOp(ut, &op, posClassicalCtrls, negClassicalCtrls, + rewriter, worklist); + }) .Case([&](UnitaryOpInterface op) { return handleUnitary(ut, &op, {}, {}, posClassicalCtrls, negClassicalCtrls, rewriter, worklist); From 9f15fb16922edaf03c24cc520c792ffbfb904063 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 10:14:17 +0200 Subject: [PATCH 104/131] :construction: Handling of ctrl --- .../Optimizations/ConstantPropagation.cpp | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index ba401ebe0c..4bb611369a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -72,9 +72,6 @@ void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter, std::span& worklist) { - op->walk([&](Operation* bodyOp) { - std::ranges::replace(worklist, bodyOp, static_cast(nullptr)); - }); for (const auto outQubit : op->getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); } @@ -137,19 +134,8 @@ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, const std::span newCtrlsQuantum, const std::span posClassicalCtrls, const std::span negClassicalCtrls, - PatternRewriter& rewriter, - std::span& worklist, const std::span targetValues = {}, const std::span resultValues = {}) { - // Check if a diagonal gate only adds a global phase - const bool addsGlobalPhase = - addsOnlyGlobalPhase(ut, op, ctrlsQuantum, posClassicalCtrls, - negClassicalCtrls, rewriter, targetValues); - if (addsGlobalPhase) { - std::ranges::replace(worklist, *op, static_cast(nullptr)); - removeOperation(op, rewriter); - return WalkResult::advance(); - } const auto params = op->getParameters(); std::vector paramValues = {params.begin(), params.end()}; @@ -173,11 +159,36 @@ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, return WalkResult::advance(); } +WalkResult handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist) { + const auto targets = op->getInputTargets(); + std::vector targetVecs = {targets.begin(), targets.end()}; + + // Check if a diagonal gate only adds a global phase + const bool addsGlobalPhase = addsOnlyGlobalPhase( + ut, op, {}, posClassicalCtrls, negClassicalCtrls, rewriter, targetVecs); + if (addsGlobalPhase) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + removeOperation(op, rewriter); + return WalkResult::advance(); + } + + return handleUnitary(ut, op, {}, {}, posClassicalCtrls, negClassicalCtrls); +} + WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, const std::span posClassicalCtrls, const std::span negClassicalCtrls, PatternRewriter& rewriter, std::span& worklist) { + // Avoid to address body twice + op->walk([&](Operation* bodyOp) { + std::ranges::replace(worklist, bodyOp, static_cast(nullptr)); + }); + const auto inputCtrls = op->getInputControls(); std::vector inCtrlValues = {inputCtrls.begin(), inputCtrls.end()}; // TODO: Check if gate is executable @@ -226,8 +237,8 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, } return handleUnitary(ut, &body, inCtrlValues, outCtrlValues, - posClassicalCtrls, negClassicalCtrls, rewriter, worklist, - targetQubits, resultQubits); + posClassicalCtrls, negClassicalCtrls, targetQubits, + resultQubits); } LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, @@ -258,8 +269,9 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, rewriter, worklist); }) .Case([&](UnitaryOpInterface op) { - return handleUnitary(ut, &op, {}, {}, posClassicalCtrls, - negClassicalCtrls, rewriter, worklist); + return handleUncontrolledUnitary(ut, &op, posClassicalCtrls, + negClassicalCtrls, rewriter, + worklist); }) // .Case([&](const ResetOp op) { // return handleReset(ut, op, posClassicalCtrls, From af40f05809535d3776452fef7dfa5a723db24e93 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:32:09 +0200 Subject: [PATCH 105/131] :memo: Added docstrings --- .../Optimizations/ConstantPropagation.cpp | 126 +++++++++++++++++- 1 file changed, 121 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 4bb611369a..076f02b84a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -28,6 +28,12 @@ namespace mlir::qco { namespace { +/** + * This method checks whether the func::FuncOp is an entry point to the program. + * + * @param op The func::FuncOp to be checked. + * @return Whether the operation is an entry point to the program. + */ bool isEntryPoint(const func::FuncOp op) { const auto passthroughAttr = op->getAttrOfType("passthrough"); if (!passthroughAttr) { @@ -43,6 +49,9 @@ bool isEntryPoint(const func::FuncOp op) { /** * This method moves all measurements as far to the front as possible, in order * to execute constant propagation more efficiently. + * + * @param module The module which contains the operations + * @param ctx The MLIR context */ bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { bool changed = false; @@ -63,6 +72,12 @@ bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { return changed; } +/** + * Removes a UnitaryOpInterface from the mlir context. + * + * @param op The qco::UnitaryOpInterface to be removed. + * @param rewriter The used rewriter. + */ void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { for (const auto outQubit : op->getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); @@ -70,14 +85,31 @@ void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { rewriter.eraseOp(*op); } -void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter, - std::span& worklist) { +/** + * Removes a CtrlOp from the mlir context. + * + * @param op The qco::CtrlOp to be removed. + * @param rewriter The used rewriter. + */ +void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter) { for (const auto outQubit : op->getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); } rewriter.eraseOp(*op); } +/** + * Handles a constant operation, meaning it is propagated through the union + * table. + * + * @param ut Union table which contains the current quantum state + * @param op The arith::ConstantOp which is propagated. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @return Whether the handling was successfully or interrupted. + */ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, const std::span posClassicalCtrls, const std::span negClassicalCtrls) { @@ -105,6 +137,22 @@ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, return WalkResult::advance(); } +/** + * Checks if only a global phase is added to the quantum machine state. If yes + * and if the global phase is not = 1, it adds a global phase gate. + * + * @param ut Union table which contains the current quantum state + * @param op The qco::UnitaryOpInterface which is propagated. + * @param ctrlsQuantum The quantum control values considered in the operation. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param rewriter The used rewriter. + * @param targetValues The target values (non-empty if the method was called via + * a quantum control (qco::CtrlOp). + * @return Whether there is only a global phase added. + */ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, const std::span ctrlsQuantum, const std::span posClassicalCtrls, @@ -129,6 +177,23 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, return addsGlobalPhase; } +/** + * Handles a unitary gate, meaning it is propagated through the union table. + * + * @param ut Union table which contains the current quantum state + * @param op The qco::UnitaryOpInterface which is propagated. + * @param ctrlsQuantum The quantum control values considered in the operation. + * @param newCtrlsQuantum The values the quantum control values become after + * the operation. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param targetValues The target values (non-empty if the method was called via + * a quantum control (qco::CtrlOp). + * @param resultValues The values the target values become after the operation. + * @return Whether the handling was successfully or interrupted. + */ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, const std::span ctrlsQuantum, const std::span newCtrlsQuantum, @@ -159,6 +224,23 @@ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, return WalkResult::advance(); } +/** + * Handles an uncontrolled unitary gate. First, it is checked whether the gate + * only adds a global phase in case it is a diagonal gate. Then the gate is + * either propagated on the union table or removed/replaced by a gloal phase + * gate. + * + * @param ut Union table which contains the current quantum state + * @param op The qco::UnitaryOpInterface which is propagated. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param rewriter The used rewriter + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return Whether the handling was successfully or interrupted. + */ WalkResult handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, const std::span posClassicalCtrls, const std::span negClassicalCtrls, @@ -179,6 +261,23 @@ WalkResult handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, return handleUnitary(ut, op, {}, {}, posClassicalCtrls, negClassicalCtrls); } +/** + * Handles a CtrlOp. First, it is checked whether the CtrlOp is executable + * considering the current quantum machine state. Then the CtrlOp is either + * propagated on the union table or removed/replaced by an unconditional gate. + * If it is replaced by an unconditional gate, the gate is propagated. + * + * @param ut Union table which contains the current quantum state + * @param op The qco::CtrlOp which is propagated. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param rewriter The used rewriter + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return Whether the handling was successfully or interrupted. + */ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, const std::span posClassicalCtrls, const std::span negClassicalCtrls, @@ -232,7 +331,7 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, negClassicalCtrls, rewriter, targetQubits); if (addsGlobalPhase) { std::ranges::replace(worklist, *op, static_cast(nullptr)); - removeCtrlOperation(op, rewriter, worklist); + removeCtrlOperation(op, rewriter); return WalkResult::advance(); } @@ -241,6 +340,21 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, resultQubits); } +/** + * Iterates through the worklist of operators and propagates the quantum machine + * state through the union table. The iteration can be called with specific + * classical controls which are considered in every propagation. + * + * @param rewriter The used rewriter + * @param ut The union table which contains the current quantum machine state. + * @param worklist The worklist which contains the operations that are iterated + * through. + * @param posClassicalCtrls The positive classical controls considered in every + * operation. + * @param negClassicalCtrls The negative classical controls considered in every + * operation. + * @return Whether the iteration was successfully or interrupted. + */ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, std::span& worklist, const std::span posClassicalCtrls, @@ -397,6 +511,8 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * (similarly to `walkAndApplyPatterns`). Consequently, a custom driver * would be required in any case, which adds unnecessary code to maintain. * + * @param module The module which contains the operations + * @param ctx The MLIR context * @return Success if constant propagation has been applied successfully */ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { @@ -423,8 +539,8 @@ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { } /** - * @brief This pass applies constant propagation to a circuit. It assumes that - * all states start in |0> and removes quantum instructions that are superfluous + * This pass applies constant propagation to a circuit. It assumes that all + * states start in |0> and removes quantum instructions that are superfluous * when the current state is considered. It also replaces quantum resources by * classical resources. */ From 27e9da3aac6922044e3c4756af14fc70724092c5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:39:13 +0200 Subject: [PATCH 106/131] :construction: Added creation of classical value inside measurement --- .../Optimizations/ConstantPropagation/UnionTable.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 09c1d02107..af55447faa 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -137,6 +137,10 @@ void UnionTable::propagateMeasurement( const Value classicalTarget, const std::span posCtrlsClassical, const std::span negCtrlsClassical) { + if (!valuesToEntries.contains(classicalTarget)) { + propagateIntAlloc(classicalTarget, 0); + } + std::vector targetVec = {quantumTarget, classicalTarget}; const std::set participatingEntries = collectParticipatingEntries(targetVec, {}, posCtrlsClassical, From 1819c0768fa612c4672210d960cfb13d22c0a8d4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:49:20 +0200 Subject: [PATCH 107/131] :construction: Added reset and measurement handling --- .../Optimizations/ConstantPropagation.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 076f02b84a..17dbd631eb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -127,8 +127,7 @@ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, ut->propagateDoubleAlloc(res, doubleAttr.getValueAsDouble()); } if (const auto boolAttr = dyn_cast(attr)) { - const bool v = boolAttr.getValue(); - if (v) { + if (boolAttr.getValue()) { ut->propagateIntAlloc(res, 1); } else { ut->propagateIntAlloc(res, 0); @@ -387,14 +386,17 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, negClassicalCtrls, rewriter, worklist); }) - // .Case([&](const ResetOp op) { - // return handleReset(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) - // .Case([&](const MeasureOp op) { - // return handleMeasure(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) + .Case([&](const ResetOp op) { + ut->propagateReset(op->getOperand(0), op->getResult(0), + posClassicalCtrls, negClassicalCtrls); + return WalkResult::advance(); + }) + .Case([&](const MeasureOp op) { + ut->propagateMeasurement(op->getOperand(0), op->getResult(0), + op->getResult(1), posClassicalCtrls, + negClassicalCtrls); + return WalkResult::advance(); + }) .Case([&](const AllocOp op) { addedAtLeastOneQubit = true; ut->propagateQubitAlloc(op->getOperand(0)); From eb0f2fb7a9f4ebc1c2b7c28cf00afcfacc6b7fde Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 16:44:26 +0200 Subject: [PATCH 108/131] :construction: Started removal of ctrl of always executed gates --- .../Optimizations/ConstantPropagation.cpp | 93 ++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 17dbd631eb..f61882bd58 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -28,6 +28,20 @@ namespace mlir::qco { namespace { +#define CREATE_OP_CASE(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), targetInput); \ + }) + +/** + * @brief Result of checking how do modify a controlled gate. + */ +struct controlsToModify { + llvm::DenseSet quantumCtrlsToRemove; + llvm::DenseSet classicalPosCtrlsToAdd; + llvm::DenseSet classicalNegCtrlsToAdd; +}; + /** * This method checks whether the func::FuncOp is an entry point to the program. * @@ -176,6 +190,38 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, return addsGlobalPhase; } +Operation* removeCtrlsOfGate(CtrlOp* op, + const llvm::DenseSet& ctrlsToRemove, + PatternRewriter& rewriter, + std::span& worklist) { + for (const auto& qubitCtrl : ctrlsToRemove) { + rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); + } + if (ctrlsToRemove.size() == op->getNumControls()) { + // Remove Ctrl completely + const auto targetInput = op->getInputTargets(); + const auto newOp = + mlir::TypeSwitch(op->getBodyUnitary()) + CREATE_OP_CASE(IdOp) CREATE_OP_CASE(HOp) CREATE_OP_CASE(XOp) + .Default([&](auto) -> Operation* { + throw std::runtime_error("Unsupported operation"); + }); + auto newUnitary = static_cast(newOp); + for (const auto inTarget : newUnitary.getInputQubits()) { + rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), + newUnitary.getOutputForInput(inTarget)); + } + for (const auto ctrlQubit : op->getOutputControls()) { + rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); + } + rewriter.eraseOp(*op); + std::ranges::replace(worklist, *op, newOp); + + return newOp; + } + return nullptr; +} + /** * Handles a unitary gate, meaning it is propagated through the union table. * @@ -289,7 +335,50 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, const auto inputCtrls = op->getInputControls(); std::vector inCtrlValues = {inputCtrls.begin(), inputCtrls.end()}; - // TODO: Check if gate is executable + + // Check if gate is executable + const auto satisfiable = ut->areThereSatisfiableCombinations( + inCtrlValues, posClassicalCtrls, negClassicalCtrls); + const auto superfluousCtrls = ut->getSuperfluousControls( + inCtrlValues, posClassicalCtrls, negClassicalCtrls); + if (superfluousCtrls.completelySuperfluous || !satisfiable) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + removeCtrlOperation(op, rewriter); + return WalkResult::advance(); + } + + // Collect quantum values to remove and classical values to add + controlsToModify ctrlsToMod; + ctrlsToMod.quantumCtrlsToRemove = superfluousCtrls.superfluousQubits; + for (const auto superfluousQ : ctrlsToMod.quantumCtrlsToRemove) { + std::erase(inCtrlValues, superfluousQ); + } + + for (const auto qCtrl : inCtrlValues) { + auto qCtrlValuesWithoutCurrent = inCtrlValues; + std::erase(qCtrlValuesWithoutCurrent, qCtrl); + if (ut->isQubitImplied(qCtrl, qCtrlValuesWithoutCurrent, posClassicalCtrls, + negClassicalCtrls)) { + std::erase(inCtrlValues, qCtrl); + ctrlsToMod.quantumCtrlsToRemove.insert(qCtrl); + } else if (auto v = ut->getValueThatIsEquivalentToQubit(qCtrl); + !v.empty()) { + for (const auto& [value, b] : v) { + if (b) { + ctrlsToMod.classicalPosCtrlsToAdd.insert(value); + } else { + ctrlsToMod.classicalNegCtrlsToAdd.insert(value); + } + break; + } + } + } + + if (!ctrlsToMod.quantumCtrlsToRemove.empty()) { + auto newOp = removeCtrlsOfGate(op, ctrlsToMod.quantumCtrlsToRemove, + rewriter, worklist); + return WalkResult::advance(); + } auto body = op->getBodyUnitary(); // Make sure that the right qubits in the right order are passed to the @@ -310,7 +399,7 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, std::vector resultQubits; resultQubits.reserve(numTargets); - const auto yieldOP = cast(*(op->getBody()->rbegin())); + const auto yieldOP = cast(*op->getBody()->rbegin()); for (unsigned int uOpOutIndex = 0; uOpOutIndex < numTargets; ++uOpOutIndex) { for (unsigned int i = 0; i < numTargets; ++i) { if (yieldOP->getOperand(i) == body.getOutputTarget(uOpOutIndex)) { From 99850ff023b763af766d6f48bbb65d268e5920bb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 17:38:37 +0200 Subject: [PATCH 109/131] :construction: Removal of ctrl of always executed gates --- .../Optimizations/ConstantPropagation.cpp | 77 +++++++++++++++++-- .../test_qco_constant_propagation.cpp | 31 ++++---- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index f61882bd58..00a7c80196 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -28,11 +28,41 @@ namespace mlir::qco { namespace { -#define CREATE_OP_CASE(opType) \ +#define CREATE_OP_CASE_NO_PARAMS(opType) \ .Case([&](auto) { \ return opType::create(rewriter, op->getLoc(), targetInput); \ }) +#define CREATE_OP_CASE_ONE_PARAM(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ + params[0]); \ + }) + +#define CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ + qubitIn[1], params[0]); \ + }) + +#define CREATE_OP_CASE_TWO_PARAMS(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ + params[0], params[1]); \ + }) + +#define CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ + qubitIn[1], params[0], params[1]); \ + }) + +#define CREATE_OP_CASE_THREE_PARAMS(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ + params[0], params[1], params[2]); \ + }) + /** * @brief Result of checking how do modify a controlled gate. */ @@ -200,12 +230,47 @@ Operation* removeCtrlsOfGate(CtrlOp* op, if (ctrlsToRemove.size() == op->getNumControls()) { // Remove Ctrl completely const auto targetInput = op->getInputTargets(); + const TypeRange resultTypes(op->getOutputTargets()); + const auto paramsRange = op->getParameters(); + const std::vector qubitIn = {targetInput.begin(), targetInput.end()}; + const std::vector params = {paramsRange.begin(), paramsRange.end()}; const auto newOp = - mlir::TypeSwitch(op->getBodyUnitary()) - CREATE_OP_CASE(IdOp) CREATE_OP_CASE(HOp) CREATE_OP_CASE(XOp) - .Default([&](auto) -> Operation* { - throw std::runtime_error("Unsupported operation"); - }); + mlir::TypeSwitch(op->getBodyUnitary()) CREATE_OP_CASE_NO_PARAMS( + IdOp) CREATE_OP_CASE_NO_PARAMS(HOp) CREATE_OP_CASE_NO_PARAMS(XOp) + CREATE_OP_CASE_NO_PARAMS(YOp) CREATE_OP_CASE_NO_PARAMS( + ZOp) CREATE_OP_CASE_NO_PARAMS(SOp) CREATE_OP_CASE_NO_PARAMS(SdgOp) + CREATE_OP_CASE_NO_PARAMS(TOp) CREATE_OP_CASE_NO_PARAMS( + TdgOp) CREATE_OP_CASE_NO_PARAMS(SXOp) CREATE_OP_CASE_NO_PARAMS(SXdgOp) + CREATE_OP_CASE_ONE_PARAM(RXOp) CREATE_OP_CASE_ONE_PARAM( + RYOp) CREATE_OP_CASE_ONE_PARAM(RZOp) + CREATE_OP_CASE_ONE_PARAM(POp) CREATE_OP_CASE_TWO_PARAMS( + ROp) CREATE_OP_CASE_TWO_PARAMS(U2Op) + CREATE_OP_CASE_THREE_PARAMS(UOp) CREATE_OP_CASE_NO_PARAMS( + SWAPOp) CREATE_OP_CASE_NO_PARAMS(iSWAPOp) + CREATE_OP_CASE_NO_PARAMS( + DCXOp) CREATE_OP_CASE_NO_PARAMS(ECROp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RXXOp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RYYOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RZXOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RZZOp) + CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( + XXPlusYYOp) + CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( + XXMinusYYOp) + .Default( + [&](auto) + -> Operation* { + throw std:: + runtime_error( + "Unsu" + "ppor" + "ted " + "oper" + "atio" + "n"); + }); auto newUnitary = static_cast(newOp); for (const auto inTarget : newUnitary.getInputQubits()) { rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 7671800706..3d07b3a087 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -74,14 +74,14 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); auto q = programBuilder.allocQubitRegister(4); q[0] = programBuilder.h(q[0]); - q[0] = programBuilder.x(q[0]); + q[0] = programBuilder.z(q[0]); q[0] = programBuilder.h(q[0]); - programBuilder.cx(q[0], q[1]); - q[2] = programBuilder.h(q[2]); - q[2] = programBuilder.z(q[2]); - q[2] = programBuilder.h(q[2]); - auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); - programBuilder.cry(0.3, q2, q3); + programBuilder.crx(i0, q[0], q[1]); + // q[2] = programBuilder.h(q[2]); + // q[2] = programBuilder.z(q[2]); + // q[2] = programBuilder.h(q[2]); + // auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); + // programBuilder.cry(0.3, q2, q3); module = programBuilder.finalize(); const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); @@ -89,16 +89,21 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { referenceBuilder.getLoc(), iAttrRef); auto qRef = referenceBuilder.allocQubitRegister(4); qRef[0] = referenceBuilder.h(qRef[0]); - qRef[0] = referenceBuilder.x(qRef[0]); + qRef[0] = referenceBuilder.z(qRef[0]); qRef[0] = referenceBuilder.h(qRef[0]); - qRef[2] = referenceBuilder.h(qRef[2]); - qRef[2] = referenceBuilder.z(qRef[2]); - qRef[2] = referenceBuilder.h(qRef[2]); - auto [q2Ref, q3Ref] = referenceBuilder.crx(i0Ref, qRef[2], qRef[3]); - referenceBuilder.cry(0.3, q2Ref, q3Ref); + qRef[0] = referenceBuilder.rx(i0Ref, qRef[1]); + // qRef[2] = referenceBuilder.h(qRef[2]); + // qRef[2] = referenceBuilder.z(qRef[2]); + // qRef[2] = referenceBuilder.h(qRef[2]); + // auto [q2Ref, q3Ref] = referenceBuilder.crx(i0Ref, qRef[2], qRef[3]); + // referenceBuilder.cry(0.3, q2Ref, q3Ref); reference = referenceBuilder.finalize(); + module->dump(); + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + module->dump(); + reference->dump(); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); From 64619cc335341d295eb2338d1e94cf421f6558b5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 18:44:28 +0200 Subject: [PATCH 110/131] :construction: Started removal of ctrls that are superfluous --- .../Optimizations/ConstantPropagation.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 00a7c80196..cfd8d4ab4e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -284,7 +284,31 @@ Operation* removeCtrlsOfGate(CtrlOp* op, return newOp; } - return nullptr; + std::vector newControlIn; + for (const auto& ctrls : op->getInputControls()) { + if (!ctrlsToRemove.contains(ctrls)) { + newControlIn.push_back(ctrls); + } + } + const auto newCtrl = + CtrlOp::create(rewriter, op->getLoc(), newControlIn, op->getTargetsIn(), + [&](const ValueRange target) { + return SmallVector{ + XOp::create(rewriter, op->getLoc(), target[0])}; + }); + + auto newUnitary = static_cast(newCtrl); + for (const auto inTarget : newUnitary.getInputQubits()) { + rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), + newUnitary.getOutputForInput(inTarget)); + } + for (const auto ctrlQubit : op->getOutputControls()) { + rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); + } + rewriter.eraseOp(*op); + std::ranges::replace(worklist, *op, newUnitary); + + return newUnitary; } /** From 0d440a2058ed318a48544732e603cb0346e7e178 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 21:14:15 +0200 Subject: [PATCH 111/131] =?UTF-8?q?=E2=9C=85=20Fixed=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_qco_constant_propagation.cpp | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 3d07b3a087..60f8545f2a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -74,14 +74,14 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); auto q = programBuilder.allocQubitRegister(4); q[0] = programBuilder.h(q[0]); - q[0] = programBuilder.z(q[0]); + q[0] = programBuilder.x(q[0]); q[0] = programBuilder.h(q[0]); programBuilder.crx(i0, q[0], q[1]); - // q[2] = programBuilder.h(q[2]); - // q[2] = programBuilder.z(q[2]); - // q[2] = programBuilder.h(q[2]); - // auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); - // programBuilder.cry(0.3, q2, q3); + q[2] = programBuilder.h(q[2]); + q[2] = programBuilder.z(q[2]); + q[2] = programBuilder.h(q[2]); + auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); + programBuilder.cry(0.3, q2, q3); module = programBuilder.finalize(); const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); @@ -89,21 +89,16 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { referenceBuilder.getLoc(), iAttrRef); auto qRef = referenceBuilder.allocQubitRegister(4); qRef[0] = referenceBuilder.h(qRef[0]); - qRef[0] = referenceBuilder.z(qRef[0]); + qRef[0] = referenceBuilder.x(qRef[0]); qRef[0] = referenceBuilder.h(qRef[0]); - qRef[0] = referenceBuilder.rx(i0Ref, qRef[1]); - // qRef[2] = referenceBuilder.h(qRef[2]); - // qRef[2] = referenceBuilder.z(qRef[2]); - // qRef[2] = referenceBuilder.h(qRef[2]); - // auto [q2Ref, q3Ref] = referenceBuilder.crx(i0Ref, qRef[2], qRef[3]); - // referenceBuilder.cry(0.3, q2Ref, q3Ref); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[2] = referenceBuilder.z(qRef[2]); + qRef[2] = referenceBuilder.h(qRef[2]); + qRef[3] = referenceBuilder.rx(i0Ref, qRef[3]); + referenceBuilder.ry(0.3, qRef[3]); reference = referenceBuilder.finalize(); - module->dump(); - ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - module->dump(); - reference->dump(); EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); @@ -135,26 +130,28 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { * controlled gate. */ TEST_F(QCOConstantPropagationTest, testRemoveImpliedQubits) { - auto q = programBuilder.allocQubitRegister(4); + auto q = programBuilder.allocQubitRegister(5); q[0] = programBuilder.h(q[0]); q[1] = programBuilder.h(q[1]); auto [q01, q2] = programBuilder.ctrl({q[0], q[1]}, {q[2]}, [&](const ValueRange target) { return SmallVector{programBuilder.x(target[0])}; }); - programBuilder.ctrl({q01[0], q01[1], q2[0]}, {q[3]}, + q[4] = programBuilder.x(q[4]); + programBuilder.ctrl({q01[1], q2[0], q[4]}, {q[3]}, [&](const ValueRange target) { return SmallVector{programBuilder.x(target[0])}; }); module = programBuilder.finalize(); - auto qRef = referenceBuilder.allocQubitRegister(4); + auto qRef = referenceBuilder.allocQubitRegister(5); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); auto [qRef01, qRef2] = referenceBuilder.ctrl( {qRef[0], qRef[1]}, {qRef[2]}, [&](const ValueRange target) { return SmallVector{referenceBuilder.x(target[0])}; }); + referenceBuilder.x(qRef[4]); referenceBuilder.cx(qRef2[0], qRef[3]); reference = referenceBuilder.finalize(); From ef6070392b8917ae64c0be6fdd7104b263ca4b15 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 07:24:58 +0200 Subject: [PATCH 112/131] :memo: Added docstring --- .../Transforms/Optimizations/ConstantPropagation.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index cfd8d4ab4e..cc24b2685a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -220,6 +220,17 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, return addsGlobalPhase; } +/** + * Removes the given quantum controls from a CtrlOp, potentially removing all + * controls and only leaving the (formerly controlled) gate in the body. + * + * @param op The qco::CtrlOp whose controls are removed. + * @param ctrlsToRemove The controls which should be removed from the CtrlOp. + * @param rewriter The used rewriter + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return The operation without given controls. + */ Operation* removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, PatternRewriter& rewriter, From 639700172cc7a0afdd51a4cd9b39c89f6844d000 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 10:04:06 +0200 Subject: [PATCH 113/131] :construction: Continued removal of ctrls in CtrlOp --- .../Optimizations/ConstantPropagation.cpp | 219 +++++++++++------- .../test_qco_constant_propagation.cpp | 35 ++- 2 files changed, 161 insertions(+), 93 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index cc24b2685a..4098aafd22 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -30,37 +30,41 @@ namespace { #define CREATE_OP_CASE_NO_PARAMS(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), targetInput); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0]); \ + }) + +#define CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(opType) \ + .Case([&](auto) { \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1]); \ }) #define CREATE_OP_CASE_ONE_PARAM(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ - params[0]); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0]); \ }) #define CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ - qubitIn[1], params[0]); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1], \ + params[0]); \ }) #define CREATE_OP_CASE_TWO_PARAMS(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ - params[0], params[1]); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0], \ + params[1]); \ }) #define CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ - qubitIn[1], params[0], params[1]); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1], \ + params[0], params[1]); \ }) #define CREATE_OP_CASE_THREE_PARAMS(opType) \ .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), resultTypes, qubitIn[0], \ - params[0], params[1], params[2]); \ + return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0], \ + params[1], params[2]); \ }) /** @@ -221,7 +225,97 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, } /** - * Removes the given quantum controls from a CtrlOp, potentially removing all + * Creates a new gate at the location of the given gate and of the type of the + * given gate. + * + * @param op The operation whose type and location is used. + * @param rewriter The used rewriter. + * @param qubitsIn A span of target inputs. + * @param params A span of parameters for the new gate. + * @return The newly created gate. + */ +Operation* createOperationFromUnitaryOperation(Operation* op, + PatternRewriter& rewriter, + const std::span qubitsIn, + const std::span params) { + const auto newOp = + mlir::TypeSwitch(op) CREATE_OP_CASE_NO_PARAMS( + IdOp) CREATE_OP_CASE_NO_PARAMS(HOp) CREATE_OP_CASE_NO_PARAMS(XOp) + CREATE_OP_CASE_NO_PARAMS(YOp) CREATE_OP_CASE_NO_PARAMS( + ZOp) CREATE_OP_CASE_NO_PARAMS(SOp) CREATE_OP_CASE_NO_PARAMS(SdgOp) + CREATE_OP_CASE_NO_PARAMS(TOp) CREATE_OP_CASE_NO_PARAMS( + TdgOp) CREATE_OP_CASE_NO_PARAMS(SXOp) + CREATE_OP_CASE_NO_PARAMS(SXdgOp) CREATE_OP_CASE_ONE_PARAM( + RXOp) CREATE_OP_CASE_ONE_PARAM(RYOp) + CREATE_OP_CASE_ONE_PARAM(RZOp) CREATE_OP_CASE_ONE_PARAM( + POp) CREATE_OP_CASE_TWO_PARAMS(ROp) + CREATE_OP_CASE_TWO_PARAMS(U2Op) CREATE_OP_CASE_THREE_PARAMS( + UOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(SWAPOp) + CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS( + iSWAPOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(DCXOp) + CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS( + ECROp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RXXOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RYYOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RZXOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RZZOp) + CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( + XXPlusYYOp) + CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( + XXMinusYYOp) + .Default( + [&](auto) + -> Operation* { + throw std:: + runtime_error( + "Unsu" + "ppor" + "ted " + "oper" + "atio" + "n"); + }); + + return newOp; +} + +/** + * Removes all controls from a gate and returns an uncontrolled operation. + * + * @param op The qco::CtrlOp whose controls are removed. + * @param rewriter The used rewriter + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return The operation without the controls. + */ +UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, + std::span& worklist) { + for (const auto& qubitCtrl : op->getInputQubits()) { + rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); + } + const auto targetInput = op->getInputTargets(); + const auto paramsRange = op->getParameters(); + std::vector qubitsIn = {targetInput.begin(), targetInput.end()}; + std::vector params = {paramsRange.begin(), paramsRange.end()}; + const auto newOp = createOperationFromUnitaryOperation( + op->getBodyUnitary(), rewriter, qubitsIn, params); + auto newUnitary = static_cast(newOp); + for (const auto inTarget : newUnitary.getInputQubits()) { + rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), + newUnitary.getOutputForInput(inTarget)); + } + for (const auto ctrlQubit : op->getOutputControls()) { + rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); + } + rewriter.eraseOp(*op); + std::ranges::replace(worklist, *op, newOp); + + return newUnitary; +} + +/** + * Removes the given quantum controls from a CtrlOp, but not removing all * controls and only leaving the (formerly controlled) gate in the body. * * @param op The qco::CtrlOp whose controls are removed. @@ -231,69 +325,14 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, * through. * @return The operation without given controls. */ -Operation* removeCtrlsOfGate(CtrlOp* op, - const llvm::DenseSet& ctrlsToRemove, - PatternRewriter& rewriter, - std::span& worklist) { +CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, + PatternRewriter& rewriter, + std::span& worklist) { for (const auto& qubitCtrl : ctrlsToRemove) { rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); } if (ctrlsToRemove.size() == op->getNumControls()) { - // Remove Ctrl completely - const auto targetInput = op->getInputTargets(); - const TypeRange resultTypes(op->getOutputTargets()); - const auto paramsRange = op->getParameters(); - const std::vector qubitIn = {targetInput.begin(), targetInput.end()}; - const std::vector params = {paramsRange.begin(), paramsRange.end()}; - const auto newOp = - mlir::TypeSwitch(op->getBodyUnitary()) CREATE_OP_CASE_NO_PARAMS( - IdOp) CREATE_OP_CASE_NO_PARAMS(HOp) CREATE_OP_CASE_NO_PARAMS(XOp) - CREATE_OP_CASE_NO_PARAMS(YOp) CREATE_OP_CASE_NO_PARAMS( - ZOp) CREATE_OP_CASE_NO_PARAMS(SOp) CREATE_OP_CASE_NO_PARAMS(SdgOp) - CREATE_OP_CASE_NO_PARAMS(TOp) CREATE_OP_CASE_NO_PARAMS( - TdgOp) CREATE_OP_CASE_NO_PARAMS(SXOp) CREATE_OP_CASE_NO_PARAMS(SXdgOp) - CREATE_OP_CASE_ONE_PARAM(RXOp) CREATE_OP_CASE_ONE_PARAM( - RYOp) CREATE_OP_CASE_ONE_PARAM(RZOp) - CREATE_OP_CASE_ONE_PARAM(POp) CREATE_OP_CASE_TWO_PARAMS( - ROp) CREATE_OP_CASE_TWO_PARAMS(U2Op) - CREATE_OP_CASE_THREE_PARAMS(UOp) CREATE_OP_CASE_NO_PARAMS( - SWAPOp) CREATE_OP_CASE_NO_PARAMS(iSWAPOp) - CREATE_OP_CASE_NO_PARAMS( - DCXOp) CREATE_OP_CASE_NO_PARAMS(ECROp) - CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( - RXXOp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RYYOp) - CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( - RZXOp) - CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( - RZZOp) - CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( - XXPlusYYOp) - CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( - XXMinusYYOp) - .Default( - [&](auto) - -> Operation* { - throw std:: - runtime_error( - "Unsu" - "ppor" - "ted " - "oper" - "atio" - "n"); - }); - auto newUnitary = static_cast(newOp); - for (const auto inTarget : newUnitary.getInputQubits()) { - rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), - newUnitary.getOutputForInput(inTarget)); - } - for (const auto ctrlQubit : op->getOutputControls()) { - rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); - } - rewriter.eraseOp(*op); - std::ranges::replace(worklist, *op, newOp); - - return newOp; + throw std::runtime_error("Cannot remove all controls of a CtrlOp"); } std::vector newControlIn; for (const auto& ctrls : op->getInputControls()) { @@ -301,25 +340,28 @@ Operation* removeCtrlsOfGate(CtrlOp* op, newControlIn.push_back(ctrls); } } - const auto newCtrl = - CtrlOp::create(rewriter, op->getLoc(), newControlIn, op->getTargetsIn(), - [&](const ValueRange target) { - return SmallVector{ - XOp::create(rewriter, op->getLoc(), target[0])}; - }); - - auto newUnitary = static_cast(newCtrl); - for (const auto inTarget : newUnitary.getInputQubits()) { + CtrlOp newCtrl = CtrlOp::create( + rewriter, op->getLoc(), newControlIn, op->getTargetsIn(), + [&](const ValueRange target) { + const auto paramsRange = op->getParameters(); + std::vector qubitsIn = {target.begin(), target.end()}; + std::vector params = {paramsRange.begin(), paramsRange.end()}; + const auto newOp = createOperationFromUnitaryOperation( + op->getBodyUnitary(), rewriter, qubitsIn, params); + return SmallVector{newOp->getResults()}; + }); + + for (const auto inTarget : newCtrl.getInputQubits()) { rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), - newUnitary.getOutputForInput(inTarget)); + newCtrl.getOutputForInput(inTarget)); } for (const auto ctrlQubit : op->getOutputControls()) { rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); } rewriter.eraseOp(*op); - std::ranges::replace(worklist, *op, newUnitary); + std::ranges::replace(worklist, *op, newCtrl); - return newUnitary; + return newCtrl; } /** @@ -475,9 +517,14 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, } if (!ctrlsToMod.quantumCtrlsToRemove.empty()) { - auto newOp = removeCtrlsOfGate(op, ctrlsToMod.quantumCtrlsToRemove, - rewriter, worklist); - return WalkResult::advance(); + if (ctrlsToMod.quantumCtrlsToRemove.size() == op->getNumControls()) { + auto newOp = removeAllCtrlsOfGate(op, rewriter, worklist); + return handleUncontrolledUnitary(ut, &newOp, posClassicalCtrls, + negClassicalCtrls, rewriter, worklist); + } + + *op = removeCtrlsOfGate(op, ctrlsToMod.quantumCtrlsToRemove, rewriter, + worklist); } auto body = op->getBodyUnitary(); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 60f8545f2a..e0e374c21a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -81,7 +81,9 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { q[2] = programBuilder.z(q[2]); q[2] = programBuilder.h(q[2]); auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); - programBuilder.cry(0.3, q2, q3); + // programBuilder.cry(0.3, q2, q3); + programBuilder.ry(0.3, q2); + programBuilder.rz(0.3, q3); // Error when collecting participating entries module = programBuilder.finalize(); const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); @@ -98,8 +100,11 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { referenceBuilder.ry(0.3, qRef[3]); reference = referenceBuilder.finalize(); + module->dump(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + module->dump(); + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } @@ -131,6 +136,9 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { */ TEST_F(QCOConstantPropagationTest, testRemoveImpliedQubits) { auto q = programBuilder.allocQubitRegister(5); + const auto iAttr = programBuilder.getF64FloatAttr(-0.3926991); + Value i0 = + arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); q[0] = programBuilder.h(q[0]); q[1] = programBuilder.h(q[1]); auto [q01, q2] = @@ -138,21 +146,34 @@ TEST_F(QCOConstantPropagationTest, testRemoveImpliedQubits) { return SmallVector{programBuilder.x(target[0])}; }); q[4] = programBuilder.x(q[4]); - programBuilder.ctrl({q01[1], q2[0], q[4]}, {q[3]}, - [&](const ValueRange target) { - return SmallVector{programBuilder.x(target[0])}; - }); + auto [q124, q3] = programBuilder.ctrl( + {q01[1], q2[0], q[4]}, {q[3]}, [&](const ValueRange target) { + return SmallVector{programBuilder.rx(i0, target[0])}; + }); + programBuilder.h(q01[0]); + programBuilder.h(q124[0]); + programBuilder.h(q124[1]); + programBuilder.h(q3[0]); + programBuilder.h(q124[2]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(5); + const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); + Value i0Ref = arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getLoc(), iAttrRef); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.h(qRef[1]); auto [qRef01, qRef2] = referenceBuilder.ctrl( {qRef[0], qRef[1]}, {qRef[2]}, [&](const ValueRange target) { return SmallVector{referenceBuilder.x(target[0])}; }); - referenceBuilder.x(qRef[4]); - referenceBuilder.cx(qRef2[0], qRef[3]); + qRef[4] = referenceBuilder.x(qRef[4]); + auto [qRef21, qRef31] = referenceBuilder.crx(i0Ref, qRef2[0], qRef[3]); + referenceBuilder.h(qRef01[0]); + referenceBuilder.h(qRef01[1]); + referenceBuilder.h(qRef21); + referenceBuilder.h(qRef31); + referenceBuilder.h(qRef[4]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From c52720922e1aab2ad8cee18a1e1369f6562ed36d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 12:43:41 +0200 Subject: [PATCH 114/131] :construction: Fixed creation of uncontrolled gates --- .../Optimizations/ConstantPropagation.cpp | 4 ++-- .../test_qco_constant_propagation.cpp | 17 +++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 4098aafd22..f3b07bbbaf 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -302,8 +302,8 @@ UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, op->getBodyUnitary(), rewriter, qubitsIn, params); auto newUnitary = static_cast(newOp); for (const auto inTarget : newUnitary.getInputQubits()) { - rewriter.replaceAllUsesWith(op->getOutputForInput(inTarget), - newUnitary.getOutputForInput(inTarget)); + rewriter.replaceAllUsesExcept( + inTarget, newUnitary.getOutputForInput(inTarget), newUnitary); } for (const auto ctrlQubit : op->getOutputControls()) { rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index e0e374c21a..2e585ceb56 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -81,9 +81,7 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { q[2] = programBuilder.z(q[2]); q[2] = programBuilder.h(q[2]); auto [q2, q3] = programBuilder.crx(i0, q[2], q[3]); - // programBuilder.cry(0.3, q2, q3); - programBuilder.ry(0.3, q2); - programBuilder.rz(0.3, q3); // Error when collecting participating entries + programBuilder.cry(0.3, q2, q3); module = programBuilder.finalize(); const auto iAttrRef = referenceBuilder.getF64FloatAttr(-0.3926991); @@ -100,11 +98,8 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { referenceBuilder.ry(0.3, qRef[3]); reference = referenceBuilder.finalize(); - module->dump(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - module->dump(); - EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } @@ -540,8 +535,8 @@ TEST_F(QCOConstantPropagationTest, testRemoveMultiQubitPhaseGateMinusOne) { module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); - qRef[0] = referenceBuilder.x(qRef[0]); - referenceBuilder.cx(qRef[0], qRef[1]); + referenceBuilder.x(qRef[0]); + referenceBuilder.x(qRef[1]); referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); @@ -559,15 +554,13 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveMultiQubitPhaseGate) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.h(q[0]); auto [q0, q1] = programBuilder.cx(q[0], q[1]); - auto [q01, b0] = programBuilder.measure(q0); - programBuilder.cz(q01, q1); + programBuilder.cz(q0, q1); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.h(qRef[0]); auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); - auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); - referenceBuilder.cz(qRef01, qRef1); + referenceBuilder.cz(qRef0, qRef1); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 80c6b82c32d33a056be969ebcd40cad899c0a934 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 14:06:53 +0200 Subject: [PATCH 115/131] :test_tube: Added test to check propagation in classical branching --- .../test_qco_constant_propagation.cpp | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 2e585ceb56..0c755c8f48 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -452,6 +452,52 @@ TEST_F(QCOConstantPropagationTest, testClassicalImpliesQuantum) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks if propagation through classical branching is + * done correctly. + */ +TEST_F(QCOConstantPropagationTest, testPropagatingThroughClassicalBranching) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.x(q[1]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q01, b0] = programBuilder.measure(q0); + const auto qRange = programBuilder.qcoIf( + b0, {q01, q1}, + [&](const ValueRange args) { + const auto qubit = programBuilder.x(args[1]); + return SmallVector{args[0], qubit}; + }, + [&](const ValueRange args) { + const auto qubit = programBuilder.x(args[0]); + return SmallVector{qubit, args[1]}; + }); + programBuilder.cz(qRange[0], qRange[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.x(qRef[1]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + referenceBuilder.qcoIf( + bRef0, {qRef01, qRef1}, + [&](const ValueRange args) { + const auto qubit = referenceBuilder.x(args[1]); + return SmallVector{args[0], qubit}; + }, + [&](const ValueRange args) { + const auto qubit = referenceBuilder.x(args[0]); + return SmallVector{qubit, args[1]}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + /** * @brief Test: This test checks if a phase gate is removed if it only adds a * global phase = 1. From dd691d14323129342b53a7658fee7e3c68ff8f0c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 16:23:10 +0200 Subject: [PATCH 116/131] :construction: Added propagation of classical branching --- .../ConstantPropagation/UnionTable.hpp | 49 +++---- .../Optimizations/ConstantPropagation.cpp | 120 +++++++++++++++++- .../ConstantPropagation/UnionTable.cpp | 25 ++++ .../test_qco_constant_propagation.cpp | 1 + 4 files changed, 157 insertions(+), 38 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 36fb11b212..1608d563d1 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -81,37 +81,6 @@ class UnionTable { std::set> entries; llvm::DenseMap qubitsToGlobalIndices; - /** @brief: Replaces values globally by new values - * - * @param replacedValues Values to be replaced - * @param newValues Values the first values are replaced with. - * @throws runtime_error if the size of the two parameters is not equal. - */ - void replaceValuesGlobally(const std::span replacedValues, - const std::span newValues) { - if (replacedValues.size() != newValues.size()) { - throw std::domain_error( - "replacedValues and newValues do not have the same size."); - } - - for (unsigned int i = 0; i < replacedValues.size(); ++i) { - const auto rV = replacedValues[i]; - const auto nV = newValues[i]; - qubitsToGlobalIndices[nV] = qubitsToGlobalIndices.at(rV); - qubitsToGlobalIndices.erase(rV); - const auto ute = valuesToEntries.at(rV); - valuesToEntries.erase(rV); - valuesToEntries[nV] = ute; - if (ute->participatingQubits.contains(rV)) { - ute->participatingQubits.insert(nV); - ute->participatingQubits.erase(rV); - } else { - ute->participatingClassicalValues.insert(nV); - ute->participatingClassicalValues.erase(rV); - } - } - } - /** @brief: Collects a set of all participating entries. * * @param targets An array of the Values of the target qubits. @@ -312,6 +281,15 @@ class UnionTable { [[nodiscard("UnionTable::toString called but ignored")]] std::string toString() const; + /** @brief: Replaces values globally by new values + * + * @param replacedValues Values to be replaced + * @param newValues Values the first values are replaced with. + * @throws runtime_error if the size of the two parameters is not equal. + */ + void replaceValuesGlobally(std::span replacedValues, + std::span newValues); + [[nodiscard("UnionTable::allTop called but ignored")]] bool areStatesAllTop(); @@ -379,6 +357,15 @@ class UnionTable { std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}); + /** + * This method replaces all instances of a value in the union table by + * another. + * + * @param from The Value that is being replaced. + * @param to The Value the replaced Value becomes. + */ + void replaceValues(Value from, Value to); + /** * @brief This method propagates a qubit alloc. * diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index f3b07bbbaf..93de93dbc9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -76,6 +76,11 @@ struct controlsToModify { llvm::DenseSet classicalNegCtrlsToAdd; }; +LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, + std::span& worklist, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls); + /** * This method checks whether the func::FuncOp is an entry point to the program. * @@ -364,6 +369,107 @@ CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, return newCtrl; } +/** + * Handles classical branching. Iterates through the body of the branching in a + * new loop and removes the body from the current iteration. + * + * @param ut Union table which contains the current quantum state + * @param op The qco::UnitaryOpInterface which is propagated. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param rewriter The used rewriter + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return Whether the handling was successfully or interrupted. + */ +WalkResult handleIfOp(UnionTable* ut, IfOp* op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist) { + const Value condition = op->getCondition(); + // TODO: Always/Never executed + + const auto& thenBlock = op->thenBlock(); + const auto& elseBlock = op->elseBlock(); + const bool thenEmpty = thenBlock->getOperations().size() <= 1; + const bool elseEmpty = elseBlock->getOperations().size() <= 1; + + const auto targetQubits = op->getQubits(); + std::vector targets = {targetQubits.begin(), targetQubits.end()}; + std::vector args; + + // propagate through then and else block + if (!thenEmpty) { + for (const Value arg : thenBlock->getArguments()) { + args.push_back(arg); + } + ut->replaceValuesGlobally(targets, args); + targets = args; + std::vector newWorklist; + + op->thenBlock()->walk([&](Operation* innerOp) { + newWorklist.push_back(innerOp); + // Propagating values in order to assign the right values to the right + // result values + const auto input = innerOp->getOperands(); + const auto output = innerOp->getResults(); + for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { + std::ranges::replace(targets, input[i], output[i]); + } + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); + std::span wl = {newWorklist.begin(), newWorklist.end()}; + std::vector newPosClassicalCtrls = {posClassicalCtrls.begin(), + posClassicalCtrls.end()}; + newPosClassicalCtrls.push_back(condition); + const auto resThen = iterateThroughWorklist( + rewriter, ut, wl, newPosClassicalCtrls, negClassicalCtrls); + + if (resThen.failed()) { + return WalkResult::interrupt(); + } + args.clear(); + } + if (!elseEmpty) { + for (const Value arg : elseBlock->getArguments()) { + args.push_back(arg); + } + ut->replaceValuesGlobally(targets, args); + targets = args; + std::vector newWorklist; + + op->elseBlock()->walk([&](Operation* innerOp) { + newWorklist.push_back(innerOp); + // Propagating values in order to assign the right values to the right + // result values + const auto input = innerOp->getOperands(); + const auto output = innerOp->getResults(); + for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { + std::ranges::replace(targets, input[i], output[i]); + } + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); + std::span wl = {newWorklist.begin(), newWorklist.end()}; + std::vector newNegClassicalCtrls = {negClassicalCtrls.begin(), + negClassicalCtrls.end()}; + newNegClassicalCtrls.push_back(condition); + const auto resElse = iterateThroughWorklist( + rewriter, ut, wl, posClassicalCtrls, newNegClassicalCtrls); + + if (resElse.failed()) { + return WalkResult::interrupt(); + } + } + const auto resultQubits = op->getResults(); + std::vector results = {resultQubits.begin(), resultQubits.end()}; + ut->replaceValuesGlobally(targets, results); + + return WalkResult::advance(); +} + /** * Handles a unitary gate, meaning it is propagated through the union table. * @@ -644,13 +750,13 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, .Case([&]([[maybe_unused]] SinkOp op) { return WalkResult::advance(); }) - // .Case([&](const IfOp op) { - // return handleIf(qcp, op, worklist, posClassicalCtrls, - // negClassicalCtrls, rewriter); - // }) - // .Case([&]([[maybe_unused]] YieldOp op) { - // return WalkResult::advance(); - // }) + .Case([&](IfOp op) { + return handleIfOp(ut, &op, posClassicalCtrls, negClassicalCtrls, + rewriter, worklist); + }) + .Case([&]([[maybe_unused]] YieldOp op) { + return WalkResult::advance(); + }) // /// built-in Dialect // .Case([&]([[maybe_unused]] ModuleOp op) { // return WalkResult::advance(); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index af55447faa..18930e4a9c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -66,6 +66,31 @@ std::string UnionTable::toString() const { return result; } +void UnionTable::replaceValuesGlobally(const std::span replacedValues, + const std::span newValues) { + if (replacedValues.size() != newValues.size()) { + throw std::domain_error( + "replacedValues and newValues do not have the same size."); + } + + for (unsigned int i = 0; i < replacedValues.size(); ++i) { + const auto rV = replacedValues[i]; + const auto nV = newValues[i]; + qubitsToGlobalIndices[nV] = qubitsToGlobalIndices.at(rV); + qubitsToGlobalIndices.erase(rV); + const auto ute = valuesToEntries.at(rV); + valuesToEntries.erase(rV); + valuesToEntries[nV] = ute; + if (ute->participatingQubits.contains(rV)) { + ute->participatingQubits.insert(nV); + ute->participatingQubits.erase(rV); + } else { + ute->participatingClassicalValues.insert(nV); + ute->participatingClassicalValues.erase(rV); + } + } +} + bool UnionTable::areStatesAllTop() { if (allTop) { return true; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 0c755c8f48..c2710128f8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -490,6 +490,7 @@ TEST_F(QCOConstantPropagationTest, testPropagatingThroughClassicalBranching) { const auto qubit = referenceBuilder.x(args[0]); return SmallVector{qubit, args[1]}; }); + referenceBuilder.gphase(std::numbers::pi); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 3ec5f72753bbc43508d578676c7f5229169a2994 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 07:44:10 +0200 Subject: [PATCH 117/131] :test_tube: Extended test for removal of classical condition --- .../test_qco_constant_propagation.cpp | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index c2710128f8..d5b9c08835 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -271,25 +271,33 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsZero) { * bit they depend on is always one. */ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsOne) { - auto q = programBuilder.allocQubitRegister(1); + auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.x(q[0]); auto [q0, b0] = programBuilder.measure(q[0]); - programBuilder.qcoIf( - b0, {q0}, + const auto qRange01 = programBuilder.qcoIf( + b0, {q0, q[1]}, [&](const ValueRange args) { - const auto qi0 = programBuilder.x(args[0]); - return SmallVector{qi0}; + const auto qi0 = programBuilder.h(args[0]); + const auto qi1 = programBuilder.h(args[1]); + const auto qi11 = programBuilder.z(qi1); + const auto [qi2, qi3] = programBuilder.cx(qi0, qi11); + return SmallVector{qi2, qi3}; }, [&](const ValueRange args) { const auto qi0 = programBuilder.h(args[0]); - return SmallVector{qi0}; + return SmallVector{qi0, args[1]}; }); + programBuilder.h(qRange01[1]); module = programBuilder.finalize(); - auto qRef = referenceBuilder.allocQubitRegister(1); + auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.x(qRef[0]); auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); - referenceBuilder.x(qRef0); + qRef[0] = referenceBuilder.h(qRef0); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[1] = referenceBuilder.z(qRef[1]); + const auto [qRef01, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + referenceBuilder.h(qRef1); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 22e02a91c584a99cf26a427a75b7d78147df4249 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 09:24:34 +0200 Subject: [PATCH 118/131] :construction: Removal of branch if condition is one --- .../Optimizations/ConstantPropagation.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 93de93dbc9..2887a8ead6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -392,6 +392,24 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, const Value condition = op->getCondition(); // TODO: Always/Never executed + if (ut->isClassicalValueAlwaysTrue(condition)) { + op->elseBlock()->walk([&](Operation* innerOp) { + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); + + const auto operation = op->getOperation(); + Block* block = &op->getThenRegion().front(); + Operation* terminator = block->getTerminator(); + const auto results = terminator->getOperands(); + rewriter.inlineBlockBefore(block, operation, op->getQubits()); + rewriter.replaceOp(operation, results); + rewriter.eraseOp(terminator); + std::ranges::replace(worklist, terminator, + static_cast(nullptr)); + + return WalkResult::advance(); + } + const auto& thenBlock = op->thenBlock(); const auto& elseBlock = op->elseBlock(); const bool thenEmpty = thenBlock->getOperations().size() <= 1; From aea9d4775580ba1b26b8678ca723d51166e884dd Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 11:25:49 +0200 Subject: [PATCH 119/131] :construction: Added handling to remove classical branches --- .../Optimizations/ConstantPropagation.cpp | 150 +++++++++++++----- .../test_qco_constant_propagation.cpp | 55 ++++++- 2 files changed, 164 insertions(+), 41 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 2887a8ead6..76227b6b6e 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -151,6 +151,28 @@ void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter) { rewriter.eraseOp(*op); } +/** + * Moves either the then or else block of an if operation out of the operation. + * Removes the terminator of the block both from the block and the worklist. + * + * @param ifOperation The operation whose then or else block gets inlined. + * @param block The block that gets inlined. + * @param worklist The worklist which contains the operations that are iterated + * through. + * @param rewriter The used rewriter. + */ +void inlineBranchBlock(IfOp* ifOperation, Block* block, + std::span& worklist, + PatternRewriter& rewriter) { + const auto operation = ifOperation->getOperation(); + Operation* terminator = block->getTerminator(); + const auto results = terminator->getOperands(); + rewriter.inlineBlockBefore(block, operation, ifOperation->getQubits()); + rewriter.replaceOp(operation, results); + rewriter.eraseOp(terminator); + std::ranges::replace(worklist, terminator, static_cast(nullptr)); +} + /** * Handles a constant operation, meaning it is propagated through the union * table. @@ -390,53 +412,50 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, PatternRewriter& rewriter, std::span& worklist) { const Value condition = op->getCondition(); - // TODO: Always/Never executed + // Remove branching if value is always or never true if (ut->isClassicalValueAlwaysTrue(condition)) { op->elseBlock()->walk([&](Operation* innerOp) { std::ranges::replace(worklist, innerOp, static_cast(nullptr)); }); - const auto operation = op->getOperation(); Block* block = &op->getThenRegion().front(); - Operation* terminator = block->getTerminator(); - const auto results = terminator->getOperands(); - rewriter.inlineBlockBefore(block, operation, op->getQubits()); - rewriter.replaceOp(operation, results); - rewriter.eraseOp(terminator); - std::ranges::replace(worklist, terminator, - static_cast(nullptr)); + inlineBranchBlock(op, block, worklist, rewriter); return WalkResult::advance(); } + if (ut->isClassicalValueAlwaysFalse(condition)) { + op->thenBlock()->walk([&](Operation* innerOp) { + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); - const auto& thenBlock = op->thenBlock(); - const auto& elseBlock = op->elseBlock(); - const bool thenEmpty = thenBlock->getOperations().size() <= 1; - const bool elseEmpty = elseBlock->getOperations().size() <= 1; + Block* block = &op->getElseRegion().front(); + inlineBranchBlock(op, block, worklist, rewriter); + + return WalkResult::advance(); + } + + Block* thenBlock = op->thenBlock(); + Block* elseBlock = op->elseBlock(); + bool thenEmpty = thenBlock->getOperations().size() <= 1; + bool elseEmpty = elseBlock->getOperations().size() <= 1; const auto targetQubits = op->getQubits(); std::vector targets = {targetQubits.begin(), targetQubits.end()}; - std::vector args; + std::vector thenArgs; + std::vector elseArgs; // propagate through then and else block if (!thenEmpty) { for (const Value arg : thenBlock->getArguments()) { - args.push_back(arg); + thenArgs.push_back(arg); } - ut->replaceValuesGlobally(targets, args); - targets = args; + ut->replaceValuesGlobally(targets, thenArgs); std::vector newWorklist; + // Create a new worklist to iterate over the inner instructions op->thenBlock()->walk([&](Operation* innerOp) { newWorklist.push_back(innerOp); - // Propagating values in order to assign the right values to the right - // result values - const auto input = innerOp->getOperands(); - const auto output = innerOp->getResults(); - for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { - std::ranges::replace(targets, input[i], output[i]); - } std::ranges::replace(worklist, innerOp, static_cast(nullptr)); }); std::span wl = {newWorklist.begin(), newWorklist.end()}; @@ -449,25 +468,23 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (resThen.failed()) { return WalkResult::interrupt(); } - args.clear(); + op->thenBlock()->walk([&](Operation* innerOp) { + const auto input = innerOp->getOperands(); + const auto output = innerOp->getResults(); + for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { + std::ranges::replace(thenArgs, input[i], output[i]); + } + }); } if (!elseEmpty) { for (const Value arg : elseBlock->getArguments()) { - args.push_back(arg); + elseArgs.push_back(arg); } - ut->replaceValuesGlobally(targets, args); - targets = args; + ut->replaceValuesGlobally(thenArgs.empty() ? targets : thenArgs, elseArgs); std::vector newWorklist; op->elseBlock()->walk([&](Operation* innerOp) { newWorklist.push_back(innerOp); - // Propagating values in order to assign the right values to the right - // result values - const auto input = innerOp->getOperands(); - const auto output = innerOp->getResults(); - for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { - std::ranges::replace(targets, input[i], output[i]); - } std::ranges::replace(worklist, innerOp, static_cast(nullptr)); }); std::span wl = {newWorklist.begin(), newWorklist.end()}; @@ -480,10 +497,71 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (resElse.failed()) { return WalkResult::interrupt(); } + op->elseBlock()->walk([&](Operation* innerOp) { + // Propagating values in order to assign the right values to the right + // result values + const auto input = innerOp->getOperands(); + const auto output = innerOp->getResults(); + for (unsigned int i = 0; i < std::min(input.size(), output.size()); ++i) { + std::ranges::replace(elseArgs, input[i], output[i]); + } + }); } const auto resultQubits = op->getResults(); std::vector results = {resultQubits.begin(), resultQubits.end()}; - ut->replaceValuesGlobally(targets, results); + + thenBlock = op->thenBlock(); + elseBlock = op->elseBlock(); + thenEmpty = thenBlock->getOperations().size() <= 1; + elseEmpty = elseBlock->getOperations().size() <= 1; + + // Remove if operation completely if both branches are empty after propagation + if (thenEmpty && elseEmpty) { + // Check that there is no implicit swap in one branch by re-ordered yield + // operands and get order of returned qubits + std::vector order; + bool implicitSwap = false; + if (!thenArgs.empty()) { + for (unsigned int i = 0; i < thenBlock->getArguments().size(); ++i) { + auto it = std::ranges::find(thenArgs, thenBlock->getArguments()[i]); + if (it != thenArgs.end()) { + const unsigned int pos = std::distance(thenArgs.begin(), it); + order.push_back(pos); + } + } + } + if (!elseArgs.empty()) { + for (unsigned int i = 0; i < elseBlock->getArguments().size(); ++i) { + auto it = std::ranges::find(elseArgs, elseBlock->getArguments()[i]); + if (it != elseArgs.end()) { + const unsigned int pos = std::distance(thenArgs.begin(), it); + if (!thenArgs.empty()) { + implicitSwap |= order.at(i) == pos; + } else { + order.push_back(pos); + } + } + } + } + if (implicitSwap) { + throw std::runtime_error("Constant propagation does not allow implicit " + "swapping of qubits in branching."); + } + // remove if Op and replace the values in the module and union table + std::ranges::replace(worklist, *op, static_cast(nullptr)); + for (unsigned int inputQubitIndex = 0; + inputQubitIndex < op->getQubits().size(); ++inputQubitIndex) { + rewriter.replaceAllUsesWith(op->getResults()[order.at(inputQubitIndex)], + op->getQubits()[inputQubitIndex]); + } + std::vector inputQubitVec = {op->getQubits().begin(), + op->getQubits().end()}; + ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, + inputQubitVec); + rewriter.eraseOp(*op); + } else { + ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, results); + } return WalkResult::advance(); } diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index d5b9c08835..a504a29726 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -213,10 +213,17 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { q[1] = programBuilder.x(q[1]); auto [q0, q1] = programBuilder.cx(q[0], q[1]); auto [q01, b0] = programBuilder.measure(q0); - programBuilder.qcoIf(b0, {q01, q1}, [&](const ValueRange args) { - const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); - return SmallVector{qi0, qi1}; - }); + auto qRange01 = programBuilder.qcoIf( + b0, {q01, q1}, + [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); + return SmallVector{qi0, qi1}; + }, + [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); + return SmallVector{qi0, qi1}; + }); + programBuilder.y(qRange01[1]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -224,6 +231,44 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { qRef[1] = referenceBuilder.x(qRef[1]); auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); referenceBuilder.measure(qRef0); + referenceBuilder.y(qRef1); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that a swap of the qubits in a branch is + * considered during propagation. + */ +TEST_F(QCOConstantPropagationTest, testBranchHasASwap) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.x(q[1]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q01, b0] = programBuilder.measure(q0); + auto qRange01 = programBuilder.qcoIf( + b0, {q01, q1}, + [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); + return SmallVector{qi1, qi0}; + }, + [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); + return SmallVector{qi1, qi0}; + }); + programBuilder.y(qRange01[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.x(qRef[1]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + referenceBuilder.y(qRef01); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); @@ -327,7 +372,7 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveClassicalConditional) { module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(1); - qRef[0] = referenceBuilder.x(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); referenceBuilder.qcoIf( bRef0, {qRef0}, From cd1ff0a654a11abf9314b8633189f25d50730224 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 15:32:18 +0200 Subject: [PATCH 120/131] :construction: First draft of creating branches --- .../Optimizations/ConstantPropagation.cpp | 30 +++++++++++++++++++ .../ConstantPropagation/UnionTable.cpp | 3 -- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 76227b6b6e..3536a2c206 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -728,6 +729,35 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, *op = removeCtrlsOfGate(op, ctrlsToMod.quantumCtrlsToRemove, rewriter, worklist); } + if (!ctrlsToMod.classicalPosCtrlsToAdd.empty() || + !ctrlsToMod.classicalNegCtrlsToAdd.empty()) { + Value condition = *ctrlsToMod.classicalPosCtrlsToAdd.begin(); + ValueRange insertedQubits = op->getInputQubits(); + const SmallVector locs(insertedQubits.size(), op->getLoc()); + auto newIfOp = + IfOp::create(rewriter, op->getLoc(), condition, insertedQubits); + + auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, + newIfOp->getResultTypes(), locs); + + rewriter.setInsertionPointToStart(thenBlock); + IRMapping map; + for (auto [originalInput, ifArgs] : + llvm::zip(insertedQubits, thenBlock->getArguments())) { + map.map(originalInput, ifArgs); + } + auto thenClone = rewriter.clone(*op->getOperation(), map); + + YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); + + auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, + newIfOp->getResultTypes(), locs); + YieldOp::create(rewriter, op->getLoc(), elseBlock->getArguments()); + + rewriter.replaceOp(op->getOperation(), newIfOp.getResults()); + + return WalkResult::advance(); + } auto body = op->getBodyUnitary(); // Make sure that the right qubits in the right order are passed to the diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 18930e4a9c..bbb2ef79d0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -518,9 +518,6 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, bool alwaysOne = true; bool alwaysZero = true; for (const auto& hs : valuesToEntries.at(qCtrl)->states) { - if (!alwaysOne) { - break; - } if (alwaysZero && !hs.isQubitAlwaysZero(qIndex)) { alwaysZero = false; } From e05c4332bb89cfd1c80d134e2e38d1ff871a74ed Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 17:16:16 +0200 Subject: [PATCH 121/131] :construction: Create branches during constant propagation --- .../Optimizations/ConstantPropagation.cpp | 94 +++++++++++++------ 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 3536a2c206..8a45cef6f9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -535,7 +535,7 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, for (unsigned int i = 0; i < elseBlock->getArguments().size(); ++i) { auto it = std::ranges::find(elseArgs, elseBlock->getArguments()[i]); if (it != elseArgs.end()) { - const unsigned int pos = std::distance(thenArgs.begin(), it); + const unsigned int pos = std::distance(elseArgs.begin(), it); if (!thenArgs.empty()) { implicitSwap |= order.at(i) == pos; } else { @@ -567,6 +567,57 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, return WalkResult::advance(); } +/** + * Puts the given operation into a classical branch and propagates the branch. + * + * @param ut Union table which contains the current quantum state. + * @param op The operation to be put in a classical branch. + * @param posClassicalCtrls The positive classical controls considered in the + * operation. + * @param negClassicalCtrls The negative classical controls considered in the + * operation. + * @param ctrlsToMod The controls which need to be used in the branch. + * @param rewriter The used rewriter. + * @param worklist The worklist which contains the operations that are iterated + * through. + * @return Whether the propagation of the new operations in a branch succeeded. + */ +WalkResult putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + controlsToModify ctrlsToMod, + PatternRewriter& rewriter, + std::span& worklist) { + const Value condition = *ctrlsToMod.classicalPosCtrlsToAdd.begin(); + ValueRange insertedQubits = op.getInputQubits(); + const SmallVector locs(insertedQubits.size(), op->getLoc()); + auto newIfOp = + IfOp::create(rewriter, op->getLoc(), condition, insertedQubits); + + auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, + newIfOp->getResultTypes(), locs); + + rewriter.setInsertionPointToStart(thenBlock); + IRMapping map; + for (auto [originalInput, ifArgs] : + llvm::zip(insertedQubits, thenBlock->getArguments())) { + map.map(originalInput, ifArgs); + } + auto thenClone = rewriter.clone(*op.getOperation(), map); + + YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); + + auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, + newIfOp->getResultTypes(), locs); + YieldOp::create(rewriter, op->getLoc(), elseBlock->getArguments()); + + rewriter.replaceAllUsesWith(op.getOutputQubits(), newIfOp.getResults()); + rewriter.replaceOp(op.getOperation(), newIfOp.getResults()); + + return handleIfOp(ut, &newIfOp, posClassicalCtrls, negClassicalCtrls, + rewriter, worklist); +} + /** * Handles a unitary gate, meaning it is propagated through the union table. * @@ -714,6 +765,7 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, } else { ctrlsToMod.classicalNegCtrlsToAdd.insert(value); } + ctrlsToMod.quantumCtrlsToRemove.insert(qCtrl); break; } } @@ -722,41 +774,23 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, if (!ctrlsToMod.quantumCtrlsToRemove.empty()) { if (ctrlsToMod.quantumCtrlsToRemove.size() == op->getNumControls()) { auto newOp = removeAllCtrlsOfGate(op, rewriter, worklist); + if (!ctrlsToMod.classicalPosCtrlsToAdd.empty() || + !ctrlsToMod.classicalNegCtrlsToAdd.empty()) { + return putOperationIntoBranch(ut, newOp, posClassicalCtrls, + negClassicalCtrls, ctrlsToMod, rewriter, + worklist); + } return handleUncontrolledUnitary(ut, &newOp, posClassicalCtrls, negClassicalCtrls, rewriter, worklist); } - *op = removeCtrlsOfGate(op, ctrlsToMod.quantumCtrlsToRemove, rewriter, worklist); - } - if (!ctrlsToMod.classicalPosCtrlsToAdd.empty() || - !ctrlsToMod.classicalNegCtrlsToAdd.empty()) { - Value condition = *ctrlsToMod.classicalPosCtrlsToAdd.begin(); - ValueRange insertedQubits = op->getInputQubits(); - const SmallVector locs(insertedQubits.size(), op->getLoc()); - auto newIfOp = - IfOp::create(rewriter, op->getLoc(), condition, insertedQubits); - - auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, - newIfOp->getResultTypes(), locs); - - rewriter.setInsertionPointToStart(thenBlock); - IRMapping map; - for (auto [originalInput, ifArgs] : - llvm::zip(insertedQubits, thenBlock->getArguments())) { - map.map(originalInput, ifArgs); + if (!ctrlsToMod.classicalPosCtrlsToAdd.empty() || + !ctrlsToMod.classicalNegCtrlsToAdd.empty()) { + return putOperationIntoBranch(ut, *op, posClassicalCtrls, + negClassicalCtrls, ctrlsToMod, rewriter, + worklist); } - auto thenClone = rewriter.clone(*op->getOperation(), map); - - YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); - - auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, - newIfOp->getResultTypes(), locs); - YieldOp::create(rewriter, op->getLoc(), elseBlock->getArguments()); - - rewriter.replaceOp(op->getOperation(), newIfOp.getResults()); - - return WalkResult::advance(); } auto body = op->getBodyUnitary(); From 4c3f9ccf56c243e53018110ef94672e3043f53c9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 17:57:19 +0200 Subject: [PATCH 122/131] :test_tube: Added tests for movement of measurement --- .../test_qco_constant_propagation.cpp | 76 +++++++++---------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index a504a29726..7038d44008 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -215,10 +215,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { auto [q01, b0] = programBuilder.measure(q0); auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, - [&](const ValueRange args) { - const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); - return SmallVector{qi0, qi1}; - }, + [&](const ValueRange args) { return SmallVector{args[0], args[1]}; }, [&](const ValueRange args) { const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); return SmallVector{qi0, qi1}; @@ -240,43 +237,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } -/** - * @brief Test: This test checks that a swap of the qubits in a branch is - * considered during propagation. - */ -TEST_F(QCOConstantPropagationTest, testBranchHasASwap) { - auto q = programBuilder.allocQubitRegister(3); - q[0] = programBuilder.h(q[0]); - q[1] = programBuilder.x(q[1]); - auto [q0, q1] = programBuilder.cx(q[0], q[1]); - auto [q01, b0] = programBuilder.measure(q0); - auto qRange01 = programBuilder.qcoIf( - b0, {q01, q1}, - [&](const ValueRange args) { - const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); - return SmallVector{qi1, qi0}; - }, - [&](const ValueRange args) { - const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); - return SmallVector{qi1, qi0}; - }); - programBuilder.y(qRange01[0]); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(3); - qRef[0] = referenceBuilder.h(qRef[0]); - qRef[1] = referenceBuilder.x(qRef[1]); - auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); - auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); - referenceBuilder.y(qRef01); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} - /** * @brief Test: This test checks that gates are unconditionally applied if the * bit they depend on is always zero. @@ -402,7 +362,8 @@ TEST_F(QCOConstantPropagationTest, q[0] = programBuilder.h(q[0]); auto [q0, q1] = programBuilder.cx(q[0], q[1]); programBuilder.measure(q0); - programBuilder.cx(q1, q[2]); + auto [q11, q2] = programBuilder.cx(q1, q[2]); + programBuilder.h(q11); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -413,6 +374,7 @@ TEST_F(QCOConstantPropagationTest, const auto qi0 = referenceBuilder.x(args[0]); return SmallVector{qi0}; }); + referenceBuilder.h(qRef1); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); @@ -665,6 +627,36 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveMultiQubitPhaseGate) { ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that a quantum conditional is replaced by a + * classical if a qubit and a classical bit are equivalent. + */ +TEST_F(QCOConstantPropagationTest, testMoveMeasurementToFront) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q11, q2] = programBuilder.cx(q1, q[2]); + programBuilder.h(q11); + programBuilder.measure(q0); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef0); + referenceBuilder.qcoIf(bRef0, {qRef[2]}, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[0]); + return SmallVector{qi0}; + }); + referenceBuilder.h(qRef1); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } \ No newline at end of file From bf7b1afd87a314c35ae7deb6746f97ded298f444 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 18:07:07 +0200 Subject: [PATCH 123/131] :construction: Added moving measurement in front --- .../Optimizations/ConstantPropagation.cpp | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 8a45cef6f9..dda3b00295 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -107,23 +107,24 @@ bool isEntryPoint(const func::FuncOp op) { * @param module The module which contains the operations * @param ctx The MLIR context */ -bool moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { +void moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { bool changed = false; - PatternRewriter rewriter(ctx); - module.walk([&](MeasureOp op) { - Operation* previousInstruction = op.getQubitIn().getDefiningOp(); - Operation* previousNode = op->getPrevNode(); - while (isa(previousNode) && - previousInstruction != previousNode) { - previousNode = previousNode->getPrevNode(); - } - if (previousNode != previousInstruction) { - rewriter.moveOpAfter(op, previousInstruction); - changed = true; - } - }); - - return changed; + do { + changed = false; + PatternRewriter rewriter(ctx); + module.walk([&](MeasureOp op) { + Operation* previousInstruction = op.getQubitIn().getDefiningOp(); + Operation* previousNode = op->getPrevNode(); + while (isa(previousNode) && + previousInstruction != previousNode) { + previousNode = previousNode->getPrevNode(); + } + if (previousNode != previousInstruction) { + rewriter.moveOpAfter(op, previousInstruction); + changed = true; + } + }); + } while (changed); } /** @@ -1020,6 +1021,8 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * @return Success if constant propagation has been applied successfully */ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { + moveMeasurementsToFront(module, ctx); + PatternRewriter rewriter(ctx); /// Prepare work-list. From ffe629386e6c7d735005d7f140402e8f38934845 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 18:41:59 +0200 Subject: [PATCH 124/131] :construction: Added handling of classical operations --- .../ConstantPropagation/UnionTable.hpp | 26 ++++++---- .../Optimizations/ConstantPropagation.cpp | 49 +++++-------------- .../ConstantPropagation/UnionTable.cpp | 36 ++++++++++++++ 3 files changed, 65 insertions(+), 46 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 1608d563d1..6054fcc629 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -318,6 +318,23 @@ class UnionTable { std::span negCtrlsClassical = {}, std::span params = {}); + /** + * @brief This method propagates a classical operation. + * + * + * @param op The operation to be applied. + * @param targets An array of the Values of the target classical values. + * @param results The value of the result. + * @param posCtrlsClassical An array of the values of the ctrl bits. + * @param negCtrlsClassical An array of the values of the negative ctrl bits. + * @throws invalid_argument if a value is given, but is not found in the + * existing ones. + */ + void propagateClassicalOperation(Operation* op, std::span targets, + std::span results, + std::span posCtrlsClassical = {}, + std::span negCtrlsClassical = {}); + /** * @brief This method applies a measurement. * @@ -357,15 +374,6 @@ class UnionTable { std::span posCtrlsClassical = {}, std::span negCtrlsClassical = {}); - /** - * This method replaces all instances of a value in the union table by - * another. - * - * @param from The Value that is being replaced. - * @param to The Value the replaced Value becomes. - */ - void replaceValues(Value from, Value to); - /** * @brief This method propagates a qubit alloc. * diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index dda3b00295..8fc03ec905 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -905,9 +905,6 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, ut->propagateQubitAlloc(op->getOperand(0)); return WalkResult::advance(); }) - // .Case( - // [&](const StaticOp op) { return handleStaticOp(ut, - // op); }) .Case([&]([[maybe_unused]] SinkOp op) { return WalkResult::advance(); }) @@ -918,10 +915,6 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, .Case([&]([[maybe_unused]] YieldOp op) { return WalkResult::advance(); }) - // /// built-in Dialect - // .Case([&]([[maybe_unused]] ModuleOp op) { - // return WalkResult::advance(); - // }) // qtensor dialect .Case([&]([[maybe_unused]] qtensor::AllocOp op) { return WalkResult::advance(); @@ -939,40 +932,11 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, [&]([[maybe_unused]] qtensor::InsertOp op) { return WalkResult::advance(); }) - // memref Dialect - // .Case([&](const memref::AllocOp op) { - // addedAtLeastOneQubit = true; - // ut->propagateQubitAlloc(op->getOpResult(0)); - // return WalkResult::advance(); - // }) - // .Case([&](const memref::AllocaOp op) { - // return handleAlloca(ut, op); - // }) - // .Case( - // [&]([[maybe_unused]] const memref::DeallocOp op) { - // return WalkResult::advance(); - // }) - // .Case([&](const memref::LoadOp op) { - // addedAtLeastOneQubit = true; - // return handleLoad(ut, op); - // }) - // .Case([&](const memref::StoreOp op) { - // return handleStore(ut, op, posClassicalCtrls, - // negClassicalCtrls); - // }) // arith dialect .Case([&](const arith::ConstantOp op) { return handleConstant(ut, op, posClassicalCtrls, negClassicalCtrls); }) - // .Case( - // [&](const arith::XOrIOp op) { return - // handleXOrIOp(qcp, op); - // }) - // .Case( - // [&](const arith::AndIOp op) { return - // handleAndIOp(qcp, op); - // }) // func Dialect .Case([&](const func::FuncOp op) { if (!isEntryPoint(op)) { @@ -985,7 +949,18 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, .Case([&]([[maybe_unused]] func::ReturnOp op) { return WalkResult::advance(); }) - .Default([](auto) { + .Default([ut, posClassicalCtrls, negClassicalCtrls](Operation* op) { + if (llvm::isa(op->getDialect())) { + std::vector operands = {op->getOperands().begin(), + op->getOperands().end()}; + std::vector results = {op->getResults().begin(), + op->getResults().end()}; + ut->propagateClassicalOperation(op, operands, results, + posClassicalCtrls, + negClassicalCtrls); + return WalkResult::advance(); + } + throw std::runtime_error("Unsupported operation"); return WalkResult::interrupt(); }); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index bbb2ef79d0..729f53c61d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -157,6 +157,42 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); } +void UnionTable::propagateClassicalOperation( + Operation* op, std::span targets, std::span results, + std::span posCtrlsClassical, std::span negCtrlsClassical) { + const auto result = results[0]; + if (!valuesToEntries.contains(result)) { + if (op->getResult(0).getType().isFloat()) { + propagateDoubleAlloc(result, 0.0); + } else { + propagateIntAlloc(result, 0); + } + } + const std::set participatingEntries = + collectParticipatingEntries(targets, results, posCtrlsClassical, + negCtrlsClassical); + + try { + unifyEntries(participatingEntries); + } catch (std::domain_error&) { + putEntriesToTop(participatingEntries); + return; + } + const Value operand1 = targets[0]; + const Value operand2 = targets.size() > 1 ? targets[1] : nullptr; + const Value operand3 = targets.size() > 2 ? targets[2] : nullptr; + + const auto ute = valuesToEntries.at(*targets.begin()); + for (auto hs : ute->states) { + hs.propagateClassicalOperation(op, result, operand1, operand2, operand3, + posCtrlsClassical, negCtrlsClassical); + if (hs.isHybridStateTop()) { + putEntriesToTop({*ute}); + break; + } + } +} + void UnionTable::propagateMeasurement( const Value quantumTarget, const Value newQuantumValue, const Value classicalTarget, const std::span posCtrlsClassical, From 774e859fbd8aaaa1c5c0a3cd3bfaacb4f8397fde Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:30:37 +0200 Subject: [PATCH 125/131] :construction: Creating then and else branches --- .../Optimizations/ConstantPropagation.cpp | 48 +++++++++++++------ .../test_qco_constant_propagation.cpp | 38 ++++++++++----- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 8fc03ec905..164f3867ae 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -570,6 +570,8 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, /** * Puts the given operation into a classical branch and propagates the branch. + * Only supports one new condition, i.e. only considers the first positive or + * negative new controls. * * @param ut Union table which contains the current quantum state. * @param op The operation to be put in a classical branch. @@ -589,28 +591,46 @@ WalkResult putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, controlsToModify ctrlsToMod, PatternRewriter& rewriter, std::span& worklist) { - const Value condition = *ctrlsToMod.classicalPosCtrlsToAdd.begin(); + const bool createThenBranch = !ctrlsToMod.classicalPosCtrlsToAdd.empty(); + const Value condition = createThenBranch + ? *ctrlsToMod.classicalPosCtrlsToAdd.begin() + : *ctrlsToMod.classicalNegCtrlsToAdd.begin(); ValueRange insertedQubits = op.getInputQubits(); const SmallVector locs(insertedQubits.size(), op->getLoc()); auto newIfOp = IfOp::create(rewriter, op->getLoc(), condition, insertedQubits); - auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, - newIfOp->getResultTypes(), locs); - - rewriter.setInsertionPointToStart(thenBlock); IRMapping map; - for (auto [originalInput, ifArgs] : - llvm::zip(insertedQubits, thenBlock->getArguments())) { - map.map(originalInput, ifArgs); - } - auto thenClone = rewriter.clone(*op.getOperation(), map); - YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); + if (createThenBranch) { + auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, + newIfOp->getResultTypes(), locs); + rewriter.setInsertionPointToStart(thenBlock); + for (auto [originalInput, ifArgs] : + llvm::zip(insertedQubits, thenBlock->getArguments())) { + map.map(originalInput, ifArgs); + } + auto thenClone = rewriter.clone(*op.getOperation(), map); + YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); - auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, - newIfOp->getResultTypes(), locs); - YieldOp::create(rewriter, op->getLoc(), elseBlock->getArguments()); + auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, + newIfOp->getResultTypes(), locs); + YieldOp::create(rewriter, op->getLoc(), elseBlock->getArguments()); + } else { + auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, + newIfOp->getResultTypes(), locs); + rewriter.setInsertionPointToStart(elseBlock); + for (auto [originalInput, ifArgs] : + llvm::zip(insertedQubits, elseBlock->getArguments())) { + map.map(originalInput, ifArgs); + } + auto elseClone = rewriter.clone(*op.getOperation(), map); + YieldOp::create(rewriter, op->getLoc(), elseClone->getResults()); + + auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, + newIfOp->getResultTypes(), locs); + YieldOp::create(rewriter, op->getLoc(), thenBlock->getArguments()); + } rewriter.replaceAllUsesWith(op.getOutputQubits(), newIfOp.getResults()); rewriter.replaceOp(op.getOperation(), newIfOp.getResults()); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 7038d44008..5f4c364d7c 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -391,10 +391,13 @@ TEST_F(QCOConstantPropagationTest, testEquivalentClassicalAndQuantumControl) { auto q = programBuilder.allocQubitRegister(3); q[0] = programBuilder.h(q[0]); auto [q0, q1] = programBuilder.cx(q[0], q[1]); - programBuilder.measure(q0); + auto [q01, b0] = programBuilder.measure(q0); auto [q11, q2] = programBuilder.cx(q1, q[2]); q[1] = programBuilder.x(q11); - programBuilder.cy(q[1], q2); + auto [q12, q21] = programBuilder.cy(q[1], q2); + programBuilder.x(q01); + programBuilder.y(q12); + programBuilder.h(q21); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -406,14 +409,17 @@ TEST_F(QCOConstantPropagationTest, testEquivalentClassicalAndQuantumControl) { const auto qi0 = referenceBuilder.x(args[0]); return SmallVector{qi0}; }); - referenceBuilder.x(qRef1); - referenceBuilder.qcoIf( + qRef[1] = referenceBuilder.x(qRef1); + const auto qRange21 = referenceBuilder.qcoIf( bRef0, qRange2, [&](const ValueRange args) { return SmallVector{args[0]}; }, [&](const ValueRange args) { const auto qi0 = referenceBuilder.y(args[0]); return SmallVector{qi0}; }); + referenceBuilder.x(qRef01); + referenceBuilder.y(qRef[1]); + referenceBuilder.h(qRange21[0]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); @@ -433,10 +439,13 @@ TEST_F(QCOConstantPropagationTest, testClassicalImpliesQuantum) { q[1] = programBuilder.x(q[1]); auto [q01, q1] = programBuilder.cx(q0, q[1]); auto [q11, q02] = programBuilder.ch(q1, q01); - programBuilder.qcoIf(b0, {q02, q11}, [&](const ValueRange args) { - const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); - return SmallVector{qi0, qi1}; - }); + const auto qRange = + programBuilder.qcoIf(b0, {q02, q11}, [&](const ValueRange args) { + const auto [qi0, qi1] = programBuilder.cx(args[0], args[1]); + return SmallVector{qi0, qi1}; + }); + programBuilder.x(qRange[0]); + programBuilder.y(qRange[1]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); @@ -448,17 +457,20 @@ TEST_F(QCOConstantPropagationTest, testClassicalImpliesQuantum) { const auto qi0 = referenceBuilder.x(args[0]); return SmallVector{qi0}; }); - referenceBuilder.qcoIf( + const auto qRefRange0 = referenceBuilder.qcoIf( bRef0, {qRef0}, [&](const ValueRange args) { return SmallVector{args[0]}; }, [&](const ValueRange args) { const auto qi0 = referenceBuilder.h(args[0]); return SmallVector{qi0}; }); - referenceBuilder.qcoIf(bRef0, qRefRange1, [&](const ValueRange args) { - const auto qi0 = referenceBuilder.x(args[0]); - return SmallVector{qi0}; - }); + const auto qRefRange = referenceBuilder.qcoIf( + bRef0, {qRefRange0[0], qRefRange1[0]}, [&](const ValueRange args) { + const auto qi0 = referenceBuilder.x(args[1]); + return SmallVector{args[0], qi0}; + }); + referenceBuilder.x(qRefRange[0]); + referenceBuilder.y(qRefRange[1]); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From e83acbdc4d1e48cf5aae1245fe6893b1069b68b6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:41:02 +0200 Subject: [PATCH 126/131] :construction: Added usage of parameters --- .../Optimizations/ConstantPropagation.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 164f3867ae..ed6d635717 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -1013,9 +1013,15 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * * @param module The module which contains the operations * @param ctx The MLIR context + * @param maxNonzeroAmplitudes The maximum number of non-zero amplitudes in the + * tracted quantum states before reaching top. + * @param maxHybridStates The maximum number of hybrid states which have a + * non-zero probabilty. * @return Success if constant propagation has been applied successfully */ -LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { +LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, + const size_t maxNonzeroAmplitudes, + const size_t maxHybridStates) { moveMeasurementsToFront(module, ctx); PatternRewriter rewriter(ctx); @@ -1033,7 +1039,7 @@ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx) { } // TODO: Take maximum from params - auto ut = UnionTable(16, 4); + auto ut = UnionTable(maxNonzeroAmplitudes, maxHybridStates); std::span wl = {worklist.begin(), worklist.end()}; @@ -1051,7 +1057,8 @@ struct ConstantPropagation final using ConstantPropagationBase::ConstantPropagationBase; void runOnOperation() override { - if (failed(applyCP(getOperation(), &getContext()))) { + if (failed(applyCP(getOperation(), &getContext(), maximumNonzeroAmplitudes, + maximumHybridStates))) { signalPassFailure(); } } From a87875a11ccd902be175c3f6d9bb0ae1d6f50baa Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:54:06 +0200 Subject: [PATCH 127/131] :construction: Added pipeline frame --- .../Optimizations/ConstantPropagation.cpp | 2 +- mlir/tools/mqt-cc/mqt-cc.cpp | 3 ++- .../Compiler/test_compiler_pipeline.cpp | 25 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index ed6d635717..3e1bfc23c3 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -922,7 +922,7 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, }) .Case([&](const AllocOp op) { addedAtLeastOneQubit = true; - ut->propagateQubitAlloc(op->getOperand(0)); + ut->propagateQubitAlloc(op->getResult(0)); return WalkResult::advance(); }) .Case([&]([[maybe_unused]] SinkOp op) { diff --git a/mlir/tools/mqt-cc/mqt-cc.cpp b/mlir/tools/mqt-cc/mqt-cc.cpp index 9616462e79..e6c26281da 100644 --- a/mlir/tools/mqt-cc/mqt-cc.cpp +++ b/mlir/tools/mqt-cc/mqt-cc.cpp @@ -81,7 +81,7 @@ static cl::opt disableMergeSingleQubitRotationGates( static cl::opt enableHadamardLifting( "hadamard-lifting", cl::desc("Apply Hadamard lifting during optimization"), cl::init(false)); -static cl::opt enableHadamardLifting( +static cl::opt enableConstantPropagation( "constant-propagation", cl::desc("Apply constant propagation during optimization"), cl::init(false)); @@ -185,6 +185,7 @@ int main(int argc, char** argv) { config.disableMergeSingleQubitRotationGates = disableMergeSingleQubitRotationGates; config.enableHadamardLifting = enableHadamardLifting; + config.enableConstantPropagation = enableConstantPropagation; // Run the compilation pipeline CompilationRecord record; diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index cdf01202cd..5f0e941ac3 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -246,6 +246,31 @@ TEST_F(CompilerPipelineTest, HadamardLiftingPass) { EXPECT_NE(record.afterQCOCanon, record.afterOptimization); } +/** + * @brief Test: Constant propagation pass is invoked during the optimization + * stage + * + * We run the pipeline with enabled constant propagation and check whether the + * outputs differ, i.e. that the pipeline ran and changed the IR. + * Correctness of the pass is tested in a dedicated test. + */ +TEST_F(CompilerPipelineTest, ConstantPropagationPass) { + auto module = mlir::qc::QCProgramBuilder::build( + context.get(), [&](mlir::qc::QCProgramBuilder& b) { + auto q0 = b.allocQubit(); + auto q1 = b.allocQubit(); + b.x(q0); + b.cx(q0, q1); + }); + ASSERT_TRUE(module); + + mlir::CompilationRecord record; + runPipeline(module.get(), false, true, false, true, record); + + // The outputs must differ, proving the pass ran and transformed the IR + EXPECT_NE(record.afterQCOCanon, record.afterOptimization); +} + INSTANTIATE_TEST_SUITE_P( QuantumComputationPipelineProgramsTest, CompilerPipelineTest, testing::Values( From 74d198b05df0a1b1b0a1ac14c2ae701408c70208 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 21:46:11 +0200 Subject: [PATCH 128/131] :construction: Adapted to changes from main --- .../Optimizations/ConstantPropagation.cpp | 156 +++++++++--------- .../ConstantPropagation/test_hybridState.cpp | 1 - .../ConstantPropagation/test_quantumState.cpp | 2 - .../ConstantPropagation/test_unionTable.cpp | 2 - 4 files changed, 80 insertions(+), 81 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 3e1bfc23c3..29a28ba03d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -30,42 +30,49 @@ namespace mlir::qco { namespace { #define CREATE_OP_CASE_NO_PARAMS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0]); \ }) #define CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], qubitsIn[1]); \ }) #define CREATE_OP_CASE_ONE_PARAM(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], \ + gate.getTheta()); \ }) #define CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1], \ - params[0]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], qubitsIn[1], \ + gate.getTheta()); \ }) #define CREATE_OP_CASE_TWO_PARAMS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0], \ - params[1]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], \ + gate.getTheta(), gate.getPhi()); \ }) #define CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], qubitsIn[1], \ - params[0], params[1]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], qubitsIn[1], \ + gate.getTheta(), gate.getPhi()); \ + }) + +#define CREATE_OP_CASE_PLUS_MINUS_OPS(opType) \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], qubitsIn[1], \ + gate.getTheta(), gate.getBeta()); \ }) #define CREATE_OP_CASE_THREE_PARAMS(opType) \ - .Case([&](auto) { \ - return opType::create(rewriter, op->getLoc(), qubitsIn[0], params[0], \ - params[1], params[2]); \ + .Case([&](opType gate) { \ + return opType::create(rewriter, gate.getLoc(), qubitsIn[0], \ + gate.getTheta(), gate.getPhi(), gate.getLambda()); \ }) /** @@ -260,51 +267,49 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, * @param op The operation whose type and location is used. * @param rewriter The used rewriter. * @param qubitsIn A span of target inputs. - * @param params A span of parameters for the new gate. * @return The newly created gate. */ -Operation* createOperationFromUnitaryOperation(Operation* op, - PatternRewriter& rewriter, - const std::span qubitsIn, - const std::span params) { +Operation* +createOperationFromUnitaryOperation(Operation* op, PatternRewriter& rewriter, + const std::span qubitsIn) { const auto newOp = - mlir::TypeSwitch(op) CREATE_OP_CASE_NO_PARAMS( - IdOp) CREATE_OP_CASE_NO_PARAMS(HOp) CREATE_OP_CASE_NO_PARAMS(XOp) - CREATE_OP_CASE_NO_PARAMS(YOp) CREATE_OP_CASE_NO_PARAMS( - ZOp) CREATE_OP_CASE_NO_PARAMS(SOp) CREATE_OP_CASE_NO_PARAMS(SdgOp) - CREATE_OP_CASE_NO_PARAMS(TOp) CREATE_OP_CASE_NO_PARAMS( - TdgOp) CREATE_OP_CASE_NO_PARAMS(SXOp) - CREATE_OP_CASE_NO_PARAMS(SXdgOp) CREATE_OP_CASE_ONE_PARAM( - RXOp) CREATE_OP_CASE_ONE_PARAM(RYOp) - CREATE_OP_CASE_ONE_PARAM(RZOp) CREATE_OP_CASE_ONE_PARAM( - POp) CREATE_OP_CASE_TWO_PARAMS(ROp) - CREATE_OP_CASE_TWO_PARAMS(U2Op) CREATE_OP_CASE_THREE_PARAMS( - UOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(SWAPOp) - CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS( - iSWAPOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(DCXOp) + mlir::TypeSwitch(op) + .Case([&](U2Op gate) { + return U2Op::create(rewriter, gate.getLoc(), qubitsIn[0], + gate.getPhi(), gate.getLambda()); + }) CREATE_OP_CASE_NO_PARAMS(IdOp) CREATE_OP_CASE_NO_PARAMS(HOp) + CREATE_OP_CASE_NO_PARAMS(XOp) CREATE_OP_CASE_NO_PARAMS( + YOp) CREATE_OP_CASE_NO_PARAMS(ZOp) CREATE_OP_CASE_NO_PARAMS(SOp) + CREATE_OP_CASE_NO_PARAMS(SdgOp) CREATE_OP_CASE_NO_PARAMS( + TOp) CREATE_OP_CASE_NO_PARAMS(TdgOp) + CREATE_OP_CASE_NO_PARAMS(SXOp) CREATE_OP_CASE_NO_PARAMS( + SXdgOp) CREATE_OP_CASE_ONE_PARAM(RXOp) + CREATE_OP_CASE_ONE_PARAM(RYOp) CREATE_OP_CASE_ONE_PARAM( + RZOp) CREATE_OP_CASE_ONE_PARAM(POp) + CREATE_OP_CASE_TWO_PARAMS(ROp) CREATE_OP_CASE_THREE_PARAMS( + UOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(SWAPOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS( - ECROp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RXXOp) - CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RYYOp) + iSWAPOp) CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS(DCXOp) + CREATE_OP_CASE_NO_PARAMS_TWO_QUBITS( + ECROp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS(RXXOp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( - RZXOp) + RYYOp) CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( - RZZOp) - CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( - XXPlusYYOp) - CREATE_OP_CASE_TWO_PARAMS_TWO_QUBITS( - XXMinusYYOp) - .Default( - [&](auto) - -> Operation* { - throw std:: - runtime_error( - "Unsu" - "ppor" - "ted " - "oper" - "atio" - "n"); - }); + RZXOp) + CREATE_OP_CASE_ONE_PARAM_TWO_QUBITS( + RZZOp) + CREATE_OP_CASE_PLUS_MINUS_OPS( + XXPlusYYOp) + CREATE_OP_CASE_PLUS_MINUS_OPS( + XXMinusYYOp) + .Default([&](auto) -> Operation* { + throw std::runtime_error("Unsu" + "ppor" + "ted " + "oper" + "atio" + "n"); + }); return newOp; } @@ -323,20 +328,19 @@ UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, for (const auto& qubitCtrl : op->getInputQubits()) { rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); } + + const auto innerUnitary = + utils::getSoleBodyUnitary(*op->getBody()); + const auto targetInput = op->getInputTargets(); - const auto paramsRange = op->getParameters(); std::vector qubitsIn = {targetInput.begin(), targetInput.end()}; - std::vector params = {paramsRange.begin(), paramsRange.end()}; - const auto newOp = createOperationFromUnitaryOperation( - op->getBodyUnitary(), rewriter, qubitsIn, params); + const auto newOp = + createOperationFromUnitaryOperation(innerUnitary, rewriter, qubitsIn); auto newUnitary = static_cast(newOp); for (const auto inTarget : newUnitary.getInputQubits()) { rewriter.replaceAllUsesExcept( inTarget, newUnitary.getOutputForInput(inTarget), newUnitary); } - for (const auto ctrlQubit : op->getOutputControls()) { - rewriter.replaceAllUsesWith(ctrlQubit, op->getInputForOutput(ctrlQubit)); - } rewriter.eraseOp(*op); std::ranges::replace(worklist, *op, newOp); @@ -369,14 +373,14 @@ CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, newControlIn.push_back(ctrls); } } + const auto innerUnitary = + utils::getSoleBodyUnitary(*op->getBody()); CtrlOp newCtrl = CtrlOp::create( rewriter, op->getLoc(), newControlIn, op->getTargetsIn(), [&](const ValueRange target) { - const auto paramsRange = op->getParameters(); std::vector qubitsIn = {target.begin(), target.end()}; - std::vector params = {paramsRange.begin(), paramsRange.end()}; const auto newOp = createOperationFromUnitaryOperation( - op->getBodyUnitary(), rewriter, qubitsIn, params); + innerUnitary, rewriter, qubitsIn); return SmallVector{newOp->getResults()}; }); @@ -814,7 +818,7 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, } } - auto body = op->getBodyUnitary(); + auto body = utils::getSoleBodyUnitary(*op->getBody()); // Make sure that the right qubits in the right order are passed to the // propagation of the body unitary std::vector targetQubits; @@ -890,10 +894,10 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, if (curr == nullptr) { continue; // Skip erased ops. } - auto n = curr->getName().stripDialect().str(); - std::string oName = - "Op: " + curr->getName().getStringRef().str() + - " dialect: " + curr->getName().getDialectNamespace().str(); + // auto n = curr->getName().stripDialect().str(); + // std::string oName = + // "Op: " + curr->getName().getStringRef().str() + + // " dialect: " + curr->getName().getDialectNamespace().str(); rewriter.setInsertionPoint(curr); @@ -909,8 +913,8 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, negClassicalCtrls, rewriter, worklist); }) - .Case([&](const ResetOp op) { - ut->propagateReset(op->getOperand(0), op->getResult(0), + .Case([&](ResetOp op) { + ut->propagateReset(op.getOperand(), op.getResult(), posClassicalCtrls, negClassicalCtrls); return WalkResult::advance(); }) @@ -920,9 +924,9 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, negClassicalCtrls); return WalkResult::advance(); }) - .Case([&](const AllocOp op) { + .Case([&](AllocOp op) { addedAtLeastOneQubit = true; - ut->propagateQubitAlloc(op->getResult(0)); + ut->propagateQubitAlloc(op.getResult()); return WalkResult::advance(); }) .Case([&]([[maybe_unused]] SinkOp op) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index 9755844506..a7a04fdbb1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -19,7 +19,6 @@ #include #include -#include #include using namespace mlir::qco; diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp index 1314e86c7c..cd672da510 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -17,8 +17,6 @@ #include #include -#include -#include #include #include #include diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index 0263df5eb4..d4d9c99036 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -17,8 +17,6 @@ #include #include -#include - using namespace mlir::qco; class UnionTableTest : public testing::Test { From edd955c4e6976a6de3c007dd6ee4f2ee30ecd209 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:49:55 +0000 Subject: [PATCH 129/131] =?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 --- .../ConstantPropagation/QuantumState.hpp | 2 +- .../ConstantPropagation/UnionTable.hpp | 2 +- .../mlir/Dialect/QCO/Transforms/Passes.td | 27 +++++++++++-------- .../Optimizations/ConstantPropagation.cpp | 2 +- .../Transforms/Optimizations/CMakeLists.txt | 16 ++++++----- .../ConstantPropagation/test_hybridState.cpp | 4 +-- .../ConstantPropagation/test_unionTable.cpp | 14 +++++----- .../test_qco_constant_propagation.cpp | 2 +- 8 files changed, 38 insertions(+), 31 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp index dcbcd420eb..db59ec1251 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -158,7 +158,7 @@ class QuantumState { * measured is set to 0. * * @param target The global index of the qubit to be measured. - * @param reset True if target should be resetted in addition to measured. + * @param reset True if target should be reset in addition to measured. * @return MeasurementResult, containing the probability for the result and * the QuantumStates after measurement. */ diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp index 6054fcc629..d9208b0d7c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -494,7 +494,7 @@ class UnionTable { * posCtrl (negCtrl) qubits/values that are always true (false) are * superfluous. * - * @param qubitCtrls The valuess of the positively controlling qubits. + * @param qubitCtrls The values of the positively controlling qubits. * @param posCtrlsClassical The values of the positively controlling classical * values. * @param negCtrlsClassical The values of the negatively controlling classical diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 9c804c1e4e..0646a8c6be 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -202,16 +202,17 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { let dependentDialects = ["mlir::qco::QCODialect"]; - let summary = "This pass applies constant propagation to a circuit. It " - "assumes that all input qubits are |0>. It propagates the " - "state of the qubits up to a given complexity threshold and " - "removes gates which are superfluous considering the current " - "state. It also replaces quantum control with classical control " - "if possible and moves measurements as far to the front as possible."; + let summary = + "This pass applies constant propagation to a circuit. It " + "assumes that all input qubits are |0>. It propagates the " + "state of the qubits up to a given complexity threshold and " + "removes gates which are superfluous considering the current " + "state. It also replaces quantum control with classical control " + "if possible and moves measurements as far to the front as possible."; let description = [{ This pass applies quantum constant propagation. This optimization routines assumes that the input qubits of the circuits are |0>. It propagates the qubit states and the state of additional classical values through the circuit. - All quantum instructions are removed which are superfluous considering the current state. Additionaly, quantum + All quantum instructions are removed which are superfluous considering the current state. Additionally, quantum controls can be replaced by equivalent classical control. To do this most efficiently, the measurements are moved as far to the front of a circuit as possible. @@ -246,10 +247,14 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { phase gate is removed and replaced by a global phase gate instead. }]; - let options = [Option<"maximumNonzeroAmplitudes", "maximumNonzeroAmplitudes", "std::size_t", "4", - "The maximum number of non-zero amplitudes in the tracted quantum states before reaching top.">, - Option<"maximumHybridStates", "maximumHybridStates", "std::size_t", "4", - "The maximum number of hybrid states which have a non-zero probabilty.">]; + let options = [Option<"maximumNonzeroAmplitudes", "maximumNonzeroAmplitudes", + "std::size_t", "4", + "The maximum number of non-zero amplitudes in the " + "tracted quantum states before reaching top.">, + Option<"maximumHybridStates", "maximumHybridStates", + "std::size_t", "4", + "The maximum number of hybrid states which have a " + "non-zero probability.">]; } #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 29a28ba03d..6005d744f5 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -1020,7 +1020,7 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * @param maxNonzeroAmplitudes The maximum number of non-zero amplitudes in the * tracted quantum states before reaching top. * @param maxHybridStates The maximum number of hybrid states which have a - * non-zero probabilty. + * non-zero probability. * @return Success if constant propagation has been applied successfully */ LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index 93fe5ebae4..10ebaf0870 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -7,13 +7,15 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-optimizations) -add_executable(${target_name} test_qco_hadamard_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp - test_quantum_loop_unroll.cpp - test_qco_constant_propagation.cpp - ConstantPropagation/test_quantumState.cpp - ConstantPropagation/test_hybridState.cpp - ConstantPropagation/test_unionTable.cpp) +add_executable( + ${target_name} + ConstantPropagation/test_hybridState.cpp + ConstantPropagation/test_quantumState.cpp + ConstantPropagation/test_unionTable.cpp + test_qco_constant_propagation.cpp + test_qco_hadamard_lifting.cpp + test_qco_merge_single_qubit_rotation.cpp + test_quantum_loop_unroll.cpp) target_link_libraries( ${target_name} diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp index a7a04fdbb1..6d6f9e5b4a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -195,7 +195,7 @@ TEST_F(HybridStateTest, handleErrorIfTwoManyAmplitudesAreNonzero) { auto hState = HybridState(fourQubits, 2); hState.propagateGate(hOp.getOperation(), vectorThree); hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); - // Error occures here + // Error occurs here hState.propagateGate(hOp.getOperation(), vectorTwo); // Should leave state in TOP hState.propagateGate(sOp.getOperation(), vectorZero); @@ -346,7 +346,7 @@ TEST_F(HybridStateTest, doResetOnTop) { auto hState = HybridState(fourQubits, 2); hState.propagateGate(hOp.getOperation(), vectorThree); hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); - // Error occures here + // Error occurs here hState.propagateGate(hOp.getOperation(), vectorTwo); // Should leave state in TOP hState.addIntegerValue(v1, 3); diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp index d4d9c99036..2c1eceb289 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -759,21 +759,21 @@ TEST_F(UnionTablePropertiesTest, hasAlwaysZeroProbabilityTest) { ut.propagateMeasurement(v4, v6, i0); llvm::DenseMap qubits0; - llvm::DenseMap classicals0; + llvm::DenseMap classics0; llvm::DenseMap qubits1; - llvm::DenseMap classicals1; + llvm::DenseMap classics1; qubits0[v5] = true; qubits0[v6] = true; qubits0[v2] = false; - classicals0[i0] = true; - classicals0[i1] = true; + classics0[i0] = true; + classics0[i1] = true; qubits1[v5] = false; qubits1[v6] = false; qubits1[v2] = true; - classicals1[i1] = false; + classics1[i1] = false; - ASSERT_FALSE(ut.hasAlwaysZeroProbability(qubits0, classicals0)); - ASSERT_TRUE(ut.hasAlwaysZeroProbability(qubits1, classicals1)); + ASSERT_FALSE(ut.hasAlwaysZeroProbability(qubits0, classics0)); + ASSERT_TRUE(ut.hasAlwaysZeroProbability(qubits1, classics1)); } TEST_F(UnionTablePropertiesTest, ZeroIsAlwaysAntecedent) { diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp index 5f4c364d7c..b9241358ee 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_constant_propagation.cpp @@ -671,4 +671,4 @@ TEST_F(QCOConstantPropagationTest, testMoveMeasurementToFront) { EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); -} \ No newline at end of file +} From 44209398b71cc98bbda973f131af8da1d8ac73ff Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 21:53:57 +0200 Subject: [PATCH 130/131] :memo: Added change information to CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd00940b0c..67f297e86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ releases may include breaking changes. ### Added +- ✨ Add a `constant propagation` pass for reducing superfluous quantum resources by propagating the quantum + machine state ([#1845]) ([**@lirem101]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function ([#1826]) ([**@ystade]) - 🐳 Add dev container configuration for consistent local development @@ -594,6 +596,7 @@ changelogs._ +[#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 [#1830]: https://github.com/munich-quantum-toolkit/core/pull/1830 [#1828]: https://github.com/munich-quantum-toolkit/core/pull/1828 From c26efd8a6b8e5af45b4bdf6ab6d121f59d59f000 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 19:56:06 +0000 Subject: [PATCH 131/131] =?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 --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67f297e86d..44faa08e11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ releases may include breaking changes. ### Added -- ✨ Add a `constant propagation` pass for reducing superfluous quantum resources by propagating the quantum - machine state ([#1845]) ([**@lirem101]) +- ✨ Add a `constant propagation` pass for reducing superfluous quantum + resources by propagating the quantum machine state ([#1845]) ([**@lirem101]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function ([#1826]) ([**@ystade]) - 🐳 Add dev container configuration for consistent local development