From 4c54a2ea7f721428b328246e61196d84802fec51 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 26 Mar 2026 16:52:22 +0100 Subject: [PATCH 001/235] :test_tube: Added tests for Hadamard lifting --- .../Transforms/Optimization/CMakeLists.txt | 22 + .../test_qco_hadamard_lifting.cpp | 503 ++++++++++++++++++ 2 files changed, 525 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/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 5eac04fb7f6fe1b6b7fe8904e05c407b1ed54b29 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 27 Mar 2026 09:41:16 +0100 Subject: [PATCH 002/235] :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 4b76cb647868f6ffa981ca50084dd61ae9f020ed Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 27 Mar 2026 15:54:01 +0100 Subject: [PATCH 003/235] :construction: Added framework for hadamard lifting pass --- CMakeLists.txt | 2 + .../Optimization/HadamardLifting.cpp | 140 ++++++++++++++++++ .../Transforms/Optimization/CMakeLists.txt | 9 +- .../test_qco_hadamard_lifting.cpp | 25 ++-- 4 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5710b627c4..6b8b2294cf 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/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 cfd5d3699eadf837a9567e02e4941d63f1cebc96 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 30 Mar 2026 14:01:31 +0200 Subject: [PATCH 004/235] :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 6e28a1ed9fa53772dc2daec5a097850e64ecd7e3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 11:51:37 +0200 Subject: [PATCH 005/235] :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 e7226e33776cb4777f1fcfd64eb9ff76d6f2f78d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 14:29:02 +0200 Subject: [PATCH 006/235] :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 e57efd3a50d0c2e1437928ac18d2ddd9957074ad Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 31 Mar 2026 15:28:37 +0200 Subject: [PATCH 007/235] :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 024a6652c7b66ae0aef39e865a32351d9a94f059 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 12:18:12 +0200 Subject: [PATCH 008/235] :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 1c8dc3d9b2bfca65c57de6d15dc429d22ac9ad50 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 13:28:06 +0200 Subject: [PATCH 009/235] :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 7a7c4f456a36cec07a8fc0c62a76b601dec6a7f0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 16:06:01 +0200 Subject: [PATCH 010/235] :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 a5111168886bfe8d3bbc8e86aa749fa0e5fafcdf Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 16:29:10 +0200 Subject: [PATCH 011/235] :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 a55ca136e4f5d6a8dd7133d2cf2c4ed4a4624ff0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Apr 2026 17:32:12 +0200 Subject: [PATCH 012/235] :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 6dc5439d6878f3a109ed3ca91bd6d1447fac8189 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 08:55:48 +0200 Subject: [PATCH 013/235] =?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 2a4a26ded71aa37501002a88fa436754cde49efb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 09:40:37 +0200 Subject: [PATCH 014/235] :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 4a90aa78bea5a30fb870df9326d5569a1c8f297d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 11:53:43 +0200 Subject: [PATCH 015/235] :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 159cc255f7bbd1cf50e1d8617c46eef409f140b5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:28:56 +0200 Subject: [PATCH 016/235] :construction: Added infrastructure to use hadamard lifting --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b8b2294cf..5710b627c4 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 From 4a8cbe249c2110a9b85a0542c8cc6b0975bed1c7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Apr 2026 17:53:27 +0200 Subject: [PATCH 017/235] :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 f7a56072c8f05915d31719205ff52a5713f84031 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 018/235] =?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 --- .../Optimization/HadamardLifting.cpp | 2 +- .../Transforms/Optimization/CMakeLists.txt | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) 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 9f7328035d45d22853bef2c06349be29ccb66e4e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 11:01:05 +0200 Subject: [PATCH 019/235] :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 4d328fba401e54add6f5aa96d2c826d8517b9a75 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 12:30:08 +0200 Subject: [PATCH 020/235] :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 7b966714e63cdb4568e48d4a040e5495f64109cc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 7 Apr 2026 12:41:56 +0200 Subject: [PATCH 021/235] :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 10a6943d33c0e2b2bff32ca52a5c274878a862ba Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:19:35 +0200 Subject: [PATCH 022/235] :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 00554d4443cbec219b6716c9376eeae5a76beb97 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 12:21:50 +0200 Subject: [PATCH 023/235] :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 b67526c2f24cc5ff245ba91a4cc132d17d12ef97 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 13:02:37 +0200 Subject: [PATCH 024/235] :bug: Controlled Pauli Y cannot be lifted --- .../Optimization/HadamardLifting.cpp | 16 ++++++++--- .../test_qco_hadamard_lifting.cpp | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) 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 af31f5e290..32ca66a1e0 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 @@ -238,6 +238,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 5c119eac252ce09f59f8aa56e33590f9a69643e6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 8 Apr 2026 14:45:41 +0200 Subject: [PATCH 025/235] :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 32ca66a1e0..5a3b99109d 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 @@ -26,9 +26,6 @@ #include #include -#include -#include - namespace { using namespace mlir; @@ -242,7 +239,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 62757af316c321094b38e5afb4938722b4f6a468 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 026/235] :construction: Apply suggestions from code review Co-authored-by: Lukas Burgholzer Signed-off-by: LiRem101 <63499678+LiRem101@users.noreply.github.com> --- .../Optimization/HadamardLifting.cpp | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) 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 288368151beeaab189a5479dc0812a1d49546dbe Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Apr 2026 15:57:34 +0200 Subject: [PATCH 027/235] :construction: Adapated code to suggestions from code review --- .../Optimization/HadamardLifting.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) 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(); } From ddd9a78ac5626069be94bcb734c38deba5ac33c9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Apr 2026 16:32:47 +0200 Subject: [PATCH 028/235] :construction: Implemented changes suggested by review, removed handling of controlled gates --- .../Optimization/HadamardLifting.cpp | 223 ++---------------- 1 file changed, 14 insertions(+), 209 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()); From a3756693b17a36c412f61a2f17b78fa954d7adf5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 13:41:50 +0200 Subject: [PATCH 029/235] :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 5a3b99109d..aa5a22b2eb 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 @@ -70,7 +70,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); @@ -89,6 +90,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()); @@ -155,12 +157,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 f8e0bf49278c67c94424898f0b2b7c0dae6fa5a8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 14:01:22 +0200 Subject: [PATCH 030/235] :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 2c6009737a39ebcd0f08fb03acf1613b50b46731 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 14:10:37 +0200 Subject: [PATCH 031/235] :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 aa5a22b2eb..79bc167b44 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 @@ -241,7 +241,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. + * and the Pauli gate is a Pauli-Y gate. */ TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardOverPauliYGateIfControlled) { auto q = programBuilder.allocQubitRegister(2); From 9a511577e1e3a21eb048332a693b90be1481c93c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 15:23:29 +0200 Subject: [PATCH 032/235] :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 7d35dea125d5bc24c5baa927a1cbbf3fb064f026 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 17:31:25 +0200 Subject: [PATCH 033/235] :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 79bc167b44..3d6192cb65 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 @@ -327,7 +327,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(); @@ -335,7 +335,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(); @@ -457,7 +457,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]); @@ -469,7 +469,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 bb2ed7fef25bb65b3c1e270a44e7dae79ae14979 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Apr 2026 18:21:43 +0200 Subject: [PATCH 034/235] :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 3d6192cb65..ddb52a67b6 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 @@ -521,3 +521,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 fe5c9b9cd8a0a5af7112e1adea7b605c52148294 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 09:58:42 +0200 Subject: [PATCH 035/235] :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 ddb52a67b6..76a967f50b 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 @@ -26,6 +26,8 @@ #include #include +#include + namespace { using namespace mlir; From 00b32dcab10aa4d68295ff62a7f9420fba013344 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 10:54:42 +0200 Subject: [PATCH 036/235] :rotating_light: Fix linter errors --- .../QCO/Transforms/Optimization/HadamardLifting.cpp | 5 ++--- .../Transforms/Optimization/test_qco_hadamard_lifting.cpp | 8 +++----- 2 files changed, 5 insertions(+), 8 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/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp index 76a967f50b..e6e377e138 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 @@ -26,8 +26,6 @@ #include #include -#include - namespace { using namespace mlir; @@ -92,7 +90,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()); @@ -159,14 +157,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 033502392f3a04bf912cd0f3b4e599469dd0006e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 13:14:24 +0200 Subject: [PATCH 037/235] :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 e6e377e138..13c95c1c87 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 @@ -26,6 +26,8 @@ #include #include +#include + namespace { using namespace mlir; @@ -327,7 +329,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(); @@ -335,7 +337,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(); @@ -457,7 +459,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]); @@ -469,7 +471,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]); @@ -536,7 +538,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]); @@ -554,7 +556,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 05f7b2795a0d82ac6dd0b0254f366a9d26d38a4c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:07:33 +0200 Subject: [PATCH 038/235] :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 b40ad9c2600819ff03d80f9331d120383bcb68c0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:17:05 +0200 Subject: [PATCH 039/235] :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 699fea7d52fb6540a4c655d348618e5fc6004702 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Apr 2026 16:24:27 +0200 Subject: [PATCH 040/235] :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 1915c5252497bd9ff3d181aed5c13f12ecc7d66a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 20 Apr 2026 08:43:32 +0200 Subject: [PATCH 041/235] :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 e94d2246f9273450d9041a309d444a2ac2bb3382 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 20 Apr 2026 09:04:58 +0200 Subject: [PATCH 042/235] :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 2f22a200f8f8a86d78fffc3a3116f263e2e82537 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 9 Jun 2026 16:15:18 +0200 Subject: [PATCH 043/235] :bricks: Add files for tracking QuantumStates --- .../ConstantPropagation/QuantumState.hpp | 25 +++++++++++++++++++ .../ConstantPropagation/QuantumState.cpp | 21 ++++++++++++++++ .../Transforms/Optimizations/CMakeLists.txt | 8 +++--- .../ConstantPropagation/test_quantumState.cpp | 21 ++++++++++++++++ 4 files changed, 70 insertions(+), 5 deletions(-) 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 46295f85ea..88abde0e84 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -15,15 +15,13 @@ add_executable( test_qco_remove_dead_gates.cpp test_qco_replace_classical_controls.cpp test_qco_reuse_qubits.cpp - test_quantum_loop_unroll.cpp) + test_quantum_loop_unroll.cpp + ConstantPropagation/test_quantumState.cpp) target_link_libraries( ${target_name} PRIVATE GTest::gtest_main - MLIRControlFlowDialect - MLIRQCODDFunctionality MLIRQCOProgramBuilder - MLIRQCOPrograms MLIRQCOTransforms MLIRQCOUtils MLIRParser @@ -33,6 +31,6 @@ target_link_libraries( LLVMSupport MLIRSupportMQT) -mqt_mlir_configure_unittest_target(${target_name} REQUIRES_EH) +mqt_mlir_configure_unittest_target(${target_name}) gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) 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 e4b8666993b48823f655425b11c54016c37b8a13 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 14:39:48 +0200 Subject: [PATCH 044/235] :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 0f27d85c4bad11cf0f108f2258f99c84db65a288 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:20:09 +0200 Subject: [PATCH 045/235] :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 ffbf98e9209b3769394ca6dc6ecfe89238115fd3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:26:11 +0200 Subject: [PATCH 046/235] =?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 4448c470931c526e375e0e4070daf33cdc724893 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:46:31 +0200 Subject: [PATCH 047/235] :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 b0e09914fd6495587c66a348fd3e6f55fa5c0de5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 10 Jun 2026 15:59:55 +0200 Subject: [PATCH 048/235] :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 6241f6787b81a020429443f73e12ab31f9dee999 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 17:42:30 +0200 Subject: [PATCH 049/235] :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 dff4b837fb957d7705441687a9ea641eb8ec3301 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 23:09:47 +0200 Subject: [PATCH 050/235] :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 49f8829b9e7c6304333651eacd5554a565feceba Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 11 Jun 2026 23:29:23 +0200 Subject: [PATCH 051/235] =?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 bcf06597a5008fed682eb9282dfcfc2c1d5b068d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 09:41:24 +0200 Subject: [PATCH 052/235] :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 88abde0e84..79886f1c32 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -20,7 +20,8 @@ add_executable( 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 31da7709704c5e43c3d3928390c638b899df139a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 16:09:24 +0200 Subject: [PATCH 053/235] :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 d2e5095174362310c216ce5fdd87a39e1d50b48d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 16:21:14 +0200 Subject: [PATCH 054/235] :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 0a78ff8773ae8802056aeb76d20be9ad92223178 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:23:32 +0200 Subject: [PATCH 055/235] :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 fb4e85de078d3daccf986b0ed586fe659d03b50f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:27:39 +0200 Subject: [PATCH 056/235] :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 0098669868bc17919c79bf25d9d3343ab1ceb353 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 17:48:05 +0200 Subject: [PATCH 057/235] :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 b4f1fb5b6679a9222d1071a165b19db39b4b0d2c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 12 Jun 2026 18:01:08 +0200 Subject: [PATCH 058/235] =?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 49879763e3e06b8919c3d85533ed93bbdc064eed Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 16 Jun 2026 21:55:48 +0200 Subject: [PATCH 059/235] =?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 3bab3ce0cd7f8c9b820d8b101432367781571ee9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 17 Jun 2026 12:41:48 +0200 Subject: [PATCH 060/235] =?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 76cd4d04f7ab3bfaf4033853cd5b94b0dd3b4fbb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 17 Jun 2026 16:18:23 +0200 Subject: [PATCH 061/235] =?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 79886f1c32..afce850cd4 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -16,7 +16,8 @@ add_executable( test_qco_replace_classical_controls.cpp test_qco_reuse_qubits.cpp test_quantum_loop_unroll.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 e4630225bab8606a910612c3cb198378c01d8156 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 08:34:08 +0200 Subject: [PATCH 062/235] :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 7374fc598ad506d05512f4c05a68e5fbb5302e3b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 09:47:10 +0200 Subject: [PATCH 063/235] :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 56c7e1c7afe1cd78aac6a6ec5a5dec2ad8eb8eac Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 10:59:08 +0200 Subject: [PATCH 064/235] :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 d95797f7422a1581c89edd74c35909c827b74a0b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 18 Jun 2026 18:24:55 +0200 Subject: [PATCH 065/235] :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 afce850cd4..b6c12c07dc 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -17,7 +17,8 @@ add_executable( test_qco_reuse_qubits.cpp test_quantum_loop_unroll.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 4ef17bb2aca750097eb83b2c01e83380eefec3d9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 11:28:57 +0200 Subject: [PATCH 066/235] :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 062f3bf3ac254384ee0f50dbfd3017e506953c54 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 12:52:41 +0200 Subject: [PATCH 067/235] :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 dae1e8303fc4990f9c8f9e0b34c436a54aa9adbb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 14:44:23 +0200 Subject: [PATCH 068/235] :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 a053ff0eb5510c0cd1a17ce753b19ff72b39557f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 19 Jun 2026 15:12:10 +0200 Subject: [PATCH 069/235] :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 47070a1e63a9e766dbd94b5174b18e1a7a2af609 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 22 Jun 2026 12:49:44 +0200 Subject: [PATCH 070/235] :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 76bdfe7d1c9369c8929ab440706b904e518ca372 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 22 Jun 2026 17:57:40 +0200 Subject: [PATCH 071/235] :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 92116595f4cbcdac89ee1cc3bd38fcee6e814cc7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 23 Jun 2026 19:20:07 +0200 Subject: [PATCH 072/235] :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 f17003bd34e318adf1304bf9181cfc1325890caf Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 24 Jun 2026 17:35:43 +0200 Subject: [PATCH 073/235] :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 760ec3ec61328e33cc39fff5cb9f35a41498b16b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 24 Jun 2026 20:10:33 +0200 Subject: [PATCH 074/235] :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 edd708bba883b4d493c688eaae4b7468fc7783b8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 25 Jun 2026 07:35:02 +0200 Subject: [PATCH 075/235] :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 0f57eae2386b31c2b0437d9642172957fb8f79c2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 16:32:16 +0200 Subject: [PATCH 076/235] :construction: Added infrastructure for constant propagation passes --- .../mlir/Dialect/QCO/Transforms/Passes.td | 52 ++++++++++++++ .../Optimizations/ConstantPropagation.cpp | 67 +++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 897c05d01c..7ff7086c50 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -180,6 +180,58 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { // Optimization Passes //===----------------------------------------------------------------------===// +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.">]; +} + 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 " 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 From c78de89dffb909f3fce9689de44bc9e9c82cb337 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 16:32:46 +0200 Subject: [PATCH 077/235] :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 b6c12c07dc..baeeaf7863 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_constant_propagation.cpp test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp test_qco_merge_single_qubit_rotation.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 762c1b6768c4604b32b5fad51f20917b07f0f15e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 26 Jun 2026 17:10:27 +0200 Subject: [PATCH 078/235] :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 eaedac06c3bc0c3b7419088262adb9a69ec04ca0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 13:34:39 +0200 Subject: [PATCH 079/235] :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 fbcbc0510c2890a59e575a04428bfff7dd38bb9b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 17:44:21 +0200 Subject: [PATCH 080/235] :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 b0b84adf9e49e293c2df6abdcab4841f22e047eb Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 17:45:10 +0200 Subject: [PATCH 081/235] :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 7b2a7e4a52e63cfb8c2aad339398334403d1f9e7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 19:07:03 +0200 Subject: [PATCH 082/235] :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 f9b18cac5f7fd4c99d8824d74d3e5b262b10672d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 29 Jun 2026 19:16:06 +0200 Subject: [PATCH 083/235] :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 14a741d3935068b4d9b5672b5f7b4bbe35db9bcf Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 09:38:34 +0200 Subject: [PATCH 084/235] :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 878b6a363113c8f8566a888f96d06bedc6a7dcd0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 10:14:17 +0200 Subject: [PATCH 085/235] :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 b12db95a03ef1bd6e9581ffa9613e5725eb7a8b5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:32:09 +0200 Subject: [PATCH 086/235] :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 427d7bc59d0c471cc9837b95d25285c27462cda1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:39:13 +0200 Subject: [PATCH 087/235] :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 553ac915d6976314986ded69df3930047a6e5ac1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 13:49:20 +0200 Subject: [PATCH 088/235] :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 aaaa0042ea2ed74ea74a7e2a7142ed50a023791a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 16:44:26 +0200 Subject: [PATCH 089/235] :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 f8df9f91743283457727107562d197e94b3f7258 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 17:38:37 +0200 Subject: [PATCH 090/235] :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 1ac6c4a9c6d8cad4446bbde0d22a708aa7a6f438 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 18:44:28 +0200 Subject: [PATCH 091/235] :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 7ca1cc3562857282f753ba009f96aab9f2e1679f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 30 Jun 2026 21:14:15 +0200 Subject: [PATCH 092/235] =?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 25f13310b026d984ba0d7d519682862a08c79dad Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 07:24:58 +0200 Subject: [PATCH 093/235] :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 fce3a4cb3de8d5e9d9deaab24dc5eb64eae52cf4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 10:04:06 +0200 Subject: [PATCH 094/235] :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 7a25ed389e38851171eb1f206d3af7a856c21fb5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 12:43:41 +0200 Subject: [PATCH 095/235] :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 9d63dc7f0c9e4412efccb8905aca53a9370bccc2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 14:06:53 +0200 Subject: [PATCH 096/235] :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 57da507ef5ec07db2efb262d3b4a79beefdee8b4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 1 Jul 2026 16:23:10 +0200 Subject: [PATCH 097/235] :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 dfd85b74aedcd6616ed5ea5c38eb4d7891cedd00 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 07:44:10 +0200 Subject: [PATCH 098/235] :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 6dbe893a097a8ff8be7c833f5746814d40b2d7a6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 09:24:34 +0200 Subject: [PATCH 099/235] :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 a2c716127cb997f75410db40cba1dc9bdafa7881 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 11:25:49 +0200 Subject: [PATCH 100/235] :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 dfa3786e2d751430b005bfaa001d810a6c42b185 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 15:32:18 +0200 Subject: [PATCH 101/235] :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 cab21cc7a284670ce7ff23f6e2fccd559d9c7014 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 17:16:16 +0200 Subject: [PATCH 102/235] :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 fed2841a38b2e5f07153a102038f96615829fcb5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 17:57:19 +0200 Subject: [PATCH 103/235] :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 2ee5519b88201fc64764d9ef04509f352ac9ca86 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 18:07:07 +0200 Subject: [PATCH 104/235] :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 b3ed30f7541f48740797ee72070193d772e57aea Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 18:41:59 +0200 Subject: [PATCH 105/235] :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 0199caa53d5b11a0a416564e78833a3612c66ab2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:30:37 +0200 Subject: [PATCH 106/235] :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 d0dea4ef323b90458d551834b673669c2192f1a2 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:41:02 +0200 Subject: [PATCH 107/235] :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 ed40d43bd283b91a30bf7e3c47d05f1f06a1a4f0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 19:54:06 +0200 Subject: [PATCH 108/235] :construction: Added pipeline frame --- .../Optimizations/ConstantPropagation.cpp | 2 +- .../Compiler/test_compiler_pipeline.cpp | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) 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/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index d7f929fb0f..a3eb6ea2ae 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -1480,6 +1480,31 @@ TEST_F(CompilerPipelineTest, PopulateDecomposeMultiControlledPipeline) { EXPECT_NE(after, before); } +/** + * @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( NativeQCPrograms, CompilerPipelineTest, testing::Values( From e45f3b5f4b654c220fa75811c9c3b9b893d26eb0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 21:46:11 +0200 Subject: [PATCH 109/235] :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 cb94911b8f4aadfd5433134134b08e624c5eeae0 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 2 Jul 2026 21:53:57 +0200 Subject: [PATCH 110/235] :memo: Added change information to CHANGELOG.md --- CHANGELOG.md | 221 ++++++++------------------------------------------- 1 file changed, 32 insertions(+), 189 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6641b22e2..eb3f7cdb2e 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**]) - ✨ Let a package register a program serializer for a program format through the `mqt.core.qiskit.program_serializers` entry point group ([#2114]) ([**@marcelwa**]) @@ -91,204 +93,62 @@ releases may include breaking changes. - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function - ([#1826]) ([**@ystade**]) + ([#1826]) ([**@ystade]) - 🐳 Add dev container configuration for consistent local development environment ([#1786]) ([**@denialhaag**]) -- ✨ Add two-qubit Weyl (KAK) decomposition and native-gateset synthesis support - ([#1803], [#1832]) ([**@simon1hofmann**], [**@burgholzer**]) -- ✨ Extend the QCO unitary matrix library ([#1774], [#1802], [#1809], [#1814], - [#1850]) ([**@simon1hofmann**], [**@burgholzer**]) - ✨ Add a `fuse-single-qubit-unitary-runs` pass for fusing compile-time single-qubit unitary runs via Euler resynthesis ([#1672]) ([**@simon1hofmann**], [**@burgholzer**]) +- ✨ Add QIR program format support to the DDSIM QDMI Device ([#1766]) + ([**@rturrado**]) +- 🚸 Add [CMake presets] to provide a standardized and reproducible way to + configure builds ([#1660]) ([**@denialhaag**]) - ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive - fixed and parameterized single-qubit gates using quaternions with global-phase - correction ([#1407], [#1674], [#2002], [#2038]) ([**@J4MMlE**], - [**@denialhaag**], [**@MatthiasReumann**], [**@simon1hofmann**]) + rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], + [**@denialhaag**], [**@MatthiasReumann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], - [#1676], [#1706], [#1776], [#1836], [#1934], [#2000], [#2018], [#2105]) - ([**@denialhaag**], [**@burgholzer**]) -- ✨ Add a `place-and-route` pass for mapping scalar- and tensor-allocated - circuits to compiler-target topologies while preserving target site IDs and - materializing routing workspace on demand ([#1537], [#1547], [#1568], [#1581], - [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], - [#1870], [#1904], [#1911], [#1951], [#1997], [#2016], [#2060]) - ([**@MatthiasReumann**], [**@burgholzer**]) -- ✨ Add a pass for qubit reuse in quantum programs, as well as related - auxiliary passes and patterns ([#1705], [#1755], [#1756], [#1923], [#1924], - [#2039]) ([**@DRovara**], [**@burgholzer**], [**@simon1hofmann**]) + [#1676], [#1706], [#1776]) ([**@denialhaag**], [**@burgholzer**]) +- ✨ Add a `place-and-route` pass for mapping circuits to architectures with + restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], + [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@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], [#1603], [#1620], [#1623], - [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], - [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], [#1781], - [#1782], [#1806], [#1807], [#1815], [#1808], [#1824], [#1869], [#1872], - [#1886], [#1914], [#1925], [#1927], [#1935], [#1936], [#1938], [#1975], - [#1976], [#2006], [#2014], [#2015], [#2017], [#2026], [#2028], [#2058], - [#2125]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], - [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], - [**@simon1hofmann**], [**@J4MMlE**]) + [#1570], [#1572], [#1573], [#1580], [#1602], [#1620], [#1623], [#1624], + [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1710], + [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1774], + [#1780], [#1781], [#1782], [#1787], [#1802], [#1803], [#1806], [#1807], + [#1808], [#1809], [#1823], [#1824], [#1830]) ([**@burgholzer**], + [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], + [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) ### Changed -- ⬆️ Update QDMI to version 1.3.3 ([#2168]) ([**@denialhaag**]) -- ⬆️ Update `nanobind` to version 2.15.0 ([#2141]) ([**@denialhaag**]) -- 💥 Prune dead and misleading CoreIR APIs, including renaming the non-garbage - logical output count to `getNoutputQubits()` and `num_output_qubits` ([#2112]) - ([**@simon1hofmann**]) -- ♻️ Simplify Python optional-dependency checks while preserving the Qiskit and - PennyLane availability flags ([#2108]) ([**@simon1hofmann**]) -- 💥 Remove the unused `pybind11` CMake helper and rename - `add_mqt_python_binding_nanobind` to `add_mqt_python_binding` ([#2106]) - ([**@denialhaag**]) -- 💥 Replace the MQT-specific QDMI primitive `options` mappings with explicit - shot and precision defaults ([#2084]) ([**@burgholzer**]) -- 💥 Move Python QDMI entities and the neutral-atom specialization to QDMI - namespaces, expose device registration and opening through - `mqt.core.qdmi.driver`, retain v3 FoMaC compatibility aliases, and let the - Qiskit adapter open stable device IDs directly ([#2074]) ([**@burgholzer**]) -- ⬆️ Update `nanobind` to version 2.14.0 ([#2073]) ([**@denialhaag**]) -- 🛡️ Isolate DDSIM QIR job execution and restrict statevector extraction to - terminal `irreversible` regions of Base-profile programs ([#2036]) - ([**@burgholzer**]) -- 💥 Update the QIR runner for QIR 2.1 entry points and resource management, - with entry-point selection and reproducible multi-shot execution ([#2035]) - ([**@burgholzer**]) -- 💥 Require LLVM/MLIR and QIR support in every MQT Core build and remove the - corresponding build options ([#1953]) ([**@burgholzer**]) +- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], + [#1737]) ([**@denialhaag**]) - ⬆️ Require LLVM 22.1 for C++ library builds ([#1549]) ([**@burgholzer**], [**@denialhaag**]) - 📦 Build MLIR by default for C++ library builds ([#1356]) ([**@burgholzer**], [**@denialhaag**]) -### Fixed - -- 🐛 Distinguish scalar OpenQASM qubits from one-element qubit registers and - reject indexing scalar qubits ([#2157]) ([**@DRovara**], [**@burgholzer**]) -- 🐛 Preserve the original OpenQASM type error when an assignment's right-hand - expression cannot be typed ([#2156]) ([**@DRovara**], [**@burgholzer**]) - ### Removed -- 💥 Remove batch job submission from the QDMI client. `Device::submitJob` now - states that MQT Core does not support batch jobs ([#2148]) ([**@marcelwa**]) -- 💥 Remove the IQM JSON converter `qiskit_to_iqm_json` and the `MoveGate` from - the Qiskit plugin, which [QDMI-on-IQM] now owns ([#2114]) ([**@marcelwa**]) -- 💥 Remove the unused decision-diagram approximation algorithm, including the - `dd/Approximation.hpp` header, `dd::ApproximationMetadata`, and - `dd::approximate`. No replacement is provided ([#2154]) ([**@burgholzer**]) -- 💥 Remove `nlohmann_json` from the public package contract. MQT Core no longer - installs or exports the library, no installed header exposes a `nlohmann` - type, and the decision-diagram statistics report through strings and streams - ([#2138]) ([**@denialhaag**]) -- 💥 Remove the neutral-atom stack, which moves to MQT QMAP. This drops the - neutral-atom computation model, the neutral-atom FoMaC device session, the - neutral-atom QDMI device and its configuration, the `mqt.core.na` Python - module, `AodOperation`, and the `Move`, `Bridge`, `AodActivate`, - `AodDeactivate`, and `AodMove` operation kinds ([#2137]) ([**@denialhaag**]) -- 💥 Remove the random-number generator, seed, and `getGenerator()` method from - `QuantumComputation`; randomized algorithms now own generators initialized - from their seed arguments ([#2111]) ([**@simon1hofmann**]) -- 💥 Remove QDMI device configuration through `[tool.qdmi]` in `pyproject.toml` - and the vendored toml++ header ([#2116]) ([**@denialhaag**]) -- 💥 Remove the FoMaC compatibility name from the C++ and Python QDMI APIs. Use - the `qdmi` C++ namespace, headers, libraries, and CMake targets; the - `mqt.core.qdmi` and `mqt.core.na.qdmi` Python modules; and the module-level - Python driver functions ([#2115]) ([**@burgholzer**]) -- 💥 Remove the legacy `QuantumComputation`-to-MLIR translator and its C++ and - Python compiler inputs. Use OpenQASM, Qiskit circuits, or typed MLIR programs - as compiler inputs ([#2054]) ([**@burgholzer**]) -- 💥 Remove the ZX-calculus library, including the `mqt-core-zx` target, - `MQT::CoreZX` alias, `zx` headers and namespace, and its Boost.Multiprecision - and GMP build support. Equivalence-checking users should use [MQT QCEC]; its - ZX implementation is internal and does not provide a replacement public API. +- 📝 Remove support for generating LaTeX documentation ([#1828]) + ([**@denialhaag**]) - 🔥 Remove the density matrix support from the MQT Core DD package ([#1466]) ([**@burgholzer**]) - 🔥 Remove `datastructures` (`ds`) (sub)library from MQT Core ([#1458]) ([**@burgholzer**]) -## [3.8.0] - 2026-07-30 - -_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#380)._ - -### Added - -- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC, including - explicit text and exact-byte APIs and all standard QDMI program formats - ([#1957]) ([**@burgholzer**]) -- ✨ Add versioned, relocatable configuration and stable-ID registration for - QDMI device libraries, including disabled-ID reservations, fresh device - sessions, idempotent registration, and external-device target metadata - ([#1912]) ([**@burgholzer**]) -- ✨ Add native relative-phase CCX (`rccx`) support across the IR, DD package, - ZX diagrams, OpenQASM import/export, and Python/Qiskit bindings ([#1886], - [#1950]) ([**@simon1hofmann**]) -- ✨ Add support for QDMI child devices to the driver and FoMaC libraries - ([#1897], [#1952]) ([**@burgholzer**]) -- ✨ Add typed custom property and result queries to the C++ and Python FoMaC - libraries ([#1895]) ([**@burgholzer**]) -- ✨ Add support for custom job parameters to C++ and Python FoMaC library - ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) -- ✨ Add labeled and ordered output schemas to the QIR runtime ([#1877]) - ([**@rturrado**]) -- ✨ Add boolean, integer, floating-point, tuple, and array record output - functions to the QIR runtime ([#1799]) ([**@rturrado**]) -- ✨ Add the reusable in-process `MQT::CoreQIRJIT` library and QIR program - format support to the DDSIM QDMI device ([#1766]) ([**@rturrado**]) - -### Changed - -- ⬆️ Raise the minimum supported QDMI version to 1.3.2 ([#1897]) - ([**@burgholzer**]) - -### Removed - -- 🔥 Replace the unstable C++ `Driver::addDynamicDeviceLibrary` and Python - `add_dynamic_device_library` APIs with definition registration and stable-ID - opening ([#1912]) ([**@burgholzer**]) - ### Fixed -- 🐛 Allow MQT Core to be embedded as a CMake subproject without target - collisions and make its bundled QDMI devices individually configurable - ([#1965]) ([**@burgholzer**]) - 🐛 Fix QIR function names for adjoint gates ([#1830]) ([**@denialhaag**]) - -## [3.7.0] - 2026-07-09 - -_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#370)._ - -### Added - -- ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter - ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) -- 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function - ([#1826]) ([**@ystade**]) -- 🚸 Add [CMake presets] to provide a standardized and reproducible way to - configure builds ([#1660]) ([**@denialhaag**]) - -### Changed - -- ⬆️ Update QDMI to version 1.3.2 ([#1873]) ([**@denialhaag**]) -- ♻️ Improve implementation and usability of FoMaC classes ([#1849]) - ([**@MatthiasReumann**]) -- ⬆️ Update `nanobind` to version 2.13.0 ([#1817]) -- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], - [#1737]) ([**@denialhaag**]) - -### Removed - -- 📝 Remove support for generating LaTeX documentation ([#1828]) - ([**@denialhaag**]) - -### Fixed - - 🐛 Fix invalid `prop_type` for `QDMI_DEVICE_PROPERTY_COUPLINGMAP` in QDMI SC Device ([#1842]) ([**@MatthiasReumann**]) @@ -357,7 +217,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#350)._ ### Changed - ⬆️ Update `nanobind` to version 2.12.0 ([#1528]) -- ⬆️ Update QDMI to version 1.3.0 ([#1652]) ([**@burgholzer**]) +- ⬆️ Update QDMI to `v1.3.0` ([#1652]) ([**@burgholzer**]) - 📦 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]) @@ -634,7 +494,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#320)._ ### Added -- 🐍 Start building CPython 3.14 wheels ([#1076]) ([**@denialhaag**]) +- 🐍 Build Python 3.14 wheels ([#1076]) ([**@denialhaag**]) - ✨ Add MQT-internal MLIR dialect conversions ([#1001]) ([**@li-mingbao**]) ### Changed @@ -787,15 +647,13 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#300)._ ## [2.7.0] - 2024-10-08 -_📚 Refer to the -[GitHub Release Notes](https://github.com/munich-quantum-toolkit/core/releases) -for previous changelogs._ +_📚 Refer to the [GitHub Release +Notes](https://github.com/munich-quantum-toolkit/core/releases) for previous +changelogs._ -[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.8.0...HEAD -[3.8.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.8.0 -[3.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.7.0 +[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.6.1...HEAD [3.6.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.1 [3.6.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.0 [3.5.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.1 @@ -927,27 +785,20 @@ for previous changelogs._ [#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 [#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 +[#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 -[#1836]: https://github.com/munich-quantum-toolkit/core/pull/1836 -[#1832]: https://github.com/munich-quantum-toolkit/core/pull/1832 [#1830]: https://github.com/munich-quantum-toolkit/core/pull/1830 [#1828]: https://github.com/munich-quantum-toolkit/core/pull/1828 [#1826]: https://github.com/munich-quantum-toolkit/core/pull/1826 [#1824]: https://github.com/munich-quantum-toolkit/core/pull/1824 [#1823]: https://github.com/munich-quantum-toolkit/core/pull/1823 -[#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 -[#1815]: https://github.com/munich-quantum-toolkit/core/pull/1815 -[#1814]: https://github.com/munich-quantum-toolkit/core/pull/1814 -[#1810]: https://github.com/munich-quantum-toolkit/core/pull/1810 [#1809]: https://github.com/munich-quantum-toolkit/core/pull/1809 [#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808 [#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807 [#1806]: https://github.com/munich-quantum-toolkit/core/pull/1806 -[#1805]: https://github.com/munich-quantum-toolkit/core/pull/1805 [#1803]: https://github.com/munich-quantum-toolkit/core/pull/1803 [#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802 -[#1799]: https://github.com/munich-quantum-toolkit/core/pull/1799 [#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787 [#1786]: https://github.com/munich-quantum-toolkit/core/pull/1786 [#1782]: https://github.com/munich-quantum-toolkit/core/pull/1782 @@ -958,8 +809,6 @@ for previous changelogs._ [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 -[#1756]: https://github.com/munich-quantum-toolkit/core/pull/1756 -[#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 [#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 @@ -974,11 +823,9 @@ for previous changelogs._ [#1710]: https://github.com/munich-quantum-toolkit/core/pull/1710 [#1709]: https://github.com/munich-quantum-toolkit/core/pull/1709 [#1706]: https://github.com/munich-quantum-toolkit/core/pull/1706 -[#1705]: https://github.com/munich-quantum-toolkit/core/pull/1705 [#1702]: https://github.com/munich-quantum-toolkit/core/pull/1702 [#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700 [#1694]: https://github.com/munich-quantum-toolkit/core/pull/1694 -[#1687]: https://github.com/munich-quantum-toolkit/core/pull/1687 [#1676]: https://github.com/munich-quantum-toolkit/core/pull/1676 [#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675 [#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674 @@ -988,7 +835,6 @@ for previous changelogs._ [#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662 [#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660 [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 -[#1648]: https://github.com/munich-quantum-toolkit/core/pull/1648 [#1638]: https://github.com/munich-quantum-toolkit/core/pull/1638 [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 [#1635]: https://github.com/munich-quantum-toolkit/core/pull/1635 @@ -998,7 +844,6 @@ for previous changelogs._ [#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 -[#1603]: https://github.com/munich-quantum-toolkit/core/pull/1603 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 [#1600]: https://github.com/munich-quantum-toolkit/core/pull/1600 [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 @@ -1108,7 +953,6 @@ for previous changelogs._ [#1164]: https://github.com/munich-quantum-toolkit/core/pull/1164 [#1157]: https://github.com/munich-quantum-toolkit/core/pull/1157 [#1151]: https://github.com/munich-quantum-toolkit/core/pull/1151 -[#1150]: https://github.com/munich-quantum-toolkit/core/pull/1150 [#1148]: https://github.com/munich-quantum-toolkit/core/pull/1148 [#1147]: https://github.com/munich-quantum-toolkit/core/pull/1147 [#1140]: https://github.com/munich-quantum-toolkit/core/pull/1140 @@ -1219,7 +1063,6 @@ for previous changelogs._ [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ [Common Changelog]: https://common-changelog.org -[QDMI-on-IQM]: https://github.com/iqm-finland/QDMI-on-IQM [Semantic Versioning]: https://semver.org/spec/v2.0.0.html [munich-quantum-toolkit]: https://github.com/munich-quantum-toolkit [PEP 639]: https://peps.python.org/pep-0639/ From d82739366e43f7cc00f88e06527b99011ba23230 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 111/235] =?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 +- .../ConstantPropagation/test_hybridState.cpp | 4 +-- .../ConstantPropagation/test_unionTable.cpp | 14 +++++----- .../test_qco_constant_propagation.cpp | 2 +- 7 files changed, 29 insertions(+), 24 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 7ff7086c50..c15f5e7ffe 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -182,16 +182,17 @@ def MappingPass : Pass<"place-and-route", "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. @@ -226,10 +227,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.">]; } def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { 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/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 c3d0f305b7f4c7d18ddbd34d0c7f16f430d83614 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 16:38:01 +1000 Subject: [PATCH 112/235] :rotating_light: Fixed linter warnings --- .../Optimizations/ConstantPropagation.cpp | 173 ++++++++++-------- .../ConstantPropagation/HybridState.cpp | 4 +- .../ConstantPropagation/QuantumState.cpp | 5 + .../ConstantPropagation/UnionTable.cpp | 53 +++--- .../ConstantPropagation/test_hybridState.cpp | 13 +- .../ConstantPropagation/test_quantumState.cpp | 1 - .../ConstantPropagation/test_unionTable.cpp | 4 + 7 files changed, 150 insertions(+), 103 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6005d744f5..10a6c25202 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -1,26 +1,47 @@ /* * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. + * Copyright (c) 2025 - 2026 Munich Qua#include + * "mlir/Dialect/QCO/IR/QCOInterfaces.h"ntum 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 "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" #include "mlir/Dialect/QCO/Transforms/Passes.h" #include "mlir/Dialect/QTensor/IR/QTensorOps.h" +#include "mlir/Dialect/Utils/Utils.h" +#include +#include +#include #include +#include +#include +#include #include +#include #include +#include #include #include +#include #include - -#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include namespace mlir::qco { @@ -78,16 +99,17 @@ namespace { /** * @brief Result of checking how do modify a controlled gate. */ -struct controlsToModify { +struct ControlsToModify { llvm::DenseSet quantumCtrlsToRemove; llvm::DenseSet classicalPosCtrlsToAdd; llvm::DenseSet classicalNegCtrlsToAdd; }; -LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, - std::span& worklist, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls); +static LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, + UnionTable* ut, + std::span& worklist, + std::span posClassicalCtrls, + std::span negClassicalCtrls); /** * This method checks whether the func::FuncOp is an entry point to the program. @@ -95,7 +117,7 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * @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) { +static bool isEntryPoint(const func::FuncOp op) { const auto passthroughAttr = op->getAttrOfType("passthrough"); if (!passthroughAttr) { return false; @@ -114,7 +136,7 @@ bool isEntryPoint(const func::FuncOp op) { * @param module The module which contains the operations * @param ctx The MLIR context */ -void moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { +static void moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { bool changed = false; do { changed = false; @@ -140,7 +162,7 @@ void moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { * @param op The qco::UnitaryOpInterface to be removed. * @param rewriter The used rewriter. */ -void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { +static void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { for (const auto outQubit : op->getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); } @@ -153,7 +175,7 @@ void removeOperation(UnitaryOpInterface* op, PatternRewriter& rewriter) { * @param op The qco::CtrlOp to be removed. * @param rewriter The used rewriter. */ -void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter) { +static void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter) { for (const auto outQubit : op->getOutputQubits()) { rewriter.replaceAllUsesWith(outQubit, op->getInputForOutput(outQubit)); } @@ -170,10 +192,10 @@ void removeCtrlOperation(CtrlOp* op, PatternRewriter& rewriter) { * through. * @param rewriter The used rewriter. */ -void inlineBranchBlock(IfOp* ifOperation, Block* block, - std::span& worklist, - PatternRewriter& rewriter) { - const auto operation = ifOperation->getOperation(); +static void inlineBranchBlock(IfOp* ifOperation, Block* block, + std::span& worklist, + PatternRewriter& rewriter) { + auto* const operation = ifOperation->getOperation(); Operation* terminator = block->getTerminator(); const auto results = terminator->getOperands(); rewriter.inlineBlockBefore(block, operation, ifOperation->getQubits()); @@ -194,9 +216,9 @@ void inlineBranchBlock(IfOp* ifOperation, Block* block, * operation. * @return Whether the handling was successfully or interrupted. */ -WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls) { +static 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."); @@ -236,12 +258,12 @@ WalkResult handleConstant(UnionTable* ut, arith::ConstantOp op, * 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, - const std::span negClassicalCtrls, - PatternRewriter& rewriter, - const std::span targetValues) { +static bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, + const std::span ctrlsQuantum, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + const std::span targetValues) { bool addsGlobalPhase = false; if (isa(op) || isa(op) || isa(op) || isa(op) || isa(op) || isa(op)) { @@ -269,10 +291,10 @@ bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, * @param qubitsIn A span of target inputs. * @return The newly created gate. */ -Operation* +static Operation* createOperationFromUnitaryOperation(Operation* op, PatternRewriter& rewriter, const std::span qubitsIn) { - const auto newOp = + auto* const newOp = mlir::TypeSwitch(op) .Case([&](U2Op gate) { return U2Op::create(rewriter, gate.getLoc(), qubitsIn[0], @@ -323,8 +345,9 @@ createOperationFromUnitaryOperation(Operation* op, PatternRewriter& rewriter, * through. * @return The operation without the controls. */ -UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, - std::span& worklist) { +static UnitaryOpInterface +removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, + std::span& worklist) { for (const auto& qubitCtrl : op->getInputQubits()) { rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); } @@ -334,7 +357,7 @@ UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, const auto targetInput = op->getInputTargets(); std::vector qubitsIn = {targetInput.begin(), targetInput.end()}; - const auto newOp = + auto* const newOp = createOperationFromUnitaryOperation(innerUnitary, rewriter, qubitsIn); auto newUnitary = static_cast(newOp); for (const auto inTarget : newUnitary.getInputQubits()) { @@ -358,9 +381,10 @@ UnitaryOpInterface removeAllCtrlsOfGate(CtrlOp* op, PatternRewriter& rewriter, * through. * @return The operation without given controls. */ -CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, - PatternRewriter& rewriter, - std::span& worklist) { +static CtrlOp removeCtrlsOfGate(CtrlOp* op, + const llvm::DenseSet& ctrlsToRemove, + PatternRewriter& rewriter, + std::span& worklist) { for (const auto& qubitCtrl : ctrlsToRemove) { rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); } @@ -379,7 +403,7 @@ CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, rewriter, op->getLoc(), newControlIn, op->getTargetsIn(), [&](const ValueRange target) { std::vector qubitsIn = {target.begin(), target.end()}; - const auto newOp = createOperationFromUnitaryOperation( + auto* const newOp = createOperationFromUnitaryOperation( innerUnitary, rewriter, qubitsIn); return SmallVector{newOp->getResults()}; }); @@ -412,11 +436,11 @@ CtrlOp removeCtrlsOfGate(CtrlOp* op, const llvm::DenseSet& ctrlsToRemove, * 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) { +static WalkResult handleIfOp(UnionTable* ut, IfOp* op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + PatternRewriter& rewriter, + std::span& worklist) { const Value condition = op->getCondition(); // Remove branching if value is always or never true @@ -589,12 +613,12 @@ WalkResult handleIfOp(UnionTable* ut, IfOp* op, * 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) { +static WalkResult +putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + ControlsToModify ctrlsToMod, PatternRewriter& rewriter, + std::span& worklist) { const bool createThenBranch = !ctrlsToMod.classicalPosCtrlsToAdd.empty(); const Value condition = createThenBranch ? *ctrlsToMod.classicalPosCtrlsToAdd.begin() @@ -614,7 +638,7 @@ WalkResult putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, llvm::zip(insertedQubits, thenBlock->getArguments())) { map.map(originalInput, ifArgs); } - auto thenClone = rewriter.clone(*op.getOperation(), map); + auto* thenClone = rewriter.clone(*op.getOperation(), map); YieldOp::create(rewriter, op->getLoc(), thenClone->getResults()); auto* elseBlock = rewriter.createBlock(&newIfOp.getElseRegion(), {}, @@ -628,7 +652,7 @@ WalkResult putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, llvm::zip(insertedQubits, elseBlock->getArguments())) { map.map(originalInput, ifArgs); } - auto elseClone = rewriter.clone(*op.getOperation(), map); + auto* elseClone = rewriter.clone(*op.getOperation(), map); YieldOp::create(rewriter, op->getLoc(), elseClone->getResults()); auto* thenBlock = rewriter.createBlock(&newIfOp.getThenRegion(), {}, @@ -660,13 +684,13 @@ WalkResult putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, * @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, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls, - const std::span targetValues = {}, - const std::span resultValues = {}) { +static WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, + const std::span ctrlsQuantum, + const std::span newCtrlsQuantum, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls, + const std::span targetValues = {}, + const std::span resultValues = {}) { const auto params = op->getParameters(); std::vector paramValues = {params.begin(), params.end()}; @@ -707,11 +731,12 @@ WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, * through. * @return Whether the handling was successfully or interrupted. */ -WalkResult handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls, - PatternRewriter& rewriter, - std::span& worklist) { +static 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()}; @@ -744,11 +769,11 @@ WalkResult handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, * through. * @return Whether the handling was successfully or interrupted. */ -WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls, - PatternRewriter& rewriter, - std::span& worklist) { +static 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)); @@ -769,7 +794,7 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, } // Collect quantum values to remove and classical values to add - controlsToModify ctrlsToMod; + ControlsToModify ctrlsToMod; ctrlsToMod.quantumCtrlsToRemove = superfluousCtrls.superfluousQubits; for (const auto superfluousQ : ctrlsToMod.quantumCtrlsToRemove) { std::erase(inCtrlValues, superfluousQ); @@ -881,10 +906,11 @@ WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, * operation. * @return Whether the iteration was successfully or interrupted. */ -LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, - std::span& worklist, - const std::span posClassicalCtrls, - const std::span negClassicalCtrls) { +static LogicalResult +iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, + std::span& worklist, + const std::span posClassicalCtrls, + const std::span negClassicalCtrls) { /// Iterate work-list. bool addedAtLeastOneQubit = false; for (Operation* curr : worklist) { @@ -1023,9 +1049,9 @@ LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, * non-zero probability. * @return Success if constant propagation has been applied successfully */ -LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, - const size_t maxNonzeroAmplitudes, - const size_t maxHybridStates) { +static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, + const size_t maxNonzeroAmplitudes, + const size_t maxHybridStates) { moveMeasurementsToFront(module, ctx); PatternRewriter rewriter(ctx); @@ -1060,6 +1086,7 @@ struct ConstantPropagation final : impl::ConstantPropagationBase { using ConstantPropagationBase::ConstantPropagationBase; +protected: void runOnOperation() override { if (failed(applyCP(getOperation(), &getContext(), maximumNonzeroAmplitudes, maximumHybridStates))) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 5bb7bde127..d8b59e7414 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -17,18 +17,20 @@ #include #include +#include #include #include #include #include +#include #include #include #include #include #include #include -#include +#include #include 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 b8c0489502..108d257d8b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -16,13 +16,18 @@ #include +#include #include +#include #include #include #include +#include +#include #include #include #include +#include #include #include #include diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 729f53c61d..32a76448ae 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -12,18 +12,29 @@ #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" +#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp" +#include +#include +#include #include - -#include +#include +#include + +#include +#include +#include +#include +#include #include +#include #include #include #include #include -#include +#include +#include namespace mlir::qco { @@ -375,10 +386,10 @@ bool UnionTable::hasAlwaysZeroProbability( const llvm::DenseMap& qubitValues, const llvm::DenseMap& classicalValues) const { std::set participatingEntries; - for (auto& [qV, _] : qubitValues) { + for (const auto& [qV, _] : qubitValues) { participatingEntries.insert(*valuesToEntries.at(qV)); } - for (auto& [cV, _] : classicalValues) { + for (const auto& [cV, _] : classicalValues) { participatingEntries.insert(*valuesToEntries.at(cV)); } for (const auto& ute : participatingEntries) { @@ -459,11 +470,11 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, const std::span negCtrlsClassical) { // Diagonal gates w/o parameters: IdOp, ZOp, SOp, SdgOp, TOp, TdgOp if (isa(op)) { - return std::optional(0.0); + return {0.0}; } if (!(isa(op) || isa(op) || isa(op) || isa(op) || isa(op))) { - return std::optional(); + return {}; } const auto targetIndex = qubitsToGlobalIndices.at(target); const auto targetUte = valuesToEntries.at(target); @@ -479,11 +490,11 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, } if (alwaysZero) { - return std::optional(0.0); + return {0.0}; } if (!alwaysOne && ctrlsQuantum.empty() && posCtrlsClassical.empty() && negCtrlsClassical.empty()) { - return std::optional(); + return {}; } const auto participatingEntries = collectParticipatingEntries( @@ -496,17 +507,17 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, std::unordered_map qubitCtrlThisEntry; llvm::DenseMap classicalCtrlThisEntry; for (const auto q : ctrlsQuantum) { - if (ute.participatingQubits.contains((q))) { + if (ute.participatingQubits.contains(q)) { qubitCtrlThisEntry[qubitsToGlobalIndices.at(q)] = true; } } for (const auto c : posCtrlsClassical) { - if (ute.participatingClassicalValues.contains((c))) { + if (ute.participatingClassicalValues.contains(c)) { classicalCtrlThisEntry[c] = true; } } for (const auto c : negCtrlsClassical) { - if (ute.participatingClassicalValues.contains((c))) { + if (ute.participatingClassicalValues.contains(c)) { classicalCtrlThisEntry[c] = false; } } @@ -517,31 +528,31 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, highestStateAlwaysReached &= !ctrlsZeroProbability; } if (highestStateReachable && !highestStateAlwaysReached) { - return std::optional(); + return {}; } } if (!highestStateReachable) { - return std::optional(0.0); + return {0.0}; } if (!highestStateAlwaysReached) { - return std::optional(); + return {}; } // Only highest state reachable, return respective phase if (isa(op)) { - return std::optional(std::numbers::pi); + return {std::numbers::pi}; } if (isa(op)) { - return std::optional(std::numbers::pi / 2); + return {std::numbers::pi / 2}; } if (isa(op)) { - return std::optional(3.0 * std::numbers::pi / 2); + return {3.0 * std::numbers::pi / 2}; } if (isa(op)) { - return std::optional(std::numbers::pi / 4); + return {std::numbers::pi / 4}; } // Tdg Op - return std::optional(-std::numbers::pi / 2); + return {-std::numbers::pi / 2}; } SuperfluousResult 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 6d6f9e5b4a..9c4261be95 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -18,7 +18,6 @@ #include #include -#include #include using namespace mlir::qco; @@ -210,7 +209,7 @@ TEST_F(HybridStateTest, doMeasurementWithOneResult) { const auto resStates = hState.propagateMeasurement(0, v1); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT( resHybridState.toString(), testing::HasSubstr("{|0001> -> 1.00}: integerValue0 = 1; p = 0.60")); @@ -241,7 +240,7 @@ TEST_F(HybridStateTest, doMeasurementWithNegClassicalCtrl) { const auto resStates = hState.propagateMeasurement(0, v1, ctrl); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT( resHybridState.toString(), testing::HasSubstr( @@ -257,7 +256,7 @@ TEST_F(HybridStateTest, doMeasurementWithPosNegClassicalCtrl) { const auto resStates = hState.propagateMeasurement(0, v1, {}, ctrl); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT( resHybridState.toString(), testing::HasSubstr( @@ -273,7 +272,7 @@ TEST_F(HybridStateTest, doResetWithOneResult) { const auto resStates = hState.propagateReset(0, ctrl); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT(resHybridState.toString(), testing::HasSubstr("{|0> -> 1.00}: integerValue0 = 3; p = 1.00")); } @@ -305,7 +304,7 @@ TEST_F(HybridStateTest, doResetWithNegClassicalCtrl) { const auto resStates = hState.propagateReset(0, ctrl); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT( resHybridState.toString(), testing::HasSubstr( @@ -321,7 +320,7 @@ TEST_F(HybridStateTest, doResetWithPosNegClassicalCtrl) { const auto resStates = hState.propagateReset(0, {}, ctrl); EXPECT_TRUE(resStates.size() == 1); - const auto resHybridState = resStates.at(0); + const auto& resHybridState = resStates.at(0); EXPECT_THAT( resHybridState.toString(), testing::HasSubstr( 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 cd672da510..92270d8533 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -16,7 +16,6 @@ #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 2c1eceb289..2b9024b461 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -15,8 +15,12 @@ #include #include +#include #include +#include +#include + using namespace mlir::qco; class UnionTableTest : public testing::Test { From 19698db3d2ec946eab53a8b776ab0d16d8011765 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 113/235] =?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 | 112 --------------------------------------------------- 1 file changed, 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb3f7cdb2e..8adfe2cd21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -674,119 +674,7 @@ changelogs._ -[#2168]: https://github.com/munich-quantum-toolkit/core/pull/2168 -[#2157]: https://github.com/munich-quantum-toolkit/core/pull/2157 -[#2156]: https://github.com/munich-quantum-toolkit/core/pull/2156 -[#2154]: https://github.com/munich-quantum-toolkit/core/pull/2154 -[#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 -[#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 -[#2141]: https://github.com/munich-quantum-toolkit/core/pull/2141 -[#2140]: https://github.com/munich-quantum-toolkit/core/pull/2140 -[#2138]: https://github.com/munich-quantum-toolkit/core/pull/2138 -[#2137]: https://github.com/munich-quantum-toolkit/core/pull/2137 -[#2136]: https://github.com/munich-quantum-toolkit/core/pull/2136 -[#2133]: https://github.com/munich-quantum-toolkit/core/pull/2133 -[#2125]: https://github.com/munich-quantum-toolkit/core/pull/2125 -[#2124]: https://github.com/munich-quantum-toolkit/core/pull/2124 -[#2118]: https://github.com/munich-quantum-toolkit/core/pull/2118 -[#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 -[#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 -[#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 -[#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 -[#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 -[#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 -[#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 -[#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 -[#2084]: https://github.com/munich-quantum-toolkit/core/pull/2084 -[#2074]: https://github.com/munich-quantum-toolkit/core/pull/2074 -[#2073]: https://github.com/munich-quantum-toolkit/core/pull/2073 -[#2066]: https://github.com/munich-quantum-toolkit/core/pull/2066 -[#2060]: https://github.com/munich-quantum-toolkit/core/pull/2060 -[#2058]: https://github.com/munich-quantum-toolkit/core/pull/2058 -[#2054]: https://github.com/munich-quantum-toolkit/core/pull/2054 -[#2049]: https://github.com/munich-quantum-toolkit/core/pull/2049 -[#2043]: https://github.com/munich-quantum-toolkit/core/pull/2043 -[#2042]: https://github.com/munich-quantum-toolkit/core/pull/2042 -[#2039]: https://github.com/munich-quantum-toolkit/core/pull/2039 -[#2038]: https://github.com/munich-quantum-toolkit/core/pull/2038 -[#2036]: https://github.com/munich-quantum-toolkit/core/pull/2036 -[#2035]: https://github.com/munich-quantum-toolkit/core/pull/2035 -[#2031]: https://github.com/munich-quantum-toolkit/core/pull/2031 -[#2030]: https://github.com/munich-quantum-toolkit/core/pull/2030 -[#2028]: https://github.com/munich-quantum-toolkit/core/pull/2028 -[#2026]: https://github.com/munich-quantum-toolkit/core/pull/2026 -[#2025]: https://github.com/munich-quantum-toolkit/core/pull/2025 -[#2018]: https://github.com/munich-quantum-toolkit/core/pull/2018 -[#2017]: https://github.com/munich-quantum-toolkit/core/pull/2017 -[#2016]: https://github.com/munich-quantum-toolkit/core/pull/2016 -[#2015]: https://github.com/munich-quantum-toolkit/core/pull/2015 -[#2014]: https://github.com/munich-quantum-toolkit/core/pull/2014 -[#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011 -[#2010]: https://github.com/munich-quantum-toolkit/core/pull/2010 -[#2008]: https://github.com/munich-quantum-toolkit/core/pull/2008 -[#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007 -[#2006]: https://github.com/munich-quantum-toolkit/core/pull/2006 -[#2005]: https://github.com/munich-quantum-toolkit/core/pull/2005 -[#2003]: https://github.com/munich-quantum-toolkit/core/pull/2003 -[#2002]: https://github.com/munich-quantum-toolkit/core/pull/2002 -[#2001]: https://github.com/munich-quantum-toolkit/core/pull/2001 -[#2000]: https://github.com/munich-quantum-toolkit/core/pull/2000 -[#1999]: https://github.com/munich-quantum-toolkit/core/pull/1999 -[#1998]: https://github.com/munich-quantum-toolkit/core/pull/1998 -[#1997]: https://github.com/munich-quantum-toolkit/core/pull/1997 -[#1996]: https://github.com/munich-quantum-toolkit/core/pull/1996 -[#1995]: https://github.com/munich-quantum-toolkit/core/pull/1995 -[#1994]: https://github.com/munich-quantum-toolkit/core/pull/1994 -[#1993]: https://github.com/munich-quantum-toolkit/core/pull/1993 -[#1992]: https://github.com/munich-quantum-toolkit/core/pull/1992 -[#1987]: https://github.com/munich-quantum-toolkit/core/pull/1987 -[#1986]: https://github.com/munich-quantum-toolkit/core/pull/1986 -[#1983]: https://github.com/munich-quantum-toolkit/core/pull/1983 -[#1980]: https://github.com/munich-quantum-toolkit/core/pull/1980 -[#1979]: https://github.com/munich-quantum-toolkit/core/pull/1979 -[#1978]: https://github.com/munich-quantum-toolkit/core/pull/1978 -[#1976]: https://github.com/munich-quantum-toolkit/core/pull/1976 -[#1975]: https://github.com/munich-quantum-toolkit/core/pull/1975 -[#1973]: https://github.com/munich-quantum-toolkit/core/pull/1973 -[#1972]: https://github.com/munich-quantum-toolkit/core/pull/1972 -[#1967]: https://github.com/munich-quantum-toolkit/core/pull/1967 -[#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 -[#1961]: https://github.com/munich-quantum-toolkit/core/pull/1961 -[#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 -[#1953]: https://github.com/munich-quantum-toolkit/core/pull/1953 -[#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 -[#1951]: https://github.com/munich-quantum-toolkit/core/pull/1951 -[#1950]: https://github.com/munich-quantum-toolkit/core/pull/1950 -[#1938]: https://github.com/munich-quantum-toolkit/core/pull/1938 -[#1936]: https://github.com/munich-quantum-toolkit/core/pull/1936 -[#1935]: https://github.com/munich-quantum-toolkit/core/pull/1935 -[#1934]: https://github.com/munich-quantum-toolkit/core/pull/1934 -[#1933]: https://github.com/munich-quantum-toolkit/core/pull/1933 -[#1927]: https://github.com/munich-quantum-toolkit/core/pull/1927 -[#1925]: https://github.com/munich-quantum-toolkit/core/pull/1925 -[#1924]: https://github.com/munich-quantum-toolkit/core/pull/1924 -[#1923]: https://github.com/munich-quantum-toolkit/core/pull/1923 -[#1915]: https://github.com/munich-quantum-toolkit/core/pull/1915 -[#1914]: https://github.com/munich-quantum-toolkit/core/pull/1914 -[#1912]: https://github.com/munich-quantum-toolkit/core/pull/1912 -[#1911]: https://github.com/munich-quantum-toolkit/core/pull/1911 -[#1910]: https://github.com/munich-quantum-toolkit/core/pull/1910 -[#1904]: https://github.com/munich-quantum-toolkit/core/pull/1904 -[#1897]: https://github.com/munich-quantum-toolkit/core/pull/1897 -[#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895 -[#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 -[#1886]: https://github.com/munich-quantum-toolkit/core/pull/1886 -[#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 -[#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 -[#1872]: https://github.com/munich-quantum-toolkit/core/pull/1872 -[#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 -[#1869]: https://github.com/munich-quantum-toolkit/core/pull/1869 -[#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865 -[#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 -[#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 -[#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 [#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 -[#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#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 0ec41a16a034c2368c7abe6ce1dfa4e28ffc39b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:18:34 +0000 Subject: [PATCH 114/235] =?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 --- .../Transforms/Optimizations/ConstantPropagation.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 10a6c25202..609263a3db 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -1,3 +1,13 @@ +/* + * 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 + */ + /* * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM * Copyright (c) 2025 - 2026 Munich Qua#include From ded9cb5936e893108f0cf91a546e4460b1718f8a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 17:22:53 +1000 Subject: [PATCH 115/235] :pencil2: Fixed typo --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d9208b0d7c..65066dafdd 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -249,7 +249,7 @@ class UnionTable { * @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 + * @return A map of values as keys. The values of th emap say whether the * classical values are always true or always false. */ llvm::DenseMap From 813a21c7a3323a44b11ede647ce811a7fcc6c036 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 20:20:15 +1000 Subject: [PATCH 116/235] :rotating_light: Fixed linter warnings --- .../Optimizations/ConstantPropagation.cpp | 10 ++--- .../ConstantPropagation/HybridState.cpp | 15 +++---- .../ConstantPropagation/UnionTable.cpp | 39 ++++++------------- .../ConstantPropagation/test_hybridState.cpp | 7 +++- .../ConstantPropagation/test_quantumState.cpp | 6 +++ .../ConstantPropagation/test_unionTable.cpp | 6 +++ .../test_qco_constant_propagation.cpp | 1 + 7 files changed, 40 insertions(+), 44 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 609263a3db..b8b08f871c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -30,18 +30,18 @@ #include #include #include -#include -#include +#include #include #include +#include #include #include -#include #include #include #include #include #include +#include #include #include @@ -58,8 +58,6 @@ namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION #include "mlir/Dialect/QCO/Transforms/Passes.h.inc" -namespace { - #define CREATE_OP_CASE_NO_PARAMS(opType) \ .Case([&](opType gate) { \ return opType::create(rewriter, gate.getLoc(), qubitsIn[0]); \ @@ -1105,6 +1103,4 @@ struct ConstantPropagation final } }; -} // namespace - } // 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 d8b59e7414..c5bef987b8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -103,14 +104,10 @@ bool HybridState::operator==(const HybridState& that) const { } } - for (const auto& [d, v] : doubleValues) { - if (!that.doubleValues.contains(d) || - std::fabs(that.doubleValues.at(d)) > 1e-4) { - return false; - } - } - - return true; + return std::ranges::all_of(doubleValues, [&](const auto& p) { + auto it = that.doubleValues.find(p.first); + return it != that.doubleValues.end() && std::fabs(it->second) <= 1e-4; + }); } void HybridState::addIntegerValue(const Value value, const int64_t number) { @@ -147,7 +144,7 @@ void HybridState::propagateGate(Operation* gate, if (integerValues.contains(p)) { paramValues.push_back(integerValues.at(p)); } else if (doubleValues.contains(p)) { - paramValues.push_back(doubleValues.at(p)); + paramValues.push_back(static_cast(doubleValues.at(p))); } else { throw std::domain_error( "HybridState needs a classical value for gate parameters that is " diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 32a76448ae..36829be7fb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -16,8 +16,6 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp" #include -#include -#include #include #include #include @@ -27,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +53,7 @@ std::string UnionTable::toString() const { for (const auto& q : entry->participatingQubits) { qubitIndices.push_back(qubitsToGlobalIndices.at(q)); } - std::ranges::sort(qubitIndices, std::greater()); + std::ranges::sort(qubitIndices, std::greater{}); result += "Qubits: "; for (const auto qit : qubitIndices) { result += std::to_string(qit); @@ -343,43 +342,29 @@ void UnionTable::propagateDoubleAlloc(const Value doubleValue, 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; + return std::ranges::all_of(ute->states, [&](const auto& hs) { + return hs.isQubitAlwaysOne(qubitIndex); + }); } 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; + return std::ranges::all_of(ute->states, [&](const auto& hs) { + return hs.isQubitAlwaysZero(qubitIndex); + }); } 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; + return std::ranges::all_of(ute->states, + [&](const auto& hs) { return hs.isValueTrue(c); }); } 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; + return std::ranges::all_of( + ute->states, [&](const auto& hs) { return !hs.isValueTrue(c); }); } bool UnionTable::hasAlwaysZeroProbability( 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 9c4261be95..78d2a1d9c0 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -9,17 +9,21 @@ */ #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/HybridState.hpp" -#include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" #include #include +#include +#include #include #include #include +namespace { + using namespace mlir::qco; class HybridStateTest : public testing::Test { @@ -574,3 +578,4 @@ TEST_F(HybridStateTest, doubleOpTwoValueOperation) { testing::AnyOf(testing::HasSubstr("doubleValue0 = -3.25"), testing::HasSubstr("doubleValue1 = -3.25"))); } +} // namespace 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 92270d8533..f36613f256 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -9,17 +9,22 @@ */ #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/QuantumState.hpp" #include #include +#include +#include #include #include #include #include +namespace { + using namespace mlir::qco; class QuantumStateTest : public testing::Test { @@ -339,3 +344,4 @@ TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { EXPECT_THROW(auto qs = qState1.unify(qState2), std::domain_error); } +} // namespace \ No newline at end of file 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 2b9024b461..378e3d0e40 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -16,11 +16,16 @@ #include #include #include +#include +#include +#include #include #include #include +namespace { + using namespace mlir::qco; class UnionTableTest : public testing::Test { @@ -1188,3 +1193,4 @@ TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToPosClassicalCtrl) { negClassicalCtrl); ASSERT_TRUE(results.completelySuperfluous); } +} // namespace \ No newline at end of file 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 b9241358ee..6a89ef2ab8 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 @@ -14,6 +14,7 @@ #include "mlir/Support/IRVerification.h" #include +#include #include #include #include From 9664214019e2102c01e10429f28f9ec92a562ece Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 10:21:17 +0000 Subject: [PATCH 117/235] =?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 --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 2 +- .../Optimizations/ConstantPropagation/test_unionTable.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 f36613f256..74f07b5d67 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -344,4 +344,4 @@ TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { EXPECT_THROW(auto qs = qState1.unify(qState2), std::domain_error); } -} // namespace \ No newline at end of file +} // namespace 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 378e3d0e40..0239e1cc84 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -1193,4 +1193,4 @@ TEST_F(UnionTableSuperfluousTest, completelySuperfluousDueToPosClassicalCtrl) { negClassicalCtrl); ASSERT_TRUE(results.completelySuperfluous); } -} // namespace \ No newline at end of file +} // namespace From 678a2a06c9d8f4e02d0df9e1cf1b295a21ec56e5 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 20:58:44 +1000 Subject: [PATCH 118/235] :rotating_light: Fixed linter warnings --- .../Optimizations/ConstantPropagation.cpp | 39 ++++++++++--------- .../ConstantPropagation/HybridState.cpp | 29 ++++++++------ .../ConstantPropagation/QuantumState.cpp | 19 +++++---- .../ConstantPropagation/test_hybridState.cpp | 2 +- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index b8b08f871c..cdabef8cc2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,17 @@ #include #include +namespace { +/** + * @brief Result of checking how do modify a controlled gate. + */ +struct ControlsToModify { + llvm::DenseSet quantumCtrlsToRemove; + llvm::DenseSet classicalPosCtrlsToAdd; + llvm::DenseSet classicalNegCtrlsToAdd; +}; +} // namespace + namespace mlir::qco { #define GEN_PASS_DEF_CONSTANTPROPAGATION @@ -104,15 +116,6 @@ namespace mlir::qco { gate.getTheta(), gate.getPhi(), gate.getLambda()); \ }) -/** - * @brief Result of checking how do modify a controlled gate. - */ -struct ControlsToModify { - llvm::DenseSet quantumCtrlsToRemove; - llvm::DenseSet classicalPosCtrlsToAdd; - llvm::DenseSet classicalNegCtrlsToAdd; -}; - static LogicalResult iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, std::span& worklist, @@ -928,10 +931,6 @@ 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); @@ -1076,7 +1075,6 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, [&](Operation* op) { worklist.push_back(op); }); } - // TODO: Take maximum from params auto ut = UnionTable(maxNonzeroAmplitudes, maxHybridStates); std::span wl = {worklist.begin(), worklist.end()}; @@ -1084,6 +1082,9 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, return iterateThroughWorklist(rewriter, &ut, wl, {}, {}); } +} // namespace mlir::qco + +namespace { /** * This pass applies constant propagation to a circuit. It assumes that all * states start in |0> and removes quantum instructions that are superfluous @@ -1091,16 +1092,16 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, * classical resources. */ struct ConstantPropagation final - : impl::ConstantPropagationBase { + : mlir::qco::impl::ConstantPropagationBase { using ConstantPropagationBase::ConstantPropagationBase; protected: void runOnOperation() override { - if (failed(applyCP(getOperation(), &getContext(), maximumNonzeroAmplitudes, - maximumHybridStates))) { + if (mlir::failed(mlir::qco::applyCP(getOperation(), &getContext(), + maximumNonzeroAmplitudes, + maximumHybridStates))) { signalPassFailure(); } } }; - -} // namespace mlir::qco +} // namespace diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index c5bef987b8..08ee81b49c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -25,10 +25,12 @@ #include #include #include -#include +#include +#include #include #include #include +#include #include #include #include @@ -54,34 +56,37 @@ std::string HybridState::toString() const { if (top) { return "TOP"; } + std::ostringstream oss; - std::string str = "{" + this->qState->toString() + "}: "; + oss << "{" << this->qState->toString() << "}: "; unsigned int i = 0; bool first = true; for (const auto& key : integerValues.keys()) { if (!first) { - str += ", "; + oss << ", "; } first = false; - str += "integerValue" + std::to_string(i) + " = " + - std::to_string(integerValues.at(key)); + oss << "integerValue" << i << " = " << integerValues.at(key); ++i; } unsigned int j = 0; for (const auto& key : doubleValues.keys()) { if (!first) { - str += ", "; + oss << ", "; } first = false; - str += "doubleValue" + std::to_string(j) + " = " + - std::format("{:.2f}", doubleValues.at(key)); + + oss << "doubleValue" << j << " = " << std::fixed << std::setprecision(2) + << doubleValues.at(key); + ++j; } if (i > 0 || j > 0) { - str += "; "; + oss << "; "; } - str += "p = " + std::format("{:.2f}", this->probability) + ";"; - return str; + oss << "p = " << std::fixed << std::setprecision(2) << this->probability + << ";"; + return oss.str(); } bool HybridState::operator==(const HybridState& that) const { @@ -142,7 +147,7 @@ void HybridState::propagateGate(Operation* gate, paramValues.reserve(params.size()); for (Value p : params) { if (integerValues.contains(p)) { - paramValues.push_back(integerValues.at(p)); + paramValues.push_back(static_cast(integerValues.at(p))); } else if (doubleValues.contains(p)) { paramValues.push_back(static_cast(doubleValues.at(p))); } else { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 108d257d8b..1eb3fa2898 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -53,29 +54,27 @@ std::string QuantumState::toString() const { if (nQubits == 0) { return ""; } - std::string str; + + std::ostringstream oss; bool first = true; for (auto ordered = std::map(this->amplitudeMap.begin(), this->amplitudeMap.end()); auto const& [key, val] : ordered) { if (!first) { - str += ", "; + oss << ", "; } first = false; - str.push_back('|'); - str.append(qubitStringToBinary(key)); - str.append("> -> "); - - str.append(std::format("{:.2f}", val.real())); + oss << "|" << qubitStringToBinary(key) << "> -> " << std::fixed + << std::setprecision(2) << 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()))); + oss << (val.imag() > 0 ? " + i" : " - i"); + oss << std::fixed << std::setprecision(2) << std::abs(val.imag()); } } - return str; + return oss.str(); } bool QuantumState::operator==(const QuantumState& that) const { 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 78d2a1d9c0..17e4ec81b2 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -565,7 +565,7 @@ TEST_F(HybridStateTest, doubleOpTwoValueOperation) { hState.addDoubleValue(val2, 1.3); const auto mulFOp = mlir::arith::MulFOp::create( - programBuilder, programBuilder.getLoc(), val2.getType(), val1); + programBuilder, programBuilder.getLoc(), val1, val2); hState.propagateClassicalOperation(mulFOp, val2, val1, val2); From 9e75e06f6252cc43541ed768805f111ed987ffa8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 21:23:29 +1000 Subject: [PATCH 119/235] :rotating_light: Fixed linter warnings --- .../Optimizations/ConstantPropagation/HybridState.cpp | 3 +-- .../Optimizations/ConstantPropagation/QuantumState.cpp | 3 ++- .../Transforms/Optimizations/test_qco_constant_propagation.cpp | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 08ee81b49c..390c75c56b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -289,7 +288,7 @@ bool HybridState::hasAlwaysZeroProbability( return true; } } else if (doubleValues.contains(v)) { - const bool zeroDouble = std::norm(doubleValues.at(v)) < 1e-4; + const bool zeroDouble = std::fabs(doubleValues.at(v)) < 1e-4; if ((zeroDouble && i) || (!zeroDouble && !i)) { return true; } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 1eb3fa2898..aad4a0f2ef 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -20,13 +20,14 @@ #include #include #include -#include #include +#include #include #include #include #include #include +#include #include #include #include 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 6a89ef2ab8..77f3262181 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,6 +22,7 @@ #include #include #include +#include #include #include @@ -224,6 +225,8 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { programBuilder.y(qRange01[1]); module = programBuilder.finalize(); + std::cout << "after build\n"; + auto qRef = referenceBuilder.allocQubitRegister(3); qRef[0] = referenceBuilder.h(qRef[0]); qRef[1] = referenceBuilder.x(qRef[1]); From 625e38aad2614ad3b7bdcf6819cd445cf141a4df Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 21:31:03 +1000 Subject: [PATCH 120/235] :rotating_light: Fixed linter warnings --- .../Optimizations/ConstantPropagation/HybridState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 390c75c56b..d8ecd12ae2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -273,7 +273,7 @@ bool HybridState::isValueTrue(const Value v) const { return integerValues.at(v) != 0; } if (doubleValues.contains(v)) { - return std::norm(doubleValues.at(v)) > 1e-4; + return std::fabs(doubleValues.at(v)) > 1e-4; } throw std::domain_error("Value of a classical value is asked which does not " "exist in the HybridState."); From 6101128b7a35cf6ef7c0ae6cac136151679e565d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 21:31:21 +1000 Subject: [PATCH 121/235] :poop: Checking if only one test fails on macos --- .../test_qco_constant_propagation.cpp | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 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 77f3262181..91cc1b8612 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 @@ -209,37 +209,35 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { * @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); - auto qRange01 = programBuilder.qcoIf( - b0, {q01, q1}, - [&](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}; - }); - programBuilder.y(qRange01[1]); - module = programBuilder.finalize(); - - std::cout << "after build\n"; - - 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); - referenceBuilder.y(qRef1); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); -} +// 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); +// auto qRange01 = programBuilder.qcoIf( +// b0, {q01, q1}, +// [&](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}; +// }); +// programBuilder.y(qRange01[1]); +// 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); +// 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 gates are unconditionally applied if the From 47dbd6f688f50acec01995f37a635514d1b37ed1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 21:51:55 +1000 Subject: [PATCH 122/235] :rotating_light: Fixed linter warnings --- .../ConstantPropagation/HybridState.cpp | 4 +- .../test_qco_constant_propagation.cpp | 60 ++++++++++--------- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index d8ecd12ae2..536918e6dd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -317,8 +317,8 @@ HybridState::getValueThatIsEquivalentToQubit(const unsigned int qubit) const { } } for (const auto& [v, d] : doubleValues) { - if ((qubitZero && std::norm(d) < 1e-4) || - (qubitOne && std::norm(d) >= 1e-4)) { + if ((qubitZero && std::fabs(d) < 1e-4) || + (qubitOne && std::fabs(d) >= 1e-4)) { result[v] = true; } else { result[v] = false; 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 91cc1b8612..790b148928 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 @@ -25,6 +25,7 @@ #include #include +#include #include namespace { @@ -209,35 +210,36 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { * @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); -// auto qRange01 = programBuilder.qcoIf( -// b0, {q01, q1}, -// [&](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}; -// }); -// programBuilder.y(qRange01[1]); -// 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); -// referenceBuilder.y(qRef1); -// reference = referenceBuilder.finalize(); -// -// ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); -// -// EXPECT_TRUE( -// areModulesEquivalentWithPermutations(module.get(), reference.get())); -// } +TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { + std::cout << "Test starting..."; + 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) { return SmallVector{args[0], args[1]}; }, + [&](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); + qRef[0] = referenceBuilder.h(qRef[0]); + 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 gates are unconditionally applied if the From aae1b087cd12054b2a607fdd64757f0d339beef1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 22:02:24 +1000 Subject: [PATCH 123/235] :construction: Added const keyword --- .../Optimizations/test_qco_constant_propagation.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 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 790b148928..91e3b98397 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 @@ -25,7 +25,6 @@ #include #include -#include #include namespace { @@ -211,13 +210,12 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { * cannot be satisfied are removed. */ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { - std::cout << "Test starting..."; 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( + const auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, [&](const ValueRange args) { return SmallVector{args[0], args[1]}; }, [&](const ValueRange args) { From 1f15741a06052632cea7723cbbe9ebb339d9da7f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 22:35:25 +1000 Subject: [PATCH 124/235] :construction: Fixed span creation --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index cdabef8cc2..165d2cba52 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -1077,7 +1077,7 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, auto ut = UnionTable(maxNonzeroAmplitudes, maxHybridStates); - std::span wl = {worklist.begin(), worklist.end()}; + std::span wl(worklist.data(), worklist.size()); return iterateThroughWorklist(rewriter, &ut, wl, {}, {}); } From 9ef187831dbac24e5ca6d58fd6842544cbf320bc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 22:48:09 +1000 Subject: [PATCH 125/235] :poop: Finding error cause for macos --- .../Optimizations/test_qco_constant_propagation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 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 91e3b98397..2d361cf73f 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 @@ -222,7 +222,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); return SmallVector{qi0, qi1}; }); - programBuilder.y(qRange01[1]); + // programBuilder.y(qRange01[1]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(3); @@ -230,7 +230,7 @@ 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); + // referenceBuilder.y(qRef1); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 74236212e28071b3338e945da2a9d637671a0d39 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 23:04:25 +1000 Subject: [PATCH 126/235] :poop: Finding error cause for macos --- .../test_qco_constant_propagation.cpp | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 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 2d361cf73f..dbc9b9ce6b 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 @@ -210,22 +210,23 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { * cannot be satisfied are removed. */ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { - auto q = programBuilder.allocQubitRegister(3); + 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 qRange01 = programBuilder.qcoIf( - b0, {q01, q1}, - [&](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}; - }); + // auto [q01, b0] = + programBuilder.measure(q0); + // const auto qRange01 = programBuilder.qcoIf( + // b0, {q01, q1}, + // [&](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}; + // }); // programBuilder.y(qRange01[1]); module = programBuilder.finalize(); - auto qRef = referenceBuilder.allocQubitRegister(3); + 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]); From 3f07d4302c7667d2ee6b6daf54b5e639f65c589b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 23:24:09 +1000 Subject: [PATCH 127/235] :poop: Finding error cause for macos --- .../test_qco_constant_propagation.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 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 dbc9b9ce6b..3469568dbb 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 @@ -214,15 +214,15 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { 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 qRange01 = programBuilder.qcoIf( - // b0, {q01, q1}, - // [&](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}; - // }); + auto [q01, b0] = programBuilder.measure(q0); + // const auto qRange01 = + programBuilder.qcoIf( + b0, {q01, q1}, + [&](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}; + }); // programBuilder.y(qRange01[1]); module = programBuilder.finalize(); From 1a920a63b96d01d6831f397693fb014eaddafd2f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 23:25:32 +1000 Subject: [PATCH 128/235] :construction: Check satisfiability earlier --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 165d2cba52..6f52f83ae8 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -796,9 +796,14 @@ static WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, // Check if gate is executable const auto satisfiable = ut->areThereSatisfiableCombinations( inCtrlValues, posClassicalCtrls, negClassicalCtrls); + if (!satisfiable) { + std::ranges::replace(worklist, *op, static_cast(nullptr)); + removeCtrlOperation(op, rewriter); + return WalkResult::advance(); + } const auto superfluousCtrls = ut->getSuperfluousControls( inCtrlValues, posClassicalCtrls, negClassicalCtrls); - if (superfluousCtrls.completelySuperfluous || !satisfiable) { + if (superfluousCtrls.completelySuperfluous) { std::ranges::replace(worklist, *op, static_cast(nullptr)); removeCtrlOperation(op, rewriter); return WalkResult::advance(); From 47230ccf47b27c4738f2a4bd5422128250750021 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 14 Jul 2026 23:43:03 +1000 Subject: [PATCH 129/235] :construction: Check satisfiability earlier --- .../Transforms/Optimizations/test_qco_constant_propagation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3469568dbb..44b5baa369 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 @@ -218,7 +218,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { // const auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, - [&](const ValueRange args) { return SmallVector{args[0], args[1]}; }, + // [&](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}; From c52d3d5a5630890175a3401e0844d6dbe860820a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 07:35:28 +1000 Subject: [PATCH 130/235] :construction: Fixed span creation --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 4 ++-- .../Optimizations/test_qco_constant_propagation.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6f52f83ae8..48368cb2fd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -499,7 +499,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, newWorklist.push_back(innerOp); std::ranges::replace(worklist, innerOp, static_cast(nullptr)); }); - std::span wl = {newWorklist.begin(), newWorklist.end()}; + std::span wl(newWorklist.data(), newWorklist.size()); std::vector newPosClassicalCtrls = {posClassicalCtrls.begin(), posClassicalCtrls.end()}; newPosClassicalCtrls.push_back(condition); @@ -528,7 +528,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, newWorklist.push_back(innerOp); std::ranges::replace(worklist, innerOp, static_cast(nullptr)); }); - std::span wl = {newWorklist.begin(), newWorklist.end()}; + std::span wl(newWorklist.data(), newWorklist.size()); std::vector newNegClassicalCtrls = {negClassicalCtrls.begin(), negClassicalCtrls.end()}; newNegClassicalCtrls.push_back(condition); 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 44b5baa369..3469568dbb 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 @@ -218,7 +218,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { // const auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, - // [&](const ValueRange args) { return SmallVector{args[0], args[1]}; }, + [&](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}; From 5d97c9d40b78a40070d00886692f82a1e089642e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 08:12:43 +1000 Subject: [PATCH 131/235] :construction: Fixed erasure of variable, Assisted-by: Copilot via GitHub Actions --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 48368cb2fd..e7138735bb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -816,7 +816,8 @@ static WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, std::erase(inCtrlValues, superfluousQ); } - for (const auto qCtrl : inCtrlValues) { + const auto ctrlCandidates = inCtrlValues; + for (const auto qCtrl : ctrlCandidates) { auto qCtrlValuesWithoutCurrent = inCtrlValues; std::erase(qCtrlValuesWithoutCurrent, qCtrl); if (ut->isQubitImplied(qCtrl, qCtrlValuesWithoutCurrent, posClassicalCtrls, From 312ee4522bf34b6dee6f226c9764a823839a89e3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 09:23:18 +1000 Subject: [PATCH 132/235] :poop: Find macos Segfault cause --- .../Optimizations/ConstantPropagation.cpp | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index e7138735bb..53123f8397 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -560,45 +560,45 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, 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(elseArgs.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); + // 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(elseArgs.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); From f1e853729af6d3faf14590e058ce96b006e3662a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 23:23:57 +0000 Subject: [PATCH 133/235] =?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 --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 53123f8397..eee7fd5cbc 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -585,7 +585,8 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, // } // } // if (implicitSwap) { - // throw std::runtime_error("Constant propagation does not allow implicit " + // 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 From 14fcdc0fa37ed37f8ab16d465e6a691f91caa78d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 09:55:15 +1000 Subject: [PATCH 134/235] :poop: Find macos Segfault cause --- .../Optimizations/ConstantPropagation.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index eee7fd5cbc..15cff037cc 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -589,17 +589,22 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, // " // "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)); + // 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); + for (unsigned int inputQubitIndex = 0; + inputQubitIndex < op->getQubits().size(); ++inputQubitIndex) { + rewriter.replaceAllUsesWith(op->getResults()[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); From 5f00eef1cbcf03da22881749071f822a8bbe7ce9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 10:07:16 +1000 Subject: [PATCH 135/235] :poop: Find macos Segfault cause --- .../Optimizations/ConstantPropagation.cpp | 71 ++++++++++--------- .../test_qco_constant_propagation.cpp | 2 + 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 15cff037cc..97e8c1d0db 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -518,6 +519,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, }); } if (!elseEmpty) { + std::cout << "Working on else branch..." << std::endl; for (const Value arg : elseBlock->getArguments()) { elseArgs.push_back(arg); } @@ -534,6 +536,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, newNegClassicalCtrls.push_back(condition); const auto resElse = iterateThroughWorklist( rewriter, ut, wl, posClassicalCtrls, newNegClassicalCtrls); + std::cout << "Iterated through else branch..." << std::endl; if (resElse.failed()) { return WalkResult::interrupt(); @@ -560,42 +563,42 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, 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(elseArgs.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."); - // } + 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(elseArgs.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]); - // } + for (unsigned int inputQubitIndex = 0; + inputQubitIndex < op->getQubits().size(); ++inputQubitIndex) { + rewriter.replaceAllUsesWith(op->getResults()[order.at(inputQubitIndex)], + op->getQubits()[inputQubitIndex]); + } for (unsigned int inputQubitIndex = 0; inputQubitIndex < op->getQubits().size(); ++inputQubitIndex) { rewriter.replaceAllUsesWith(op->getResults()[inputQubitIndex], 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 3469568dbb..36e6c64bdd 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 @@ -225,6 +225,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { }); // programBuilder.y(qRange01[1]); module = programBuilder.finalize(); + std::cout << "After program finalization." << std::endl; auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.h(qRef[0]); @@ -233,6 +234,7 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { referenceBuilder.measure(qRef0); // referenceBuilder.y(qRef1); reference = referenceBuilder.finalize(); + std::cout << "After reference finalization." << std::endl; ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 0114bf523cceea66726739e777872751226226ae Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 10:15:33 +1000 Subject: [PATCH 136/235] :construction: Fix string --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 97e8c1d0db..6cd9e68b44 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -588,8 +588,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } } if (implicitSwap) { - throw std::runtime_error("Constant propagation does not allow implicit - " + 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 From df57fcfa7aee6210300e93af22f5762cf1ad3203 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 10:28:59 +1000 Subject: [PATCH 137/235] :poop: Find segfault on macos --- .../Optimizations/ConstantPropagation.cpp | 15 +++++++++------ .../test_qco_constant_propagation.cpp | 2 -- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6cd9e68b44..dce5b064b9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -519,7 +519,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, }); } if (!elseEmpty) { - std::cout << "Working on else branch..." << std::endl; for (const Value arg : elseBlock->getArguments()) { elseArgs.push_back(arg); } @@ -550,6 +549,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, std::ranges::replace(elseArgs, input[i], output[i]); } }); + std::cout << "Walked through else branch..." << std::endl; } const auto resultQubits = op->getResults(); std::vector results = {resultQubits.begin(), resultQubits.end()}; @@ -561,11 +561,13 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, // Remove if operation completely if both branches are empty after propagation if (thenEmpty && elseEmpty) { + std::cout << "If and else empty" << std::endl; // 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()) { + std::cout << "Then args not empty..." << std::endl; for (unsigned int i = 0; i < thenBlock->getArguments().size(); ++i) { auto it = std::ranges::find(thenArgs, thenBlock->getArguments()[i]); if (it != thenArgs.end()) { @@ -575,6 +577,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } } if (!elseArgs.empty()) { + std::cout << "Else args not empty..." << std::endl; for (unsigned int i = 0; i < elseBlock->getArguments().size(); ++i) { auto it = std::ranges::find(elseArgs, elseBlock->getArguments()[i]); if (it != elseArgs.end()) { @@ -591,6 +594,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, throw std::runtime_error("Constant propagation does not allow implicit " "swapping of qubits in branching."); } + std::cout << "Implicit swap checked..." << std::endl; // 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; @@ -598,16 +602,15 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, rewriter.replaceAllUsesWith(op->getResults()[order.at(inputQubitIndex)], op->getQubits()[inputQubitIndex]); } - for (unsigned int inputQubitIndex = 0; - inputQubitIndex < op->getQubits().size(); ++inputQubitIndex) { - rewriter.replaceAllUsesWith(op->getResults()[inputQubitIndex], - op->getQubits()[inputQubitIndex]); - } + + std::cout << "Replaced input with output after if removal..." << std::endl; std::vector inputQubitVec = {op->getQubits().begin(), op->getQubits().end()}; ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, inputQubitVec); + std::cout << "Replaced values globally..." << std::endl; rewriter.eraseOp(*op); + std::cout << "Erased If Op..." << std::endl; } else { ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, results); } 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 36e6c64bdd..3469568dbb 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 @@ -225,7 +225,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { }); // programBuilder.y(qRange01[1]); module = programBuilder.finalize(); - std::cout << "After program finalization." << std::endl; auto qRef = referenceBuilder.allocQubitRegister(2); qRef[0] = referenceBuilder.h(qRef[0]); @@ -234,7 +233,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { referenceBuilder.measure(qRef0); // referenceBuilder.y(qRef1); reference = referenceBuilder.finalize(); - std::cout << "After reference finalization." << std::endl; ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From cfee5e7f375c0f90b059ea893681bf04c75512e4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 10:43:09 +1000 Subject: [PATCH 138/235] :poop: Find segfault on macos --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 5 +---- .../Optimizations/test_qco_constant_propagation.cpp | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index dce5b064b9..55818b1760 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -561,7 +561,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, // Remove if operation completely if both branches are empty after propagation if (thenEmpty && elseEmpty) { - std::cout << "If and else empty" << std::endl; // Check that there is no implicit swap in one branch by re-ordered yield // operands and get order of returned qubits std::vector order; @@ -594,7 +593,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, throw std::runtime_error("Constant propagation does not allow implicit " "swapping of qubits in branching."); } - std::cout << "Implicit swap checked..." << std::endl; // 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; @@ -603,12 +601,10 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, op->getQubits()[inputQubitIndex]); } - std::cout << "Replaced input with output after if removal..." << std::endl; std::vector inputQubitVec = {op->getQubits().begin(), op->getQubits().end()}; ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, inputQubitVec); - std::cout << "Replaced values globally..." << std::endl; rewriter.eraseOp(*op); std::cout << "Erased If Op..." << std::endl; } else { @@ -1043,6 +1039,7 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, return failure(); } } + std::cout << "Iterated" << std::endl; return success(); } 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 3469568dbb..e1e49ef2be 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 @@ -201,6 +201,8 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + std::cout << "Did CP" << std::endl; + EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } From f4b48ee836b05e11ba6d766f1e19146bf59f48cf Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 10:55:41 +1000 Subject: [PATCH 139/235] :poop: Find segfault on macos --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 55818b1760..6e90c8cc4a 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -945,6 +945,13 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, continue; // Skip erased ops. } + auto n = curr->getName().stripDialect().str(); + std::string oName = + "Op: " + curr->getName().getStringRef().str() + + " dialect: " + curr->getName().getDialectNamespace().str(); + + std::cout << n << std::endl; + rewriter.setInsertionPoint(curr); const auto res = From a1fbbdce8a1be1dd57ffca001b5fa1e607f72ce4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 11:43:54 +1000 Subject: [PATCH 140/235] :poop: Find segfault on macos --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 6e90c8cc4a..61fe6abebb 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include namespace { @@ -944,13 +945,11 @@ 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(); - std::cout << n << std::endl; + std::cout << oName << std::endl; rewriter.setInsertionPoint(curr); From e9e64e16ef3535ec33f278cfc4549aa2d81cb672 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 11:56:15 +1000 Subject: [PATCH 141/235] :poop: Find segfault on macos --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 61fe6abebb..bbeaad8e58 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -611,6 +611,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } else { ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, results); } + std::cout << "Returning to iteration..." << std::endl; return WalkResult::advance(); } @@ -939,10 +940,12 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, /// Iterate work-list. bool addedAtLeastOneQubit = false; for (Operation* curr : worklist) { + std::cout << "Starting with curr operation..." << std::endl; if (addedAtLeastOneQubit && ut->areStatesAllTop()) { return success(); } if (curr == nullptr) { + std::cout << "Op is null" << std::endl; continue; // Skip erased ops. } std::string oName = @@ -1044,6 +1047,7 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, if (res.wasInterrupted()) { return failure(); } + std::cout << "Finished curr operation" << std::endl; } std::cout << "Iterated" << std::endl; return success(); From b038a5edf358b6ff11ed5921ce598e197a5c2f22 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 14:19:31 +1000 Subject: [PATCH 142/235] :poop: Find macos Segfault cause --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index bbeaad8e58..a47aaa85cd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -944,10 +944,12 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, if (addedAtLeastOneQubit && ut->areStatesAllTop()) { return success(); } + std::cout << "Continuing" << std::endl; if (curr == nullptr) { std::cout << "Op is null" << std::endl; continue; // Skip erased ops. } + std::cout << "Op is not null" << std::endl; std::string oName = "Op: " + curr->getName().getStringRef().str() + " dialect: " + curr->getName().getDialectNamespace().str(); From f142205d035e22d69a0b214f2d01ca4e143b2715 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 14:32:12 +1000 Subject: [PATCH 143/235] :poop: Find macos Segfault cause --- .../Transforms/Optimizations/ConstantPropagation.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index a47aaa85cd..a41070df22 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -950,13 +950,15 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, continue; // Skip erased ops. } std::cout << "Op is not null" << std::endl; - std::string oName = - "Op: " + curr->getName().getStringRef().str() + - " dialect: " + curr->getName().getDialectNamespace().str(); - - std::cout << oName << std::endl; + // std::string oName = + // "Op: " + curr->getName().getStringRef().str() + + // " dialect: " + curr->getName().getDialectNamespace().str(); + // + // std::cout << oName << std::endl; + std::cout << "Setting insertion point" << std::endl; rewriter.setInsertionPoint(curr); + std::cout << "Insertion point set" << std::endl; const auto res = TypeSwitch(curr) From 18d4d3dd9ec8dd014ef33f22f33de373ad771f2a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 14:47:25 +1000 Subject: [PATCH 144/235] :poop: Find macos Segfault cause --- .../Transforms/Optimizations/ConstantPropagation.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index a41070df22..7592dfe6fe 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -950,11 +950,11 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, continue; // Skip erased ops. } std::cout << "Op is not null" << std::endl; - // std::string oName = - // "Op: " + curr->getName().getStringRef().str() + - // " dialect: " + curr->getName().getDialectNamespace().str(); - // - // std::cout << oName << std::endl; + std::string oName = + "Op: " + curr->getName().getStringRef().str() + + " dialect: " + curr->getName().getDialectNamespace().str(); + + std::cout << oName << std::endl; std::cout << "Setting insertion point" << std::endl; rewriter.setInsertionPoint(curr); From 38ad358727e9d640439f791c01c9146802b0faa4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 15 Jul 2026 22:23:27 +1000 Subject: [PATCH 145/235] :construction: Put yield operations always to null --- .../Optimizations/ConstantPropagation.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 7592dfe6fe..1780b082e1 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -488,20 +488,27 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, std::vector thenArgs; std::vector elseArgs; + std::vector newThenWorklist; + std::vector newElseWorklist; + + // Create a new worklist to iterate over the inner instructions + op->thenBlock()->walk([&](Operation* innerOp) { + newThenWorklist.push_back(innerOp); + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); + op->elseBlock()->walk([&](Operation* innerOp) { + newElseWorklist.push_back(innerOp); + std::ranges::replace(worklist, innerOp, static_cast(nullptr)); + }); + // propagate through then and else block if (!thenEmpty) { for (const Value arg : thenBlock->getArguments()) { thenArgs.push_back(arg); } 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); - std::ranges::replace(worklist, innerOp, static_cast(nullptr)); - }); - std::span wl(newWorklist.data(), newWorklist.size()); + std::span wl(newThenWorklist.data(), newThenWorklist.size()); std::vector newPosClassicalCtrls = {posClassicalCtrls.begin(), posClassicalCtrls.end()}; newPosClassicalCtrls.push_back(condition); @@ -524,13 +531,8 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, elseArgs.push_back(arg); } ut->replaceValuesGlobally(thenArgs.empty() ? targets : thenArgs, elseArgs); - std::vector newWorklist; - op->elseBlock()->walk([&](Operation* innerOp) { - newWorklist.push_back(innerOp); - std::ranges::replace(worklist, innerOp, static_cast(nullptr)); - }); - std::span wl(newWorklist.data(), newWorklist.size()); + std::span wl(newElseWorklist.data(), newElseWorklist.size()); std::vector newNegClassicalCtrls = {negClassicalCtrls.begin(), negClassicalCtrls.end()}; newNegClassicalCtrls.push_back(condition); From 8b17960dec7226da9e2dd25987d35870ba3123fd Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 16 Jul 2026 08:06:46 +1000 Subject: [PATCH 146/235] :fire: Remove Debug Strings --- .../Optimizations/ConstantPropagation.cpp | 26 ++----------------- .../test_qco_constant_propagation.cpp | 9 +++---- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 1780b082e1..869dd5c2f0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -10,8 +10,7 @@ /* * Copyright (c) 2023 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Qua#include - * "mlir/Dialect/QCO/IR/QCOInterfaces.h"ntum Software Company GmbH All rights + * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH All rights * reserved. * * SPDX-License-Identifier: MIT @@ -49,11 +48,9 @@ #include #include #include -#include #include #include #include -#include #include namespace { @@ -538,7 +535,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, newNegClassicalCtrls.push_back(condition); const auto resElse = iterateThroughWorklist( rewriter, ut, wl, posClassicalCtrls, newNegClassicalCtrls); - std::cout << "Iterated through else branch..." << std::endl; if (resElse.failed()) { return WalkResult::interrupt(); @@ -552,7 +548,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, std::ranges::replace(elseArgs, input[i], output[i]); } }); - std::cout << "Walked through else branch..." << std::endl; } const auto resultQubits = op->getResults(); std::vector results = {resultQubits.begin(), resultQubits.end()}; @@ -569,7 +564,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, std::vector order; bool implicitSwap = false; if (!thenArgs.empty()) { - std::cout << "Then args not empty..." << std::endl; for (unsigned int i = 0; i < thenBlock->getArguments().size(); ++i) { auto it = std::ranges::find(thenArgs, thenBlock->getArguments()[i]); if (it != thenArgs.end()) { @@ -579,7 +573,6 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } } if (!elseArgs.empty()) { - std::cout << "Else args not empty..." << std::endl; for (unsigned int i = 0; i < elseBlock->getArguments().size(); ++i) { auto it = std::ranges::find(elseArgs, elseBlock->getArguments()[i]); if (it != elseArgs.end()) { @@ -609,11 +602,9 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, inputQubitVec); rewriter.eraseOp(*op); - std::cout << "Erased If Op..." << std::endl; } else { ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, results); } - std::cout << "Returning to iteration..." << std::endl; return WalkResult::advance(); } @@ -942,25 +933,14 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, /// Iterate work-list. bool addedAtLeastOneQubit = false; for (Operation* curr : worklist) { - std::cout << "Starting with curr operation..." << std::endl; if (addedAtLeastOneQubit && ut->areStatesAllTop()) { return success(); } - std::cout << "Continuing" << std::endl; if (curr == nullptr) { - std::cout << "Op is null" << std::endl; continue; // Skip erased ops. } - std::cout << "Op is not null" << std::endl; - std::string oName = - "Op: " + curr->getName().getStringRef().str() + - " dialect: " + curr->getName().getDialectNamespace().str(); - - std::cout << oName << std::endl; - std::cout << "Setting insertion point" << std::endl; rewriter.setInsertionPoint(curr); - std::cout << "Insertion point set" << std::endl; const auto res = TypeSwitch(curr) @@ -1053,9 +1033,7 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, if (res.wasInterrupted()) { return failure(); } - std::cout << "Finished curr operation" << std::endl; } - std::cout << "Iterated" << std::endl; return success(); } @@ -1081,7 +1059,7 @@ 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. + * tracked quantum states before reaching top. * @param maxHybridStates The maximum number of hybrid states which have a * non-zero probability. * @return Success if constant propagation has been applied successfully 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 e1e49ef2be..c7ff9799a3 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 @@ -201,8 +201,6 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableQuantumCombination) { ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - std::cout << "Did CP" << std::endl; - EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } @@ -217,15 +215,14 @@ 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); - // const auto qRange01 = - programBuilder.qcoIf( + const auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, [&](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}; }); - // programBuilder.y(qRange01[1]); + programBuilder.y(qRange01[1]); module = programBuilder.finalize(); auto qRef = referenceBuilder.allocQubitRegister(2); @@ -233,7 +230,7 @@ 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); + referenceBuilder.y(qRef1); reference = referenceBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); From 9b048ab163861be9104aff3cc21846826b6ef06d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 11:42:27 +1000 Subject: [PATCH 147/235] :bug: Prevented HS from being copied and not changed --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 36829be7fb..5190fcd395 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -193,7 +193,7 @@ void UnionTable::propagateClassicalOperation( const Value operand3 = targets.size() > 2 ? targets[2] : nullptr; const auto ute = valuesToEntries.at(*targets.begin()); - for (auto hs : ute->states) { + for (auto& hs : ute->states) { hs.propagateClassicalOperation(op, result, operand1, operand2, operand3, posCtrlsClassical, negCtrlsClassical); if (hs.isHybridStateTop()) { From b34644e8930c7eee8b430c0aa534d18133db2abe Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 11:43:02 +1000 Subject: [PATCH 148/235] :white_check_mark: Added test to remove class ctrl from various gates --- .../test_qco_constant_propagation.cpp | 54 +++++++++++++++++++ 1 file changed, 54 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 c7ff9799a3..c6cfaba8bd 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 @@ -313,6 +313,60 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionalIfItsOne) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks for various gates if they are removed if the + * classical conditional is true. + */ +TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionals) { + auto q = programBuilder.allocQubitRegister(1); + const auto bTrue = arith::ConstantOp::create(programBuilder, + programBuilder.getBoolAttr(true)) + .getResult(); + const auto bFalse = arith::ConstantOp::create( + programBuilder, programBuilder.getBoolAttr(false)) + .getResult(); + auto b = arith::OrIOp::create(programBuilder, bTrue.getType(), bTrue, bFalse) + .getResult(); + q[0] = programBuilder.h(q[0]); + programBuilder.qcoIf(b, {q[0]}, [&](const ValueRange args) { + const auto qi0 = programBuilder.u2(1.4, 2.7, args[0]); + const auto qi1 = programBuilder.sdg(qi0); + const auto qi2 = programBuilder.t(qi1); + const auto qi3 = programBuilder.sx(qi2); + const auto qi4 = programBuilder.tdg(qi3); + const auto qi5 = programBuilder.sxdg(qi4); + const auto qi6 = programBuilder.r(0.2, 0.4, qi5); + return SmallVector{qi6}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + const auto bTrueRef = + arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getBoolAttr(true)) + .getResult(); + const auto bFalseRef = + arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getBoolAttr(false)) + .getResult(); + arith::OrIOp::create(referenceBuilder, bTrue.getType(), bTrueRef, bFalseRef) + .getResult(); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[0] = referenceBuilder.u2(1.4, 2.7, qRef[0]); + qRef[0] = referenceBuilder.sdg(qRef[0]); + qRef[0] = referenceBuilder.t(qRef[0]); + qRef[0] = referenceBuilder.sx(qRef[0]); + qRef[0] = referenceBuilder.tdg(qRef[0]); + qRef[0] = referenceBuilder.sxdg(qRef[0]); + qRef[0] = referenceBuilder.r(0.2, 0.4, qRef[0]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.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. From aaaded2fa71737ba5291ae2e58bdb203d4347199 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 11:47:34 +1000 Subject: [PATCH 149/235] :white_check_mark: Added test for reset --- .../test_qco_constant_propagation.cpp | 26 +++++++++++++++++++ 1 file changed, 26 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 c6cfaba8bd..a9dd7e74c4 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 @@ -127,6 +127,32 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks that CNOTs are not changed if a reset is + * between to Hadamards, i.e. the qubits are in a superposition after the second + * Hadamard. + */ +TEST_F(QCOConstantPropagationTest, testApplyReset) { + auto q = programBuilder.allocQubitRegister(2); + q[0] = programBuilder.h(q[0]); + q[0] = programBuilder.reset(q[0]); + 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]); + qRef[0] = referenceBuilder.reset(qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.cx(qRef[0], qRef[1]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + /** * @brief Test: This test checks that implied Qubits are removed from a * controlled gate. From b1cce365f4463a3d7dc01dd5f11cf01f1e99534d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 13:45:12 +1000 Subject: [PATCH 150/235] :white_check_mark: Added test for classical operations --- .../test_qco_constant_propagation.cpp | 44 +++++++++++++++++++ 1 file changed, 44 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 a9dd7e74c4..f73ad61fb7 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 @@ -393,6 +393,50 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionals) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks if classical integer and float operations are + * applied correctly. + */ +TEST_F(QCOConstantPropagationTest, testApplyClassicalOperations) { + auto q = programBuilder.allocQubitRegister(1); + const auto floatSeven = programBuilder.floatConstant(7.2); + const auto floatZero = programBuilder.floatConstant(0.0); + auto floatRes = arith::MulFOp::create(programBuilder, floatSeven.getType(), + floatSeven, floatZero) + .getResult(); + const auto intTwo = programBuilder.intConstant(2); + const auto intTen = programBuilder.intConstant(10); + auto intRes = + arith::AddIOp::create(programBuilder, intTwo.getType(), intTwo, intTen) + .getResult(); + q[0] = programBuilder.h(q[0]); + programBuilder.qcoIf(intRes, {q[0]}, [&](const ValueRange args) { + const auto q0 = programBuilder.t(args[0]); + return SmallVector{q0}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(1); + const auto floatSevenRef = referenceBuilder.floatConstant(7.2); + const auto floatZeroRef = referenceBuilder.floatConstant(0.0); + arith::MulFOp::create(referenceBuilder, floatSevenRef.getType(), + floatSevenRef, floatZeroRef) + .getResult(); + const auto intTwoRef = referenceBuilder.intConstant(2); + const auto intTenRef = referenceBuilder.intConstant(10); + arith::AddIOp::create(referenceBuilder, intTwoRef.getType(), intTwoRef, + intTenRef) + .getResult(); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.t(qRef[0]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.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. From 4c3d6602cd8795285ab35e61e93475e21bce374f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 13:52:33 +1000 Subject: [PATCH 151/235] :white_check_mark: Added test to remove only some q ctrls --- .../test_qco_constant_propagation.cpp | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 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 f73ad61fb7..9263ba5d15 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 @@ -401,9 +401,9 @@ TEST_F(QCOConstantPropagationTest, testApplyClassicalOperations) { auto q = programBuilder.allocQubitRegister(1); const auto floatSeven = programBuilder.floatConstant(7.2); const auto floatZero = programBuilder.floatConstant(0.0); - auto floatRes = arith::MulFOp::create(programBuilder, floatSeven.getType(), - floatSeven, floatZero) - .getResult(); + arith::MulFOp::create(programBuilder, floatSeven.getType(), floatSeven, + floatZero) + .getResult(); const auto intTwo = programBuilder.intConstant(2); const auto intTen = programBuilder.intConstant(10); auto intRes = @@ -554,6 +554,44 @@ TEST_F(QCOConstantPropagationTest, testEquivalentClassicalAndQuantumControl) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks that one of multiple quantum controls are + * replaced by a classical control. + */ +TEST_F(QCOConstantPropagationTest, testRemoveSomeQuantumControl) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.h(q[1]); + auto [q0, b0] = programBuilder.measure(q[0]); + auto [q01, q2] = + programBuilder.ctrl({q0, q[1]}, {q[2]}, [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + programBuilder.x(q01[0]); + programBuilder.y(q01[1]); + programBuilder.z(q2[0]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + auto [qRef01, bRef0] = referenceBuilder.measure(qRef[0]); + const auto qRangeRef = referenceBuilder.qcoIf( + bRef0, {qRef[1], qRef[2]}, [&](const ValueRange args) { + const auto [qi0, qi1] = referenceBuilder.cx(args[0], args[1]); + return SmallVector{qi0, qi1}; + }); + referenceBuilder.x(qRef01); + referenceBuilder.y(qRangeRef[0]); + referenceBuilder.z(qRangeRef[1]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.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. From 3f232f4bb2c4d555021ec3e6189693337935d030 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 20:00:19 +1000 Subject: [PATCH 152/235] :white_check_mark: Added additional tests --- .../ConstantPropagation/UnionTable.cpp | 7 +- .../ConstantPropagation/test_unionTable.cpp | 85 ++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 5190fcd395..ebe30d33e7 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -463,6 +463,11 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, } const auto targetIndex = qubitsToGlobalIndices.at(target); const auto targetUte = valuesToEntries.at(target); + + if (targetUte->top) { + return {}; + } + bool alwaysOne = true; bool alwaysZero = true; @@ -537,7 +542,7 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, return {std::numbers::pi / 4}; } // Tdg Op - return {-std::numbers::pi / 2}; + return {-std::numbers::pi / 4}; } 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 0239e1cc84..97498ef42f 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -384,6 +384,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { mlir::Value v5; mlir::Value v6; mlir::Value i0; + mlir::Value i1; std::vector q0; std::vector q1; @@ -406,7 +407,7 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { programBuilder.initialize(); referenceBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(7); + 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(), @@ -431,6 +432,8 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { i0 = mlir::arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); + i1 = mlir::arith::ConstantOp::create(programBuilder, + programBuilder.getLoc(), iAttr); } void TearDown() override {} @@ -449,6 +452,18 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, propagateQubitAlloc) { "Qubits: 1, HybridStates: {{|0> -> 1.00}: p = 1.00;}")); } +TEST_F(UnionTableWithoutSetupAllocationsTest, applyGateAndGetToTop) { + auto ut = UnionTable(2, 1); + ut.propagateQubitAlloc(v0); + ut.propagateQubitAlloc(v1); + ut.propagateGate(hOp, q0, q2); + ut.propagateGate(hOp, q1, q3); + ut.propagateGate(xOp, q2, q4, q3, q5); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); +} + TEST_F(UnionTableWithoutSetupAllocationsTest, doMeasurementsAndGetToTop) { auto ut = UnionTable(4, 1); ut.propagateQubitAlloc(v1); @@ -502,6 +517,55 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, unifyTooLargeHybridStates) { testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); } +TEST_F(UnionTableWithoutSetupAllocationsTest, globalPhaseClassicallyDependend) { + const auto sOp = + SOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); + std::vector classicalIndexVecZero = {i0}; + std::vector classicalIndexVecOne = {i1}; + auto ut = UnionTable(4, 4); + ut.propagateQubitAlloc(v0); + ut.propagateGate(xOp, q0, q1); + ut.propagateDoubleAlloc(i0, 0.0); + ut.propagateDoubleAlloc(i1, 1.0); + + const auto globalPhaseCondsFalse = ut.globalPhaseThatIsAdded( + sOp, v1, {}, classicalIndexVecZero, classicalIndexVecOne); + const auto globalPhaseCondsTrue = ut.globalPhaseThatIsAdded( + sOp, v1, {}, classicalIndexVecOne, classicalIndexVecZero); + + ASSERT_FALSE(globalPhaseCondsFalse.has_value()); + ASSERT_TRUE(globalPhaseCondsTrue.has_value()); + ASSERT_EQ(std::numbers::pi / 2, globalPhaseCondsTrue); +} + +TEST_F(UnionTableWithoutSetupAllocationsTest, globalPhaseVariousGates) { + const auto idOp = + IdOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); + const auto sdgOp = + SdgOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); + const auto tOp = + TOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); + const auto tdgOp = + TdgOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); + auto ut = UnionTable(2, 1); + ut.propagateQubitAlloc(v0); + ut.propagateGate(xOp, q0, q1); + + const auto globalPhaseId = ut.globalPhaseThatIsAdded(idOp, v1); + const auto globalPhaseSdg = ut.globalPhaseThatIsAdded(sdgOp, v1); + const auto globalPhaseT = ut.globalPhaseThatIsAdded(tOp, v1); + const auto globalPhaseTdg = ut.globalPhaseThatIsAdded(tdgOp, v1); + + ASSERT_TRUE(globalPhaseId.has_value()); + ASSERT_TRUE(globalPhaseSdg.has_value()); + ASSERT_TRUE(globalPhaseT.has_value()); + ASSERT_TRUE(globalPhaseTdg.has_value()); + ASSERT_EQ(0.0, globalPhaseId); + ASSERT_EQ(3.0 * std::numbers::pi / 2, globalPhaseSdg); + ASSERT_EQ(std::numbers::pi / 4, globalPhaseT); + ASSERT_EQ(-std::numbers::pi / 4, globalPhaseTdg); +} + class UnionTablePropertiesTest : public testing::Test { protected: mlir::MLIRContext context; @@ -511,6 +575,7 @@ class UnionTablePropertiesTest : public testing::Test { HOp hOp; XOp xOp; ZOp zOp; + SOp sOp; SWAPOp swapOp; mlir::Value v0; @@ -565,6 +630,8 @@ class UnionTablePropertiesTest : public testing::Test { q[0]); zOp = ZOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); + sOp = SOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), {q[0].getType(), q[1].getType()}, {q[0], q[1]}); @@ -729,6 +796,22 @@ TEST_F(UnionTablePropertiesTest, testOneGlobalPhase) { EXPECT_EQ(0.0, globalPhase.value()); } +TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValueOnZeroQubit) { + const llvm::DenseMap result = + ut.getValueThatIsEquivalentToQubit(v0); + ASSERT_FALSE(result.empty()); + ASSERT_TRUE(result.at(i0)); +} + +TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValueOnOneQubit) { + ut.propagateGate(xOp, q0, q4); + ut.propagateIntAlloc(i1, 10); + const llvm::DenseMap result = + ut.getValueThatIsEquivalentToQubit(v4); + ASSERT_FALSE(result.empty()); + ASSERT_TRUE(result.at(i1)); +} + TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4, q6); From 3fc3c46e1da25d9a477a14f0b0c73b58706d81d9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 20:06:38 +1000 Subject: [PATCH 153/235] :pencil2: Fixed typo --- .../Optimizations/ConstantPropagation/test_unionTable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 97498ef42f..c258085074 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -517,7 +517,7 @@ TEST_F(UnionTableWithoutSetupAllocationsTest, unifyTooLargeHybridStates) { testing::HasSubstr("Qubits: 10, HybridStates: {TOP}")); } -TEST_F(UnionTableWithoutSetupAllocationsTest, globalPhaseClassicallyDependend) { +TEST_F(UnionTableWithoutSetupAllocationsTest, globalPhaseClassicallyDependent) { const auto sOp = SOp::create(programBuilder, programBuilder.getLoc(), v0.getType(), v0); std::vector classicalIndexVecZero = {i0}; From caebb615abed06040b84cbd9e37201df38788ac4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 21:54:31 +1000 Subject: [PATCH 154/235] :white_check_mark: Added tests --- .../ConstantPropagation/HybridState.cpp | 3 +- .../ConstantPropagation/test_hybridState.cpp | 134 ++++++++++++++++++ .../ConstantPropagation/test_unionTable.cpp | 18 +++ 3 files changed, 154 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 536918e6dd..4dfd3a0f07 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -110,7 +110,8 @@ bool HybridState::operator==(const HybridState& that) const { return std::ranges::all_of(doubleValues, [&](const auto& p) { auto it = that.doubleValues.find(p.first); - return it != that.doubleValues.end() && std::fabs(it->second) <= 1e-4; + return it != that.doubleValues.end() && + std::fabs(it->second - p.second) <= 1e-4; }); } 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 17e4ec81b2..14aea678ec 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -578,4 +578,138 @@ TEST_F(HybridStateTest, doubleOpTwoValueOperation) { testing::AnyOf(testing::HasSubstr("doubleValue0 = -3.25"), testing::HasSubstr("doubleValue1 = -3.25"))); } + +TEST_F(HybridStateTest, isDoubleValueTrue) { + auto hState = HybridState(fourQubits, 3); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(0.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.5); + hState.addDoubleValue(val2, 0.0); + + EXPECT_TRUE(hState.isValueTrue(val1)); + EXPECT_FALSE(hState.isValueTrue(val2)); +} + +TEST_F(HybridStateTest, hybridStatesEqual) { + auto hState1 = HybridState(vectorZeroTwoFour, 3); + auto hState2 = HybridState(vectorZeroTwoFour, 3); + const auto i0 = programBuilder.getI64IntegerAttr(0); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(0.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); + + hState1.addIntegerValue(val0, 9); + hState1.addDoubleValue(val1, -2.5); + hState1.addDoubleValue(val2, 0.0); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + hState2.addIntegerValue(val0, 9); + hState2.addDoubleValue(val1, -2.5); + hState2.addDoubleValue(val2, 0.0); + hState2.propagateGate(hOp.getOperation(), vectorFour); + hState2.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + EXPECT_TRUE(hState1 == hState2); +} + +TEST_F(HybridStateTest, hybridStatesNotEqualQuantumStates) { + auto hState1 = HybridState(vectorZeroTwoFour, 3); + auto hState2 = HybridState(vectorZeroTwoFour, 3); + const auto i0 = programBuilder.getI64IntegerAttr(0); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(0.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); + + hState1.addIntegerValue(val0, 9); + hState1.addDoubleValue(val1, -2.5); + hState1.addDoubleValue(val2, 0.0); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + hState2.addIntegerValue(val0, 9); + hState2.addDoubleValue(val1, -2.5); + hState2.addDoubleValue(val2, 0.0); + hState2.propagateGate(hOp.getOperation(), vectorFour); + hState2.propagateGate(xOp.getOperation(), vectorTwo); + + EXPECT_TRUE(hState1 != hState2); +} + +TEST_F(HybridStateTest, hybridStatesNotEqualInteger) { + auto hState1 = HybridState(vectorZeroTwoFour, 3); + auto hState2 = HybridState(vectorZeroTwoFour, 3); + const auto i0 = programBuilder.getI64IntegerAttr(0); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(0.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); + + hState1.addIntegerValue(val0, 9); + hState1.addDoubleValue(val1, -2.5); + hState1.addDoubleValue(val2, 0.0); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + hState2.addIntegerValue(val0, 10); + hState2.addDoubleValue(val1, -2.5); + hState2.addDoubleValue(val2, 0.0); + hState2.propagateGate(hOp.getOperation(), vectorFour); + hState2.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + EXPECT_TRUE(hState1 != hState2); +} + +TEST_F(HybridStateTest, hybridStatesNotEqualDouble) { + auto hState1 = HybridState(vectorZeroTwoFour, 3); + auto hState2 = HybridState(vectorZeroTwoFour, 3); + const auto i0 = programBuilder.getI64IntegerAttr(0); + const auto i1 = programBuilder.getF64FloatAttr(-2.5); + const auto i2 = programBuilder.getF64FloatAttr(0.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); + + hState1.addIntegerValue(val0, 9); + hState1.addDoubleValue(val1, -2.5); + hState1.addDoubleValue(val2, 0.0); + hState1.propagateGate(hOp.getOperation(), vectorFour); + hState1.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + hState2.addIntegerValue(val0, 9); + hState2.addDoubleValue(val1, -2.5); + hState2.addDoubleValue(val2, 0.5); + hState2.propagateGate(hOp.getOperation(), vectorFour); + hState2.propagateGate(xOp.getOperation(), vectorTwo, vectorFour); + + EXPECT_TRUE(hState1 != hState2); +} + } // namespace 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 c258085074..b874b5b822 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -812,6 +812,24 @@ TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValueOnOneQubit) { ASSERT_TRUE(result.at(i1)); } +TEST_F(UnionTablePropertiesTest, FindInverseClassicalDoubleValueOnOneQubit) { + ut.propagateGate(xOp, q0, q4); + ut.propagateDoubleAlloc(i1, 0.0); + const llvm::DenseMap result = + ut.getValueThatIsEquivalentToQubit(v4); + ASSERT_FALSE(result.empty()); + ASSERT_FALSE(result.at(i1)); +} + +TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalDoubleValueOnOneQubit) { + ut.propagateGate(xOp, q0, q4); + ut.propagateDoubleAlloc(i1, 10.0); + const llvm::DenseMap result = + ut.getValueThatIsEquivalentToQubit(v4); + ASSERT_FALSE(result.empty()); + ASSERT_TRUE(result.at(i1)); +} + TEST_F(UnionTablePropertiesTest, FindEquivalentClassicalValue) { ut.propagateGate(hOp, q0, q4); ut.propagateGate(xOp, q1, q5, q4, q6); From 4b71d9d4078fbe8ad1c04bd39e1a4319ed97e327 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 23:00:16 +1000 Subject: [PATCH 155/235] :white_check_mark: Added tests for gates --- .../ConstantPropagation/GateToMap.h | 8 +- .../ConstantPropagation/test_quantumState.cpp | 97 ++++++++++++++++--- 2 files changed, 88 insertions(+), 17 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 6a2326e6ba..663ebdcb76 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -183,10 +183,10 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { .Case([&](auto) { 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)}}}}}; + return ResultMap{{{0, {{0, Complex(c, 0)}, {2, Complex(0, -s)}}}, + {1, {{1, Complex(c, 0)}, {3, Complex(0, s)}}}, + {2, {{0, Complex(0, -s)}, {2, Complex(c, 0)}}}, + {3, {{1, Complex(0, s)}, {3, Complex(c, 0)}}}}}; }) .Case([&](auto) { const double halfParam = 0.5 * params[0]; 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 74f07b5d67..ce09d1176d 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -43,14 +43,27 @@ class QuantumStateTest : public testing::Test { std::vector vectorTwoOne = {2, 1}; std::vector vectorZeroTwoFour = {0, 2, 4}; + IdOp idOp; HOp hOp; XOp xOp; ZOp zOp; SOp sOp; + SXOp sxOp; + SXdgOp sxdgOp; TdgOp tdgOp; UOp uOp; DCXOp dcxOp; SWAPOp swapOp; + iSWAPOp iSwapOp; + RZOp rzOp; + POp pOp; + ECROp ecrOp; + RXXOp rxxOp; + RYYOp ryyOp; + RZXOp rzxOp; + RZZOp rzzOp; + XXPlusYYOp xxPlusyyOp; + XXMinusYYOp xxMinusyyOp; QuantumStateTest() : programBuilder(&context), referenceBuilder(&context) {} @@ -65,6 +78,8 @@ class QuantumStateTest : public testing::Test { referenceBuilder.initialize(); auto q = programBuilder.allocQubitRegister(4); + idOp = IdOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); hOp = HOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), @@ -73,6 +88,10 @@ class QuantumStateTest : public testing::Test { q[0]); sOp = SOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); + sxOp = SXOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), + q[0]); + sxdgOp = SXdgOp::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(), @@ -81,12 +100,35 @@ class QuantumStateTest : public testing::Test { 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]); + iSwapOp = iSWAPOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1]); + rzOp = RZOp::create(programBuilder, programBuilder.getLoc(), + {q[0].getType()}, {q[0], q[1]}); + pOp = POp::create(programBuilder, programBuilder.getLoc(), {q[0].getType()}, + {q[0], q[1]}); + ecrOp = ECROp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1]); + rxxOp = RXXOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1], q[2]); + ryyOp = RYYOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1], q[2]); + rzxOp = RZXOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1], q[2]); + rzzOp = RZZOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1], q[2]); + xxPlusyyOp = XXPlusYYOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], q[1], + q[2], q[3]); + xxMinusyyOp = XXMinusYYOp::create(programBuilder, programBuilder.getLoc(), + q[0].getType(), q[1].getType(), q[0], + q[1], q[2], q[3]); + ; } void TearDown() override {} }; -TEST_F(QuantumStateTest, ApplyHGate) { +TEST_F(QuantumStateTest, applyHGate) { auto qState = QuantumState(vectorZero, 4); qState.propagateGate(hOp.getOperation(), vectorZero); @@ -94,7 +136,7 @@ TEST_F(QuantumStateTest, ApplyHGate) { testing::HasSubstr("|0> -> 0.71, |1> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyHGateToThirdQubit) { +TEST_F(QuantumStateTest, applyHGateToThirdQubit) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); @@ -102,7 +144,7 @@ TEST_F(QuantumStateTest, ApplyHGateToThirdQubit) { testing::HasSubstr("|0000> -> 0.71, |0100> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyHHGateToThirdQubit) { +TEST_F(QuantumStateTest, applyHHGateToThirdQubit) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); qState.propagateGate(hOp.getOperation(), vectorTwo); @@ -110,7 +152,7 @@ TEST_F(QuantumStateTest, ApplyHHGateToThirdQubit) { EXPECT_THAT(qState.toString(), testing::HasSubstr("|0000> -> 1")); } -TEST_F(QuantumStateTest, ApplyHZGateToThirdQubit) { +TEST_F(QuantumStateTest, applyHZGateToThirdQubit) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); qState.propagateGate(zOp.getOperation(), vectorTwo); @@ -119,7 +161,7 @@ TEST_F(QuantumStateTest, ApplyHZGateToThirdQubit) { testing::HasSubstr("|0000> -> 0.71, |0100> -> -0.71")); } -TEST_F(QuantumStateTest, ApplyHZHGateToThirdQubit) { +TEST_F(QuantumStateTest, applyHZHGateToThirdQubit) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); qState.propagateGate(zOp.getOperation(), vectorTwo); @@ -128,7 +170,7 @@ TEST_F(QuantumStateTest, ApplyHZHGateToThirdQubit) { EXPECT_THAT(qState.toString(), testing::HasSubstr("|0100> -> 1")); } -TEST_F(QuantumStateTest, ApplyHGatesToTwoQubits) { +TEST_F(QuantumStateTest, applyHGatesToTwoQubits) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); qState.propagateGate(xOp.getOperation(), vectorZero); @@ -137,7 +179,7 @@ TEST_F(QuantumStateTest, ApplyHGatesToTwoQubits) { testing::HasSubstr("|0001> -> 0.71, |0101> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyParametrizedGateToThirdQubit) { +TEST_F(QuantumStateTest, applyParametrizedGateToThirdQubit) { std::vector params = {1, 0.5, 2}; auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorTwo); @@ -148,7 +190,7 @@ TEST_F(QuantumStateTest, ApplyParametrizedGateToThirdQubit) { testing::HasSubstr("|0000> -> 0.76 - i0.31, |0100> -> -0.20 + i0.53")); } -TEST_F(QuantumStateTest, ApplyTwoQubitGate) { +TEST_F(QuantumStateTest, applyTwoQubitGate) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorOne); qState.propagateGate(sOp.getOperation(), vectorOne); @@ -162,7 +204,7 @@ TEST_F(QuantumStateTest, ApplyTwoQubitGate) { "|0100> -> 0.35 + i0.35, |0110> -> 0.00 + i0.50")); } -TEST_F(QuantumStateTest, ApplyTwoQubitGateReversedOrd) { +TEST_F(QuantumStateTest, applyTwoQubitGateReversedOrd) { std::vector vectorOneTwo = {1, 2}; auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorOne); @@ -177,7 +219,7 @@ TEST_F(QuantumStateTest, ApplyTwoQubitGateReversedOrd) { "0.00 + i0.50, |0110> -> 0.35 - i0.35")); } -TEST_F(QuantumStateTest, ApplySwapGate) { +TEST_F(QuantumStateTest, applySwapGate) { std::vector vectorOneThree = {1, 3}; auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorOne); @@ -187,7 +229,7 @@ TEST_F(QuantumStateTest, ApplySwapGate) { testing::HasSubstr("|0000> -> 0.71, |1000> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyControlledGate1) { +TEST_F(QuantumStateTest, applyControlledGate1) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorOne); qState.propagateGate(xOp.getOperation(), vectorThree); @@ -197,7 +239,7 @@ TEST_F(QuantumStateTest, ApplyControlledGate1) { testing::HasSubstr("|0010> -> 0.71, |1000> -> 0.71")); } -TEST_F(QuantumStateTest, ApplyControlledGate2) { +TEST_F(QuantumStateTest, applyControlledGate2) { auto qState = QuantumState(fourQubits, 8); qState.propagateGate(hOp.getOperation(), vectorZero); qState.propagateGate(hOp.getOperation(), vectorOne); @@ -211,7 +253,7 @@ TEST_F(QuantumStateTest, ApplyControlledGate2) { "|0101> -> 0.35, |0110> -> 0.35, |1011> -> 0.35, |1111> -> 0.35")); } -TEST_F(QuantumStateTest, ApplyControlledTwoQubitGate) { +TEST_F(QuantumStateTest, applyControlledTwoQubitGate) { auto qState = QuantumState(fourQubits, 4); qState.propagateGate(hOp.getOperation(), vectorThree); qState.propagateGate(hOp.getOperation(), vectorTwo); @@ -344,4 +386,33 @@ TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { EXPECT_THROW(auto qs = qState1.unify(qState2), std::domain_error); } + +TEST_F(QuantumStateTest, applyVariousGates) { + std::vector paramsZero = {1.0}; + std::vector paramsOne = {2.7}; + std::vector paramsTwo = {1.3, 2.0}; + std::vector paramsThree = {-1.3, 2.0}; + auto qState = QuantumState(vectorZeroOne, 4); + qState.propagateGate(idOp.getOperation(), vectorZero); + qState.propagateGate(sxOp.getOperation(), vectorZero); + qState.propagateGate(sxdgOp.getOperation(), vectorOne); + qState.propagateGate(iSwapOp.getOperation(), vectorZeroOne); + qState.propagateGate(rzOp.getOperation(), vectorOne, {}, paramsZero); + qState.propagateGate(pOp.getOperation(), vectorZero, {}, paramsOne); + qState.propagateGate(ecrOp.getOperation(), vectorZeroOne); + qState.propagateGate(rxxOp.getOperation(), vectorZeroOne, {}, paramsZero); + qState.propagateGate(ryyOp.getOperation(), vectorZeroOne, {}, paramsZero); + qState.propagateGate(rzxOp.getOperation(), vectorZeroOne, {}, paramsZero); + qState.propagateGate(rzzOp.getOperation(), vectorZeroOne, {}, paramsZero); + qState.propagateGate(xxMinusyyOp.getOperation(), vectorZeroOne, {}, + paramsTwo); + qState.propagateGate(xxPlusyyOp.getOperation(), vectorZeroOne, {}, + paramsThree); + + EXPECT_THAT( + qState.toString(), + testing::HasSubstr("|00> -> -0.27 - i0.25, |01> -> 0.12 - i0.39, |10> " + "-> -0.62 + i0.43, |11> -> -0.15 - i0.32")); +} + } // namespace From dbc94ccaf114e9d7837cc6a54485cde18f85b182 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 23:26:15 +1000 Subject: [PATCH 156/235] :white_check_mark: Added tests --- .../Optimizations/ConstantPropagation.cpp | 2 +- .../test_qco_constant_propagation.cpp | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 869dd5c2f0..d6748924fa 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -578,7 +578,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (it != elseArgs.end()) { const unsigned int pos = std::distance(elseArgs.begin(), it); if (!thenArgs.empty()) { - implicitSwap |= order.at(i) == pos; + implicitSwap |= order.at(i) != pos; } else { order.push_back(pos); } 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 9263ba5d15..8194db2fb3 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 @@ -243,7 +243,10 @@ TEST_F(QCOConstantPropagationTest, testUnsatisfiableHybridCombination) { auto [q01, b0] = programBuilder.measure(q0); const auto qRange01 = programBuilder.qcoIf( b0, {q01, q1}, - [&](const ValueRange args) { return SmallVector{args[0], args[1]}; }, + [&](const ValueRange args) { + const auto [qi1, qi0] = programBuilder.ch(args[1], args[0]); + return SmallVector{qi0, qi1}; + }, [&](const ValueRange args) { const auto [qi0, qi1] = programBuilder.ch(args[0], args[1]); return SmallVector{qi0, qi1}; @@ -808,8 +811,8 @@ TEST_F(QCOConstantPropagationTest, testDoNotRemoveMultiQubitPhaseGate) { } /** - * @brief Test: This test checks that a quantum conditional is replaced by a - * classical if a qubit and a classical bit are equivalent. + * @brief Test: This test checks that a measurement is moved to front and the + * measurement result is afterward used as conditional. */ TEST_F(QCOConstantPropagationTest, testMoveMeasurementToFront) { auto q = programBuilder.allocQubitRegister(3); @@ -836,3 +839,31 @@ TEST_F(QCOConstantPropagationTest, testMoveMeasurementToFront) { EXPECT_TRUE( areModulesEquivalentWithPermutations(module.get(), reference.get())); } + +/** + * @brief Test: This test checks that two measurements are moved to front. + */ +TEST_F(QCOConstantPropagationTest, testMoveTwoMeasurementToFront) { + auto q = programBuilder.allocQubitRegister(4); + q[0] = programBuilder.h(q[0]); + auto [q0, q1] = programBuilder.cx(q[0], q[1]); + auto [q2, q3] = programBuilder.cx(q[2], q[3]); + programBuilder.h(q3); + programBuilder.measure(q0); + programBuilder.measure(q1); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(4); + qRef[0] = referenceBuilder.h(qRef[0]); + auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); + referenceBuilder.measure(qRef0); + referenceBuilder.measure(qRef1); + auto [qRef2, qRef3] = referenceBuilder.cx(qRef[2], qRef[3]); + referenceBuilder.h(qRef3); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} From 4cc8d347f475364488c0c6b1340e6c45eb806d5a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Fri, 17 Jul 2026 23:38:03 +1000 Subject: [PATCH 157/235] :white_check_mark: Fixed test --- .../Transforms/Optimizations/test_qco_constant_propagation.cpp | 2 ++ 1 file changed, 2 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 8194db2fb3..040f12132e 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 @@ -846,6 +846,7 @@ TEST_F(QCOConstantPropagationTest, testMoveMeasurementToFront) { TEST_F(QCOConstantPropagationTest, testMoveTwoMeasurementToFront) { auto q = programBuilder.allocQubitRegister(4); q[0] = programBuilder.h(q[0]); + q[2] = programBuilder.h(q[2]); auto [q0, q1] = programBuilder.cx(q[0], q[1]); auto [q2, q3] = programBuilder.cx(q[2], q[3]); programBuilder.h(q3); @@ -855,6 +856,7 @@ TEST_F(QCOConstantPropagationTest, testMoveTwoMeasurementToFront) { auto qRef = referenceBuilder.allocQubitRegister(4); qRef[0] = referenceBuilder.h(qRef[0]); + qRef[2] = referenceBuilder.h(qRef[2]); auto [qRef0, qRef1] = referenceBuilder.cx(qRef[0], qRef[1]); referenceBuilder.measure(qRef0); referenceBuilder.measure(qRef1); From bccd7520e6daa952453791ebe3adaa775fe83fdd Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 15:47:28 +0800 Subject: [PATCH 158/235] :memo: Corrected CHANGELOG.md formatting --- CHANGELOG.md | 197 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 163 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8adfe2cd21..a004cdecad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,16 +36,16 @@ releases may include breaking changes. Slurm license environment value ([#2025]) ([**@burgholzer**]) - ✨ Add an `unroll-modifiers` pass for unrolling multi-operation modifiers ([#2015]) ([**@denialhaag**], [**@burgholzer**]) -- ✨ Add Qiskit circuit import and export to the compiler collection ([#2031], - [#2133], [#2136], [#2140]) ([**@burgholzer**], [**@simon1hofmann**]) +- ✨ Add Qiskit circuit import and export to the compiler collection ([#2031]) + ([**@burgholzer**]) - ✨ Add generic C++ and Python FoMaC support for custom device properties that contain operation handles ([#2042]) ([**@burgholzer**]) - ✨ Support retrieving existing jobs by ID through the QDMI client API and C++ and Python FoMaC APIs, and expose optional device queue length and job queue position ([#2008], [#2010]) ([**@burgholzer**]) - 🐍 Start building CPython 3.15 wheels ([#2011]) ([**@denialhaag**]) -- ✨ Add PennyLane support for gate-based QDMI devices ([#2005], [#2147]) - ([**@burgholzer**], [**@marcelwa**]) +- ✨ Add PennyLane support for gate-based QDMI devices ([#2005]) + ([**@burgholzer**]) - ✨ Integrate QDMI devices as MLIR compiler targets across C++, Python, and `mqt-cc` ([#1687]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add structured OpenQASM emission from the QC dialect to the C++ and Python @@ -73,10 +73,8 @@ releases may include breaking changes. [#1979], [#2007], [#2026], [#2030], [#2066]) ([**@burgholzer**], [**@denialhaag**], [**@simon1hofmann**], [**@li-mingbao**], [**@DRovara**], [**@MatthiasReumann**]) -- ✨ Add decision diagram-based construction, simulation, and sampling of QCO - functions, including static unitaries, mid-circuit `measure`/`reset`, concrete - `if`/`index_switch`, initial classical SSA evaluation, dense `k>3` wire - embedding, and multi-shot `sample` ([#1915], [#1973]) ([**@simon1hofmann**]) +- ✨ Add decision diagram-based construction and simulation of static unitary + QCO functions ([#1915]) ([**@simon1hofmann**]) - ✨ Add target-independent two-qubit gate fusion, target-native post-routing synthesis, and operation-capability and static-site conformance ([#1865], [#1961], [#1998]) ([**@simon1hofmann**], [**@burgholzer**]) @@ -93,45 +91,75 @@ releases may include breaking changes. - ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) - 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function - ([#1826]) ([**@ystade]) + ([#1826]) ([**@ystade**]) - 🐳 Add dev container configuration for consistent local development environment ([#1786]) ([**@denialhaag**]) +- ✨ Add two-qubit Weyl (KAK) decomposition and native-gateset synthesis support + ([#1803], [#1832]) ([**@simon1hofmann**], [**@burgholzer**]) +- ✨ Extend the QCO unitary matrix library ([#1774], [#1802], [#1809], [#1814], + [#1850]) ([**@simon1hofmann**], [**@burgholzer**]) - ✨ Add a `fuse-single-qubit-unitary-runs` pass for fusing compile-time single-qubit unitary runs via Euler resynthesis ([#1672]) ([**@simon1hofmann**], [**@burgholzer**]) -- ✨ Add QIR program format support to the DDSIM QDMI Device ([#1766]) - ([**@rturrado**]) -- 🚸 Add [CMake presets] to provide a standardized and reproducible way to - configure builds ([#1660]) ([**@denialhaag**]) - ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**]) - ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**]) - ✨ Add a `merge-single-qubit-rotation-gates` pass for merging consecutive - rotation gates using quaternions ([#1407], [#1674]) ([**@J4MMlE**], - [**@denialhaag**], [**@MatthiasReumann**]) + fixed and parameterized single-qubit gates using quaternions with global-phase + correction ([#1407], [#1674], [#2002], [#2038]) ([**@J4MMlE**], + [**@denialhaag**], [**@MatthiasReumann**], [**@simon1hofmann**]) - ✨ Add conversions between `jeff` and QCO ([#1479], [#1548], [#1565], [#1637], - [#1676], [#1706], [#1776]) ([**@denialhaag**], [**@burgholzer**]) -- ✨ Add a `place-and-route` pass for mapping circuits to architectures with - restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], - [#1600], [#1664], [#1709], [#1716], [#1748]) ([**@MatthiasReumann**], - [**@burgholzer**]) + [#1676], [#1706], [#1776], [#1836], [#1934], [#2000], [#2018], [#2105]) + ([**@denialhaag**], [**@burgholzer**]) +- ✨ Add a `place-and-route` pass for mapping scalar- and tensor-allocated + circuits to compiler-target topologies while preserving target site IDs and + materializing routing workspace on demand ([#1537], [#1547], [#1568], [#1581], + [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], + [#1870], [#1904], [#1911], [#1951], [#1997], [#2016], [#2060]) + ([**@MatthiasReumann**], [**@burgholzer**]) +- ✨ Add a pass for qubit reuse in quantum programs, as well as related + auxiliary passes and patterns ([#1705], [#1755], [#1756], [#1923], [#1924], + [#2039]) ([**@DRovara**], [**@burgholzer**], [**@simon1hofmann**]) - ✨ 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], [#1638], [#1673], [#1675], [#1700], [#1710], - [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1774], - [#1780], [#1781], [#1782], [#1787], [#1802], [#1803], [#1806], [#1807], - [#1808], [#1809], [#1823], [#1824], [#1830]) ([**@burgholzer**], - [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], - [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) + [#1570], [#1572], [#1573], [#1580], [#1602], [#1603], [#1620], [#1623], + [#1626], [#1627], [#1635], [#1638], [#1673], [#1675], [#1700], [#1717], + [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], [#1781], + [#1782], [#1806], [#1807], [#1815], [#1808], [#1824], [#1869], [#1872], + [#1886], [#1914], [#1925], [#1927], [#1935], [#1936], [#1938], [#1975], + [#1976], [#2006], [#2014], [#2015], [#2017], [#2026], [#2028], [#2058]) + ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], + [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], + [**@simon1hofmann**], [**@J4MMlE**]) ### Changed -- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], - [#1737]) ([**@denialhaag**]) +- 💥 Prune dead and misleading CoreIR APIs, including renaming the non-garbage + logical output count to `getNoutputQubits()` and `num_output_qubits` ([#2112]) + ([**@simon1hofmann**]) +- ♻️ Simplify Python optional-dependency checks while preserving the Qiskit and + PennyLane availability flags ([#2108]) ([**@simon1hofmann**]) +- 💥 Remove the unused `pybind11` CMake helper and rename + `add_mqt_python_binding_nanobind` to `add_mqt_python_binding` ([#2106]) + ([**@denialhaag**]) +- 💥 Replace the MQT-specific QDMI primitive `options` mappings with explicit + shot and precision defaults ([#2084]) ([**@burgholzer**]) +- 💥 Move Python QDMI entities and the neutral-atom specialization to QDMI + namespaces, expose device registration and opening through + `mqt.core.qdmi.driver`, retain v3 FoMaC compatibility aliases, and let the + Qiskit adapter open stable device IDs directly ([#2074]) ([**@burgholzer**]) +- ⬆️ Update `nanobind` to version 2.14.0 ([#2073]) ([**@denialhaag**]) +- 🛡️ Isolate DDSIM QIR job execution and restrict statevector extraction to + terminal `irreversible` regions of Base-profile programs ([#2036]) + ([**@burgholzer**]) +- 💥 Update the QIR runner for QIR 2.1 entry points and resource management, + with entry-point selection and reproducible multi-shot execution ([#2035]) + ([**@burgholzer**]) +- 💥 Require LLVM/MLIR and QIR support in every MQT Core build and remove the + corresponding build options ([#1953]) ([**@burgholzer**]) - ⬆️ Require LLVM 22.1 for C++ library builds ([#1549]) ([**@burgholzer**], [**@denialhaag**]) - 📦 Build MLIR by default for C++ library builds ([#1356]) ([**@burgholzer**], @@ -139,16 +167,97 @@ releases may include breaking changes. ### Removed -- 📝 Remove support for generating LaTeX documentation ([#1828]) - ([**@denialhaag**]) +- 💥 Remove QDMI device configuration through `[tool.qdmi]` in `pyproject.toml` + and the vendored toml++ header ([#2116]) ([**@denialhaag**]) +- 💥 Remove the FoMaC compatibility name from the C++ and Python QDMI APIs. Use + the `qdmi` C++ namespace, headers, libraries, and CMake targets; the + `mqt.core.qdmi` and `mqt.core.na.qdmi` Python modules; and the module-level + Python driver functions ([#2115]) ([**@burgholzer**]) +- 💥 Remove the ZX-calculus library, including the `mqt-core-zx` target, + `MQT::CoreZX` alias, `zx` headers and namespace, and its Boost.Multiprecision + and GMP build support. Equivalence-checking users should use [MQT QCEC]; its + ZX implementation is internal and does not provide a replacement public API. - 🔥 Remove the density matrix support from the MQT Core DD package ([#1466]) ([**@burgholzer**]) - 🔥 Remove `datastructures` (`ds`) (sub)library from MQT Core ([#1458]) ([**@burgholzer**]) +## [3.8.0] - 2026-07-30 + +_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#380)._ + +### Added + +- ✨ Add binary-safe QDMI program submission and retrieval to FoMaC, including + explicit text and exact-byte APIs and all standard QDMI program formats + ([#1957]) ([**@burgholzer**]) +- ✨ Add versioned, relocatable configuration and stable-ID registration for + QDMI device libraries, including disabled-ID reservations, fresh device + sessions, idempotent registration, and external-device target metadata + ([#1912]) ([**@burgholzer**]) +- ✨ Add native relative-phase CCX (`rccx`) support across the IR, DD package, + ZX diagrams, OpenQASM import/export, and Python/Qiskit bindings ([#1886], + [#1950]) ([**@simon1hofmann**]) +- ✨ Add support for QDMI child devices to the driver and FoMaC libraries + ([#1897], [#1952]) ([**@burgholzer**]) +- ✨ Add typed custom property and result queries to the C++ and Python FoMaC + libraries ([#1895]) ([**@burgholzer**]) +- ✨ Add support for custom job parameters to C++ and Python FoMaC library + ([#1887]) ([**@flowerthrower**], [**@burgholzer**]) +- ✨ Add labeled and ordered output schemas to the QIR runtime ([#1877]) + ([**@rturrado**]) +- ✨ Add boolean, integer, floating-point, tuple, and array record output + functions to the QIR runtime ([#1799]) ([**@rturrado**]) +- ✨ Add the reusable in-process `MQT::CoreQIRJIT` library and QIR program + format support to the DDSIM QDMI device ([#1766]) ([**@rturrado**]) + +### Changed + +- ⬆️ Raise the minimum supported QDMI version to 1.3.2 ([#1897]) + ([**@burgholzer**]) + +### Removed + +- 🔥 Replace the unstable C++ `Driver::addDynamicDeviceLibrary` and Python + `add_dynamic_device_library` APIs with definition registration and stable-ID + opening ([#1912]) ([**@burgholzer**]) + ### Fixed +- 🐛 Allow MQT Core to be embedded as a CMake subproject without target + collisions and make its bundled QDMI devices individually configurable + ([#1965]) ([**@burgholzer**]) - 🐛 Fix QIR function names for adjoint gates ([#1830]) ([**@denialhaag**]) + +## [3.7.0] - 2026-07-09 + +_If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#370)._ + +### Added + +- ✨ Add support for IQM's `move` gate in the QDMI Qiskit backend converter + ([#1844], [#1848]) ([**@burgholzer**], [**@marcelwa**]) +- 🚸 Add `const` version of the `CompoundOperation`'s `getOps()` function + ([#1826]) ([**@ystade**]) +- 🚸 Add [CMake presets] to provide a standardized and reproducible way to + configure builds ([#1660]) ([**@denialhaag**]) + +### Changed + +- ⬆️ Update QDMI to version 1.3.2 ([#1873]) ([**@denialhaag**]) +- ♻️ Improve implementation and usability of FoMaC classes ([#1849]) + ([**@MatthiasReumann**]) +- ⬆️ Update `nanobind` to version 2.13.0 ([#1817]) +- ⬆️ Update [munich-quantum-toolkit/workflows] to version `v2.0.1` ([#1660], + [#1737]) ([**@denialhaag**]) + +### Removed + +- 📝 Remove support for generating LaTeX documentation ([#1828]) + ([**@denialhaag**]) + +### Fixed + - 🐛 Fix invalid `prop_type` for `QDMI_DEVICE_PROPERTY_COUPLINGMAP` in QDMI SC Device ([#1842]) ([**@MatthiasReumann**]) @@ -217,7 +326,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#350)._ ### Changed - ⬆️ Update `nanobind` to version 2.12.0 ([#1528]) -- ⬆️ Update QDMI to `v1.3.0` ([#1652]) ([**@burgholzer**]) +- ⬆️ Update QDMI to version 1.3.0 ([#1652]) ([**@burgholzer**]) - 📦 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]) @@ -653,7 +762,8 @@ changelogs._ -[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.6.1...HEAD +[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.7.0...HEAD +[3.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.7.0 [3.6.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.1 [3.6.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.0 [3.5.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.5.1 @@ -674,17 +784,35 @@ changelogs._ -[#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 +[#1904]: https://github.com/munich-quantum-toolkit/core/pull/1904 +[#1897]: https://github.com/munich-quantum-toolkit/core/pull/1897 +[#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895 +[#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 +[#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 +[#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 +[#1872]: https://github.com/munich-quantum-toolkit/core/pull/1872 +[#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 +[#1869]: https://github.com/munich-quantum-toolkit/core/pull/1869 +[#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 +[#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 +[#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 +[#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 +[#1836]: https://github.com/munich-quantum-toolkit/core/pull/1836 +[#1832]: https://github.com/munich-quantum-toolkit/core/pull/1832 [#1830]: https://github.com/munich-quantum-toolkit/core/pull/1830 [#1828]: https://github.com/munich-quantum-toolkit/core/pull/1828 [#1826]: https://github.com/munich-quantum-toolkit/core/pull/1826 [#1824]: https://github.com/munich-quantum-toolkit/core/pull/1824 [#1823]: https://github.com/munich-quantum-toolkit/core/pull/1823 +[#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 +[#1815]: https://github.com/munich-quantum-toolkit/core/pull/1815 +[#1814]: https://github.com/munich-quantum-toolkit/core/pull/1814 [#1809]: https://github.com/munich-quantum-toolkit/core/pull/1809 [#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808 [#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807 [#1806]: https://github.com/munich-quantum-toolkit/core/pull/1806 +[#1805]: https://github.com/munich-quantum-toolkit/core/pull/1805 [#1803]: https://github.com/munich-quantum-toolkit/core/pull/1803 [#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802 [#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787 @@ -697,6 +825,7 @@ changelogs._ [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 +[#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 [#1748]: https://github.com/munich-quantum-toolkit/core/pull/1748 From 29a98b489318219705d690c261c99cc373569655 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 15:50:55 +0800 Subject: [PATCH 159/235] =?UTF-8?q?=F0=9F=90=9B=20Corrected=20matrix=20rep?= =?UTF-8?q?resentations=20of=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/GateToMap.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 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 663ebdcb76..c3047b1f83 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -125,10 +125,12 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { }) .Case([&](auto) { return ResultMap{ - {{0, {{0, Complex(inv_sqrt2, 0)}, {1, exp(Complex(0, params[0]))}}}, + {{0, + {{0, Complex(inv_sqrt2, 0)}, + {1, inv_sqrt2 * exp(Complex(0, params[0]))}}}, {1, - {{0, -exp(Complex(0, params[1]))}, - {1, exp(Complex(0, params[0] + params[1]))}}}}}; + {{0, -inv_sqrt2 * exp(Complex(0, params[1]))}, + {1, inv_sqrt2 * exp(Complex(0, params[0] + params[1]))}}}}}; }) .Case([&](auto) { const double c = cos(0.5 * params[0]); @@ -147,8 +149,8 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { }) .Case([&](auto) { return ResultMap{{{0, {{0, Complex(1, 0)}}}, - {1, {{2, Complex(0, -1)}}}, - {2, {{1, Complex(0, -1)}}}, + {1, {{2, Complex(0, 1)}}}, + {2, {{1, Complex(0, 1)}}}, {3, {{3, Complex(1, 0)}}}}}; }) .Case([&](auto) { From b9d8d63cd77e3dcfcb50c188cf668ccbbf05cb0b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 15:55:03 +0800 Subject: [PATCH 160/235] :construction: Improved consistency in HybridState.hpp --- .../Optimizations/ConstantPropagation/HybridState.hpp | 7 ++----- 1 file changed, 2 insertions(+), 5 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 853b65bae7..0eec9c8d47 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_HYBRIDSTATE_H -#define MQT_CORE_HYBRIDSTATE_H +#pragma once #include "QuantumState.hpp" @@ -69,7 +68,7 @@ class HybridState { return false; } if (doubleValues.contains(negCtrl) && - std::norm(doubleValues.at(negCtrl)) > 1e-4) { + std::norm(doubleValues.at(negCtrl)) >= 1e-4) { return false; } if (!doubleValues.contains(negCtrl) && !integerValues.contains(negCtrl)) { @@ -318,5 +317,3 @@ class HybridState { getValueThatIsEquivalentToQubit(unsigned int qubit) const; }; } // namespace mlir::qco - -#endif // MQT_CORE_HYBRIDSTATE_H From 85dc765073b2b90a66b1a70b577e7fce877877db Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 16:07:05 +0800 Subject: [PATCH 161/235] :construction: Adapted QuantumState.hpp to the style guidelines --- .../Optimizations/ConstantPropagation/QuantumState.hpp | 5 +---- 1 file changed, 1 insertion(+), 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 db59ec1251..632ab0bba6 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,7 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_QUANTUMSTATE_H -#define MQT_CORE_QUANTUMSTATE_H +#pragma once #include #include @@ -345,5 +344,3 @@ class QuantumState { }; } // namespace mlir::qco - -#endif // MQT_CORE_QUANTUMSTATE_H From f3d634bcffe51ae7bea8dcba1218c2e174dc9b4b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 16:08:51 +0800 Subject: [PATCH 162/235] =?UTF-8?q?=F0=9F=90=9B=20Added=20check=20that=20p?= =?UTF-8?q?revents=20too=20large=20union=20tables=20to=20be=20created.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index aad4a0f2ef..6c147c717f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -40,6 +40,15 @@ QuantumState::QuantumState(const std::span globalQubitNumber, const std::size_t maxNonzeroAmplitudes) : nQubits(globalQubitNumber.size()), maxNonzeroAmplitudes(maxNonzeroAmplitudes) { + if (sizeof(unsigned int) < globalQubitNumber.size()) { + throw std::domain_error("Number of qubits exceeds amount of qubits that " + "can be managed in the union table."); + } + if (sizeof(unsigned int) < maxNonzeroAmplitudes) { + throw std::domain_error( + "Number of maximum nonzero amplitudes exceeds " + "amount of amplitudes that can be managed in the union table."); + } std::ranges::sort(globalQubitNumber); unsigned int localQ = 0; for (auto globalQ : globalQubitNumber) { From 871ec9630df4a83539e2a7278bf26ec22b54d625 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 16:22:06 +0800 Subject: [PATCH 163/235] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20overflowing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Optimizations/ConstantPropagation/UnionTable.hpp | 6 +++--- 1 file changed, 3 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 65066dafdd..cb4860025c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -168,11 +168,11 @@ class UnionTable { // Check if the number of entries would be too large unsigned int numberOfNewEntries = 1; for (const auto& e : entriesToUnify) { + if (numberOfNewEntries > maximumHybridEntries / e.states.size()) { + throw std::domain_error("Maximum of allowed hybrid entries exceeded."); + } numberOfNewEntries *= e.states.size(); } - if (numberOfNewEntries > maximumHybridEntries) { - throw std::domain_error("Maximum of allowed hybrid entries exceeded."); - } // Create new entry auto newEntry = UnionTableEntry(); From 6089674769b5c0103d4fbca2d212df5feaf7d297 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 19:10:08 +0800 Subject: [PATCH 164/235] :construction: Added additional checks. --- .../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 d6748924fa..2b5e771a53 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -593,8 +593,9 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, 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]); + rewriter.replaceAllUsesWith( + op->getLinearResults()[order.at(inputQubitIndex)], + op->getQubits()[inputQubitIndex]); } std::vector inputQubitVec = {op->getQubits().begin(), @@ -871,7 +872,7 @@ static WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, 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)) { + if (arguments[argIndex] == body.getInputTarget(i)) { targetQubits.insert(targetQubits.begin() + i, op->getInputTarget(argIndex)); break; @@ -1074,11 +1075,17 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, /// Prepare work-list. std::vector worklist; + bool entryPointFound = false; for (const auto func : module.getOps()) { if (!isEntryPoint(func)) { continue; // Ignore non entry_point functions for now. } + if (entryPointFound) { + throw std::domain_error("Constant propagation does not support programs " + "with more than one entry point."); + } + entryPointFound = true; func->walk( [&](Operation* op) { worklist.push_back(op); }); } From 0eff94c25bbab199a39305676725f65020494b2b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 19:13:58 +0800 Subject: [PATCH 165/235] :zap: Improved HybridState == operator --- .../Optimizations/ConstantPropagation/HybridState.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 4dfd3a0f07..4e82c7ac38 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -89,8 +89,8 @@ std::string HybridState::toString() const { } bool HybridState::operator==(const HybridState& that) const { - if (top) { - return that.top; + if (top || that.top) { + return top == that.top; } if (std::fabs(probability - that.probability) > 1e-4 || *qState.get() != *that.qState.get()) { From fd5eacd55c00a10719757522d969f81aca0469a1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 19:36:28 +0800 Subject: [PATCH 166/235] :construction: Do not alter non-owning span --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 6c147c717f..8222b26d08 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -49,9 +49,12 @@ QuantumState::QuantumState(const std::span globalQubitNumber, "Number of maximum nonzero amplitudes exceeds " "amount of amplitudes that can be managed in the union table."); } - std::ranges::sort(globalQubitNumber); + std::vector qubits; + qubits.reserve(globalQubitNumber.size()); + std::ranges::copy(globalQubitNumber, std::back_inserter(qubits)); + std::ranges::sort(qubits); unsigned int localQ = 0; - for (auto globalQ : globalQubitNumber) { + for (auto globalQ : qubits) { globalToLocalQubitNumber[globalQ] = localQ; ++localQ; } From 0717d59c5124810599271c9a29b07a790bc29d49 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 19:47:36 +0800 Subject: [PATCH 167/235] :construction: Added checks for edge-cases --- .../ConstantPropagation/UnionTable.cpp | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index ebe30d33e7..f321deabb9 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -296,6 +296,11 @@ void UnionTable::propagateReset(const Value quantumTarget, } } ute->states = vecOfNewStates; + if (vecOfNewStates.size() > maximumHybridEntries) { + putEntriesToTop({*ute}); + } else { + ute->states = vecOfNewStates; + } replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); } @@ -323,8 +328,9 @@ void UnionTable::propagateIntAlloc(const Value intValue, const int64_t number) { auto ute = UnionTableEntry(); ute.states.push_back(hs); ute.participatingClassicalValues.insert(intValue); - entries.insert(std::make_shared(ute)); - valuesToEntries[intValue] = std::make_shared(ute); + const auto utePtr = std::make_shared(ute); + entries.insert(utePtr); + valuesToEntries[intValue] = utePtr; } void UnionTable::propagateDoubleAlloc(const Value doubleValue, @@ -335,13 +341,17 @@ void UnionTable::propagateDoubleAlloc(const Value doubleValue, auto ute = UnionTableEntry(); ute.states.push_back(hs); ute.participatingClassicalValues.insert(doubleValue); - entries.insert(std::make_shared(ute)); - valuesToEntries[doubleValue] = std::make_shared(ute); + const auto utePtr = std::make_shared(ute); + entries.insert(utePtr); + valuesToEntries[doubleValue] = utePtr; } bool UnionTable::isQubitAlwaysOne(const Value q) const { const unsigned int qubitIndex = qubitsToGlobalIndices.at(q); const auto ute = valuesToEntries.at(q); + if (ute->top) { + return false; + } return std::ranges::all_of(ute->states, [&](const auto& hs) { return hs.isQubitAlwaysOne(qubitIndex); }); @@ -350,6 +360,9 @@ bool UnionTable::isQubitAlwaysOne(const Value q) const { bool UnionTable::isQubitAlwaysZero(const Value q) const { const unsigned int qubitIndex = qubitsToGlobalIndices.at(q); const auto ute = valuesToEntries.at(q); + if (ute->top) { + return false; + } return std::ranges::all_of(ute->states, [&](const auto& hs) { return hs.isQubitAlwaysZero(qubitIndex); }); @@ -357,12 +370,18 @@ bool UnionTable::isQubitAlwaysZero(const Value q) const { bool UnionTable::isClassicalValueAlwaysTrue(const Value c) const { const auto ute = valuesToEntries.at(c); + if (ute->top) { + return false; + } return std::ranges::all_of(ute->states, [&](const auto& hs) { return hs.isValueTrue(c); }); } bool UnionTable::isClassicalValueAlwaysFalse(const Value c) const { const auto ute = valuesToEntries.at(c); + if (ute->top) { + return false; + } return std::ranges::all_of( ute->states, [&](const auto& hs) { return !hs.isValueTrue(c); }); } @@ -378,6 +397,9 @@ bool UnionTable::hasAlwaysZeroProbability( participatingEntries.insert(*valuesToEntries.at(cV)); } for (const auto& ute : participatingEntries) { + if (ute.top) { + return false; + } std::unordered_map qubitValuesThisEntry; llvm::DenseMap classicalValuesThisEntry; for (const auto& [qV, qBool] : qubitValues) { From eb6568f2f967b790cc63d7e9a7f8f4cf8e2cb072 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 20:10:20 +0800 Subject: [PATCH 168/235] :bug: Fixed that bt size is used instead of byte-size. --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 8222b26d08..96d843e4cd 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -40,11 +40,12 @@ QuantumState::QuantumState(const std::span globalQubitNumber, const std::size_t maxNonzeroAmplitudes) : nQubits(globalQubitNumber.size()), maxNonzeroAmplitudes(maxNonzeroAmplitudes) { - if (sizeof(unsigned int) < globalQubitNumber.size()) { + constexpr auto maxBitNumber = sizeof(unsigned int) * 8; + if (maxBitNumber < globalQubitNumber.size()) { throw std::domain_error("Number of qubits exceeds amount of qubits that " "can be managed in the union table."); } - if (sizeof(unsigned int) < maxNonzeroAmplitudes) { + if (maxBitNumber < maxNonzeroAmplitudes) { throw std::domain_error( "Number of maximum nonzero amplitudes exceeds " "amount of amplitudes that can be managed in the union table."); From a121ce8a6e8cb44d1a4bccfb68c0e3428fb4b820 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Sun, 16 Aug 2026 20:11:06 +0800 Subject: [PATCH 169/235] :white_check_mark: Adapted tests to contain more cases and have better names --- .../ConstantPropagation/test_hybridState.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 14aea678ec..aac3ef03ce 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -235,7 +235,7 @@ TEST_F(HybridStateTest, doMeasurementWithTwoResults) { "{|0101> -> 1.00}: integerValue0 = 1; p = 0.30")); } -TEST_F(HybridStateTest, doMeasurementWithNegClassicalCtrl) { +TEST_F(HybridStateTest, doNotMeasureAsClassicalCtrlIsNegative) { auto hState = HybridState(fourQubits, 4, 0.6); constexpr auto v1 = mlir::Value(); hState.addIntegerValue(v1, 0); @@ -299,7 +299,7 @@ TEST_F(HybridStateTest, doResetWithTwoResults) { "{|1000> -> 1.00}: integerValue0 = 0; p = 0.30")); } -TEST_F(HybridStateTest, doResetWithNegClassicalCtrl) { +TEST_F(HybridStateTest, doNotResetAsClassicalCtrlIsNegative) { auto hState = HybridState(fourQubits, 4, 0.6); constexpr auto v1 = mlir::Value(); hState.addIntegerValue(v1, 0); @@ -369,8 +369,8 @@ TEST_F(HybridStateTest, unifyTwoHybridStates) { 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); + hState2.addIntegerValue(v2, 7); + hState2.addDoubleValue(v3, 4.2); const HybridState unified = hState1.unify(hState2); const auto resStr = unified.toString(); @@ -390,9 +390,9 @@ TEST_F(HybridStateTest, unifyHybridStatesOneWithoutQuantum) { 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); + auto hState2 = HybridState({}, 10, 0.5); + hState2.addIntegerValue(v2, 7); + hState2.addDoubleValue(v3, 4.2); const HybridState unified = hState1.unify(hState2); const auto resStr = unified.toString(); @@ -409,9 +409,9 @@ 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); + auto hState2 = HybridState({}, 10, 0.5); + hState2.addIntegerValue(v2, 7); + hState2.addDoubleValue(v3, 4.2); const HybridState unified = hState1.unify(hState2); const auto resStr = unified.toString(); From dba39304e694dd582be4e9587cc306955a5fa4da Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 17 Aug 2026 18:16:47 +0800 Subject: [PATCH 170/235] :construction: Adapated code such that no errors are thrown --- .../ClassicalArithOperation.h | 9 ++- .../ConstantPropagation/GateToMap.h | 3 +- .../ConstantPropagation/HybridState.hpp | 34 +++++------ .../ConstantPropagation/QuantumState.hpp | 22 ++++---- .../ConstantPropagation/UnionTable.hpp | 56 +++++++++---------- .../Optimizations/ConstantPropagation.cpp | 21 +++---- .../ConstantPropagation/HybridState.cpp | 33 +++++------ .../ConstantPropagation/QuantumState.cpp | 44 ++++++++++----- .../ConstantPropagation/UnionTable.cpp | 39 +++++-------- .../ConstantPropagation/test_quantumState.cpp | 17 +++--- 10 files changed, 136 insertions(+), 142 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 15d8f351f4..8124cc7d89 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -21,7 +21,6 @@ #include #include -#include /** * This file provides information of available arith operations. It calculates @@ -34,7 +33,7 @@ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, for (mlir::Value operand : operation->getOperands()) { if (isa(operand.getType())) { - throw std::runtime_error( + llvm::report_fatal_error( "Constant propagation does not support vectors as classical types."); } } @@ -69,7 +68,7 @@ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, .Case( [&](auto) { return value1 == 0 ? value3 : value2; }) .Default([&](auto) -> int64_t { - throw std::runtime_error("Unsupported integer operation in " + llvm::report_fatal_error("Unsupported integer operation in " "mlir::qco::classicalarithoperation"); }); } @@ -79,7 +78,7 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, double value1, for (mlir::Value operand : operation->getOperands()) { if (isa(operand.getType())) { - throw std::runtime_error( + llvm::report_fatal_error( "Constant propagation does not support vectors as classical types."); } } @@ -101,7 +100,7 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, double value1, [&](auto) { return remainder(value1, value2); }) .Case([&](auto) { return value1 - value2; }) .Default([&](auto) -> double { - throw std::runtime_error("Unsupported floating-point operation in " + llvm::report_fatal_error("Unsupported floating-point operation in " "mlir::qco::classicalarithoperation"); }); } 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 c3047b1f83..eb3e68ab53 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -21,7 +21,6 @@ #include #include #include -#include #include /** @@ -224,7 +223,7 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { {3, Complex(c, 0)}}}}}; }) .Default([&](auto) -> ResultMap { - throw std::runtime_error("Unsupported gate in mlir::qco::gatetomap"); + llvm::report_fatal_error("Unsupported gate in mlir::qco::gatetomap"); }); } 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 0eec9c8d47..b8c56b5d46 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -40,12 +39,12 @@ class HybridState { /** * @brief Checks if all positive classical controls hold and all negative - * classical controls do not hold. + * classical controls do not hold. Aborts if a classical control value cannot + * be found. * * @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) { @@ -58,7 +57,7 @@ class HybridState { return false; } if (!doubleValues.contains(posCtrl) && !integerValues.contains(posCtrl)) { - throw std::domain_error( + llvm::report_fatal_error( "HybridState needs a classical value for operation control that is " "not existent in current HybridState."); } @@ -72,7 +71,7 @@ class HybridState { return false; } if (!doubleValues.contains(negCtrl) && !integerValues.contains(negCtrl)) { - throw std::domain_error( + llvm::report_fatal_error( "HybridState needs a classical value for operation control that is " "not existent in current HybridState."); } @@ -85,14 +84,13 @@ class HybridState { * * This method applies a measurement or reset, changing the qubits and the * classical values (if a measurement is applied) corresponding to the - * measurement. + * measurement. Aborts if a classical control value cannot be found. * * @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. */ @@ -178,7 +176,8 @@ class HybridState { /** * @brief This method applies a gate to the state. * - * This method changes the hybrid state according to a gate. + * This method changes the hybrid state according to a gate. Aborts if a + * classical control value cannot be found. * * @param gate The name of the gate to be applied. * @param targets An array of the indices of the target qubits. @@ -186,7 +185,6 @@ class HybridState { * @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 = {}, @@ -198,13 +196,13 @@ class HybridState { * @brief This method applies a measurement. * * This method applies a measurement, changing the qubits and the classical - * values corresponding to the measurement. + * values corresponding to the measurement. Aborts if a classical control + * value cannot be found. * * @param quantumTarget The index of the qubit to be measured. * @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 * outcomes. */ @@ -218,12 +216,12 @@ class HybridState { * * 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. + * the measurement was one, and the result discarded. Aborts if a classical + * control value cannot be found. * * @param target The index of the qubit to be measured. * @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. */ @@ -244,8 +242,6 @@ class HybridState { * @param operand3 The third value used by the operation, might be null. * @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, @@ -258,11 +254,9 @@ class HybridState { * * 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. + * Otherwise, the state becomes top. * * @param that The HybridState to unify this with. - * @throw std::domain_error If the unified QuantumState would exceed - * maxNonzeroAmplitudes of this. */ HybridState unify(const HybridState& that); @@ -286,12 +280,12 @@ class HybridState { * 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). + * they are zero (false) or non-zero (true). Aborts if a classical value + * cannot be found. * * @param qubitValues Pairs of the qubits that are being checked and the * values that they are being checked for. * @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 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 632ab0bba6..ccc5ab004e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -54,6 +53,7 @@ class QuantumState { std::size_t maxNonzeroAmplitudes; std::unordered_map globalToLocalQubitNumber; std::unordered_map> amplitudeMap; + bool top; std::string qubitStringToBinary(const unsigned int q) const { std::string result; @@ -186,10 +186,6 @@ class QuantumState { } } - 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()}; @@ -233,16 +229,21 @@ class QuantumState { */ void normalize(); + /** + * @brief This method returns whether the quantum state is top. + * + * @return True if the quantum state is top. + */ + [[nodiscard("QuantumState::isTop called but ignored")]] bool isTop() 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. + * nonzero amplitudes. Otherwise, QuantumState becomes top. * * @param that The QuantumState to unify this with. - * @throw std::domain_error If the number of nonzero amplitudes would exceed - * maxNonzeroAmplitudes of this. */ [[nodiscard("QuantumState::unify called but ignored")]] QuantumState unify(const QuantumState& that); @@ -260,14 +261,13 @@ class QuantumState { * * 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. + * maxNonZeroAmplitude nonzero amplitudes. Otherwise, QuantumState becomes + * top. * * @param gate The gate to be applied. * @param targets A span of the global indices of the target 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 ctrls = {}, 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 cb4860025c..3b48331404 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -149,16 +149,15 @@ class UnionTable { * @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. + * more than maxNonzeroAmplitudes, it handles these states as top. 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. + * @return Whether the new entries are top or not. */ - void unifyEntries(const std::set& entriesToUnify) { + bool unifyEntries(const std::set& entriesToUnify) { if (entriesToUnify.size() == 1) { - return; + return entriesToUnify.begin()->top; } bool entriesBecomeTop = false; for (const auto& e : entriesToUnify) { @@ -168,8 +167,10 @@ class UnionTable { // Check if the number of entries would be too large unsigned int numberOfNewEntries = 1; for (const auto& e : entriesToUnify) { - if (numberOfNewEntries > maximumHybridEntries / e.states.size()) { - throw std::domain_error("Maximum of allowed hybrid entries exceeded."); + if (e.states.size() != 0 && + numberOfNewEntries > maximumHybridEntries / e.states.size()) { + putEntriesToTop(entriesToUnify); + return true; } numberOfNewEntries *= e.states.size(); } @@ -192,7 +193,7 @@ class UnionTable { } std::vector unifiedHS = {}; for (auto hs1 : newEntry.states) { - for (auto hs2 : e.states) { + for (const auto& hs2 : e.states) { unifiedHS.push_back(hs1.unify(hs2)); } } @@ -219,6 +220,7 @@ class UnionTable { } } entries.insert(ptrUTE); + return entriesBecomeTop; } /** @@ -281,11 +283,11 @@ class UnionTable { [[nodiscard("UnionTable::toString called but ignored")]] std::string toString() const; - /** @brief: Replaces values globally by new values + /** @brief: Replaces values globally by new values. Aborts if the size of the + * two parameters is not equal. * * @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); @@ -297,7 +299,8 @@ class UnionTable { * @brief This method applies a gate to the qubits. * * This method changes the amplitudes of a QuantumState according to the - * applied gate. + * applied gate. Aborts if a value is given but not found in the existing + * ones. * * @param gate The gate to be applied. * @param targets An array of the Values of the target qubits. @@ -307,8 +310,6 @@ class UnionTable { * @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, @@ -319,7 +320,8 @@ class UnionTable { std::span params = {}); /** - * @brief This method propagates a classical operation. + * @brief This method propagates a classical operation. Aborts if a value is + * given, but not found in the existing ones. * * * @param op The operation to be applied. @@ -327,8 +329,6 @@ class UnionTable { * @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, @@ -339,7 +339,9 @@ class UnionTable { * @brief This method applies a measurement. * * This method applies a measurement, changing the qubits and the classical - * bit corresponding to the measurement. + * bit corresponding to the measurement. Aborts if a value is given, but not + * found in the existing ones. This does not hold for the classical target, + * which can be newly created. * * @param quantumTarget The value of the qubit to be measured. * @param newQuantumValue The value of the qubit after the measurement. @@ -347,9 +349,6 @@ class UnionTable { * 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, @@ -361,14 +360,13 @@ class UnionTable { * * 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. + * correspond to already assigned bit values. Aborts if a value is given, but + * not found in the existing ones. * * @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 = {}, @@ -427,13 +425,12 @@ class UnionTable { * 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). + * they are zero (false) or non-zero (true). Aborts if a value is given, but + * is not found in the existing ones. * * @param qubitValues Pairs of the qubits that are being checked and the * values that they are being checked for. * @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. */ [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool @@ -466,15 +463,14 @@ 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. Only works with - * 1-qubit gates without parameters. + * 1-qubit gates without parameters. Aborts if a value is given, but is not + * found in the existing ones. * * @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. - * @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")]] diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 2b5e771a53..aaa40e341c 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -230,8 +230,8 @@ static 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."); + llvm::report_fatal_error("Cannot handle constant operation in conditional " + "branches during constant propagation."); } Value const res = op.getResult(); @@ -335,7 +335,7 @@ createOperationFromUnitaryOperation(Operation* op, PatternRewriter& rewriter, CREATE_OP_CASE_PLUS_MINUS_OPS( XXMinusYYOp) .Default([&](auto) -> Operation* { - throw std::runtime_error("Unsu" + llvm::report_fatal_error("Unsu" "ppor" "ted " "oper" @@ -399,7 +399,8 @@ static CtrlOp removeCtrlsOfGate(CtrlOp* op, rewriter.replaceAllUsesWith(op->getOutputForInput(qubitCtrl), qubitCtrl); } if (ctrlsToRemove.size() == op->getNumControls()) { - throw std::runtime_error("Cannot remove all controls of a CtrlOp"); + llvm::report_fatal_error("Method not suitable to remove all controls of a " + "CtrlOp. Should not be called for this."); } std::vector newControlIn; for (const auto& ctrls : op->getInputControls()) { @@ -586,7 +587,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } } if (implicitSwap) { - throw std::runtime_error("Constant propagation does not allow implicit " + llvm::report_fatal_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 @@ -721,7 +722,7 @@ static WalkResult handleUnitary(UnionTable* ut, UnitaryOpInterface* op, newCtrlsQuantum, posClassicalCtrls, negClassicalCtrls, paramValues); } else { - throw std::invalid_argument( + llvm::report_fatal_error( "Given targetValues and resultValues need to be of same size."); } @@ -1006,7 +1007,7 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, // func Dialect .Case([&](const func::FuncOp op) { if (!isEntryPoint(op)) { - throw std::domain_error( + llvm::report_fatal_error( "Constant propagation does not support nested functions."); return WalkResult::interrupt(); } @@ -1027,7 +1028,7 @@ iterateThroughWorklist(PatternRewriter& rewriter, UnionTable* ut, return WalkResult::advance(); } - throw std::runtime_error("Unsupported operation"); + llvm::report_fatal_error("Unsupported operation"); return WalkResult::interrupt(); }); @@ -1082,8 +1083,8 @@ static LogicalResult applyCP(ModuleOp module, MLIRContext* ctx, continue; // Ignore non entry_point functions for now. } if (entryPointFound) { - throw std::domain_error("Constant propagation does not support programs " - "with more than one entry point."); + llvm::report_fatal_error("Constant propagation does not support programs " + "with more than one entry point."); } entryPointFound = true; func->walk( diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 4e82c7ac38..0871983962 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 #include @@ -30,7 +31,6 @@ #include #include #include -#include #include #include #include @@ -151,20 +151,18 @@ void HybridState::propagateGate(Operation* gate, } else if (doubleValues.contains(p)) { paramValues.push_back(static_cast(doubleValues.at(p))); } else { - throw std::domain_error( + llvm::report_fatal_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&) { + qState->propagateGate(gate, targets, ctrlsQuantum, paramValues); + if (qState->isTop()) { top = true; } } else { - try { - qState->propagateGate(gate, targets, ctrlsQuantum); - } catch (std::domain_error const&) { + qState->propagateGate(gate, targets, ctrlsQuantum); + if (qState->isTop()) { top = true; } } @@ -201,7 +199,7 @@ void HybridState::propagateClassicalOperation( (operand2 != nullptr && !integerValues.contains(operand2)) || (operand3 != nullptr && !integerValues.contains(operand3)) || !integerValues.contains(dest)) { - throw std::domain_error( + llvm::report_fatal_error( "HybridState needs a classical value for a classical operation that " "is not existent in current HybridState."); } @@ -214,7 +212,7 @@ void HybridState::propagateClassicalOperation( if (!doubleValues.contains(operand1) || (operand2 != nullptr && !doubleValues.contains(operand2)) || !doubleValues.contains(dest)) { - throw std::domain_error( + llvm::report_fatal_error( "HybridState needs a classical value for a classical operation that " "is not existent in current HybridState."); } @@ -227,10 +225,9 @@ void HybridState::propagateClassicalOperation( HybridState HybridState::unify(const HybridState& that) { auto newHybridState = HybridState(); - try { - newHybridState.qState = - std::make_shared(qState->unify(*that.qState)); - } catch (std::domain_error const&) { + newHybridState.qState = + std::make_shared(qState->unify(*that.qState)); + if (newHybridState.qState->isTop()) { newHybridState.top = true; return newHybridState; } @@ -276,8 +273,8 @@ bool HybridState::isValueTrue(const Value v) const { if (doubleValues.contains(v)) { return std::fabs(doubleValues.at(v)) > 1e-4; } - throw std::domain_error("Value of a classical value is asked which does not " - "exist in the HybridState."); + llvm::report_fatal_error("Value of a classical value is asked which does not " + "exist in the HybridState."); } bool HybridState::hasAlwaysZeroProbability( @@ -294,8 +291,8 @@ bool HybridState::hasAlwaysZeroProbability( return true; } } else { - throw std::domain_error("Value of a classical value is asked which does " - "not exist in the HybridState."); + llvm::report_fatal_error("Value of a classical value is asked which does " + "not exist in the HybridState."); } } diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 96d843e4cd..998732565d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -41,15 +40,14 @@ QuantumState::QuantumState(const std::span globalQubitNumber, : nQubits(globalQubitNumber.size()), maxNonzeroAmplitudes(maxNonzeroAmplitudes) { constexpr auto maxBitNumber = sizeof(unsigned int) * 8; - if (maxBitNumber < globalQubitNumber.size()) { - throw std::domain_error("Number of qubits exceeds amount of qubits that " - "can be managed in the union table."); - } - if (maxBitNumber < maxNonzeroAmplitudes) { - throw std::domain_error( - "Number of maximum nonzero amplitudes exceeds " - "amount of amplitudes that can be managed in the union table."); + if (maxBitNumber < globalQubitNumber.size() || + maxBitNumber < maxNonzeroAmplitudes) { + // Number of qubits or number of maximum nonzero amplitudes exceeds amount + // of qubits/amplitudes that can be managed in the union table. + top = true; + return; } + top = false; std::vector qubits; qubits.reserve(globalQubitNumber.size()); std::ranges::copy(globalQubitNumber, std::back_inserter(qubits)); @@ -122,11 +120,18 @@ void QuantumState::normalize() { } } +bool QuantumState::isTop() const { return top; } + 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."); + // Number of nonzero amplitudes too high. State needs to be treated as TOP. + top = true; + nQubits = 0; + maxNonzeroAmplitudes = 0; + globalToLocalQubitNumber.clear(); + amplitudeMap.clear(); + return *this; } std::unordered_map newGlobalToLocalMapping; @@ -222,8 +227,12 @@ void QuantumState::propagateGate(Operation* gate, } } if (amplitudeMap.size() > maxNonzeroAmplitudes) { - throw std::domain_error("Number of nonzero amplitudes too high. State " - "needs to be treated as TOP."); + // Number of nonzero amplitudes too high. State needs to be treated as TOP. + top = true; + nQubits = 0; + maxNonzeroAmplitudes = 0; + globalToLocalQubitNumber.clear(); + amplitudeMap.clear(); } } @@ -236,6 +245,9 @@ MeasurementResult QuantumState::resetQubit(const unsigned int target) { } bool QuantumState::isQubitAlwaysOne(const unsigned int q) const { + if (top) { + return false; + } const auto localIndex = globalToLocalQubitNumber.at(q); const auto mask = 1U << localIndex; return std::ranges::all_of( @@ -244,6 +256,9 @@ bool QuantumState::isQubitAlwaysOne(const unsigned int q) const { } bool QuantumState::isQubitAlwaysZero(const unsigned int q) const { + if (top) { + return false; + } const auto localIndex = globalToLocalQubitNumber.at(q); const auto mask = 1U << localIndex; return std::ranges::all_of( @@ -252,6 +267,9 @@ bool QuantumState::isQubitAlwaysZero(const unsigned int q) const { } bool QuantumState::hasAlwaysZeroAmplitude( const std::unordered_map& qubitValues) const { + if (top) { + return false; + } unsigned int localValue = 0; unsigned int mask = 0; for (const auto& [qubitIndex, qubitOne] : qubitValues) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index f321deabb9..7d1c1bc742 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -79,7 +79,7 @@ std::string UnionTable::toString() const { void UnionTable::replaceValuesGlobally(const std::span replacedValues, const std::span newValues) { if (replacedValues.size() != newValues.size()) { - throw std::domain_error( + llvm::report_fatal_error( "replacedValues and newValues do not have the same size."); } @@ -133,10 +133,8 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, return; } - try { - unifyEntries(participatingEntries); - } catch (std::domain_error&) { - putEntriesToTop(participatingEntries); + const auto becameTop = unifyEntries(participatingEntries); + if (becameTop) { replaceValuesGlobally(targets, newQuantumTargets); return; } @@ -182,9 +180,8 @@ void UnionTable::propagateClassicalOperation( collectParticipatingEntries(targets, results, posCtrlsClassical, negCtrlsClassical); - try { - unifyEntries(participatingEntries); - } catch (std::domain_error&) { + const auto becameTop = unifyEntries(participatingEntries); + if (becameTop) { putEntriesToTop(participatingEntries); return; } @@ -219,10 +216,8 @@ void UnionTable::propagateMeasurement( std::vector quantumTargetVec = {quantumTarget}; std::vector newQuantumValueVec = {newQuantumValue}; - try { - unifyEntries(participatingEntries); - } catch (std::domain_error&) { - putEntriesToTop(participatingEntries); + const auto becameTop = unifyEntries(participatingEntries); + if (becameTop) { replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); return; } @@ -266,9 +261,8 @@ void UnionTable::propagateReset(const Value quantumTarget, negCtrlsClassical); std::vector newQuantumValueVec = {newQuantumValue}; - try { - unifyEntries(participatingEntries); - } catch (std::domain_error&) { + const auto becameTop = unifyEntries(participatingEntries); + if (becameTop) { putEntriesToTop(participatingEntries); replaceValuesGlobally(quantumTargetVec, newQuantumValueVec); return; @@ -283,17 +277,10 @@ void UnionTable::propagateReset(const Value quantumTarget, std::vector vecOfNewStates; for (auto hs : ute->states) { - try { - 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; - } + auto newStates = hs.propagateReset(qubitsToGlobalIndices.at(quantumTarget), + posCtrlsClassical, negCtrlsClassical); + vecOfNewStates.insert(vecOfNewStates.end(), newStates.begin(), + newStates.end()); } ute->states = vecOfNewStates; if (vecOfNewStates.size() > maximumHybridEntries) { 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 ce09d1176d..0aed8af9d7 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -4,7 +4,7 @@ * All rights reserved. * * SPDX-License-Identifier: MIT - * + *participatingEntries * Licensed under the MIT License */ @@ -270,8 +270,9 @@ TEST_F(QuantumStateTest, propagateGateCheckErrorIfTwoManyAmplitudesAreNonzero) { qState.propagateGate(hOp.getOperation(), vectorThree); qState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); - EXPECT_THROW(qState.propagateGate(hOp.getOperation(), vectorTwo); - , std::domain_error); + EXPECT_FALSE(qState.isTop()); + qState.propagateGate(hOp.getOperation(), vectorTwo); + EXPECT_TRUE(qState.isTop()); } TEST_F(QuantumStateTest, doMeasurementWithZeroResult) { @@ -383,8 +384,10 @@ TEST_F(QuantumStateTest, unifyTooLargeQuantumStates) { 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); + EXPECT_FALSE(qState1.isTop()); + EXPECT_FALSE(qState2.isTop()); + const auto qs = qState1.unify(qState2); + EXPECT_TRUE(qs.isTop()); } TEST_F(QuantumStateTest, applyVariousGates) { @@ -411,8 +414,8 @@ TEST_F(QuantumStateTest, applyVariousGates) { EXPECT_THAT( qState.toString(), - testing::HasSubstr("|00> -> -0.27 - i0.25, |01> -> 0.12 - i0.39, |10> " - "-> -0.62 + i0.43, |11> -> -0.15 - i0.32")); + testing::HasSubstr("|00> -> 0.58 - i0.07, |01> -> -0.14 + i0.04, |10> " + "-> 0.23 + i0.15, |11> -> -0.10 - i0.74")); } } // namespace From 711fc2d850358134dd03b07c674b46ee4aaf42f1 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 17 Aug 2026 18:54:20 +0800 Subject: [PATCH 171/235] :construction: Optimized imports --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 2 +- .../Transforms/Optimizations/ConstantPropagation/UnionTable.cpp | 2 +- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index aaa40e341c..47f68cb20f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,6 @@ #include #include #include -#include #include namespace { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 7d1c1bc742..c9bdd3117b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -16,6 +16,7 @@ #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp" #include +#include #include #include #include @@ -30,7 +31,6 @@ #include #include #include -#include #include #include #include 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 0aed8af9d7..063681911b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include From 74c646eb3ea3ff0af407e66eca990d4b57351f60 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 10:20:57 +0000 Subject: [PATCH 172/235] =?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 --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 063681911b..3889a16607 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -4,7 +4,7 @@ * All rights reserved. * * SPDX-License-Identifier: MIT - *participatingEntries + * * Licensed under the MIT License */ From 36fff56cedf3ab4bfcf6a9c5bfa3735c4443645c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 17 Aug 2026 13:16:51 +0200 Subject: [PATCH 173/235] :construction: Adapted handling of arith operation, assisted-by GPT 5.4 via KIconnect --- .../ClassicalArithOperation.h | 196 ++++++++++++------ 1 file changed, 137 insertions(+), 59 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 8124cc7d89..ec0f66fdd1 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -10,17 +10,21 @@ #pragma once -#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 +#include #include +#include #include #include +#include #include +#include /** * This file provides information of available arith operations. It calculates @@ -28,81 +32,155 @@ * two operands, not if they are applied to sequences. */ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, - int64_t value1, int64_t value2 = 0, - int64_t value3 = 0) { + const int64_t value1, + const int64_t value2 = 0, + const int64_t value3 = 0) { - for (mlir::Value operand : operation->getOperands()) { - if (isa(operand.getType())) { + for (auto operand : operation->getOperands()) { + if (mlir::isa(operand.getType())) { llvm::report_fatal_error( "Constant propagation does not support vectors as classical types."); } } + const auto intTy = + dyn_cast(operation->getResult(0).getType()); + if (!intTy) { + llvm::report_fatal_error( + "IntegerType is needed to apply arith::integer operation."); + } + + const unsigned width = intTy.getWidth(); + if (width > 64) { + llvm::report_fatal_error( + "Result of an arith operation cannot be safely stored in a 64-bit " + "integer, which is required by constant propagation."); + } + + // APInt respects the signedness of the underlying MLIR type. + const bool isSigned = intTy.isSigned(); + const llvm::APInt a(width, static_cast(value1), isSigned); + const llvm::APInt b(width, static_cast(value2), isSigned); + const llvm::APInt c(width, static_cast(value3), isSigned); + 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) { return (a + b).getSExtValue(); }) + .Case([&](auto) { return (a & b).getSExtValue(); }) + .Case( + [&](auto) { return a.sdiv(b).getSExtValue(); }) + .Case([&](auto) { + return a.getSExtValue() > b.getSExtValue() ? a.getSExtValue() + : b.getSExtValue(); }) - .Case([&](auto) { - // Division that rounds towards zero - return value1 / value2; + .Case([&](auto) { + return a.getSExtValue() < b.getSExtValue() ? a.getSExtValue() + : b.getSExtValue(); }) - .Case([&](auto) { - // Division that rounds to negative infinity - return floor(1.0 * value1 / value2); + .Case([&](auto) { return (a * b).getSExtValue(); }) + .Case([&](auto) { return (a | b).getSExtValue(); }) + .Case([&](auto) { return (a - b).getSExtValue(); }) + .Case([&](auto) { return (a ^ b).getSExtValue(); }) + .Case([&](auto) { + // SelectOp: first operand is the i1 condition. + // In our helper `value1` is the condition (0 == false). + return (a != llvm::APInt(width, 0, true)) ? b.getSExtValue() + : c.getSExtValue(); }) - .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 value1 == 0 ? value3 : value2; }) - .Default([&](auto) -> int64_t { + .Default([](auto*) { llvm::report_fatal_error("Unsupported integer operation in " "mlir::qco::classicalarithoperation"); + return 0; }); } -inline double getArithDoubleOpResult(mlir::Operation* operation, double value1, - double value2 = 0.0) { - +inline double getArithDoubleOpResult(mlir::Operation* operation, + const double value1, + const double value2 = 0.0) { for (mlir::Value operand : operation->getOperands()) { - if (isa(operand.getType())) { + if (mlir::isa(operand.getType())) { llvm::report_fatal_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 { - llvm::report_fatal_error("Unsupported floating-point operation in " - "mlir::qco::classicalarithoperation"); - }); -} + const auto floatTy = + dyn_cast(operation->getResult(0).getType()); + if (!floatTy) { + llvm::report_fatal_error("Expected floating-point result type."); + } + + const llvm::fltSemantics& sem = floatTy.getFloatSemantics(); + constexpr auto rm = llvm::APFloat::rmNearestTiesToEven; + bool losesInfo = false; -#endif // MQT_CORE_MQT_CORE_CLASSICALARITHOPERATION_H + llvm::APFloat lhs(value1); + llvm::APFloat rhs(value2); + lhs.convert(sem, rm, &losesInfo); + if (losesInfo) { + llvm::report_fatal_error("value1 cannot be represented safely."); + } + + losesInfo = false; + rhs.convert(sem, rm, &losesInfo); + if (losesInfo) { + llvm::report_fatal_error("value2 cannot be represented safely."); + } + + llvm::APFloat result = lhs; + + const bool supported = + mlir::TypeSwitch(operation) + .Case( + [&](auto) { return result.add(rhs, rm) == llvm::APFloat::opOK; }) + .Case([&](auto) { + return result.divide(rhs, rm) == llvm::APFloat::opOK; + }) + .Case([&](auto) { + result = llvm::maximum(lhs, rhs); + return true; + }) + .Case([&](auto) { + result = llvm::maxnum(lhs, rhs); + return true; + }) + .Case([&](auto) { + result = llvm::minimum(lhs, rhs); + return true; + }) + .Case([&](auto) { + result = llvm::minnum(lhs, rhs); + return true; + }) + .Case([&](auto) { + result = lhs; + return result.multiply(rhs, rm) == llvm::APFloat::opOK; + }) + .Case([&](auto) { + result = lhs; + result.changeSign(); + return true; + }) + .Case([&](auto) { + result = lhs; + return result.remainder(rhs) == llvm::APFloat::opOK; + }) + .Case([&](auto) { + result = lhs; + return result.subtract(rhs, rm) == llvm::APFloat::opOK; + }) + .Default([](auto) { return false; }); + + if (!supported) { + llvm::report_fatal_error("Unsupported floating-point operation in " + "mlir::qco::classicalarithoperation"); + } + + const double folded = result.convertToDouble(); + + if (!llvm::APFloat(folded).bitwiseIsEqual(result)) { + llvm::report_fatal_error( + "Floating-point fold result cannot be represented safely as double."); + } + + return folded; +} From 48bc069fe65a2016b170212d443c10624335ae83 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Mon, 17 Aug 2026 13:47:35 +0200 Subject: [PATCH 174/235] :construction: Corrected handling of arith operation, assisted-by GPT 5.4 via KIconnect --- .../ClassicalArithOperation.h | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 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 ec0f66fdd1..11d6ae82ef 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -130,10 +130,15 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, const bool supported = mlir::TypeSwitch(operation) - .Case( - [&](auto) { return result.add(rhs, rm) == llvm::APFloat::opOK; }) + .Case([&](auto) { + result = lhs; + auto status = result.add(rhs, rm); + return (status & llvm::APFloat::opInvalidOp) == 0; + }) .Case([&](auto) { - return result.divide(rhs, rm) == llvm::APFloat::opOK; + result = lhs; + auto status = result.divide(rhs, rm); + return (status & llvm::APFloat::opInvalidOp) == 0; }) .Case([&](auto) { result = llvm::maximum(lhs, rhs); @@ -153,7 +158,8 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, }) .Case([&](auto) { result = lhs; - return result.multiply(rhs, rm) == llvm::APFloat::opOK; + auto status = result.multiply(rhs, rm); + return (status & llvm::APFloat::opInvalidOp) == 0; }) .Case([&](auto) { result = lhs; @@ -162,7 +168,8 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, }) .Case([&](auto) { result = lhs; - return result.remainder(rhs) == llvm::APFloat::opOK; + auto status = result.remainder(rhs); + return (status & llvm::APFloat::opInvalidOp) == 0; }) .Case([&](auto) { result = lhs; From 21c1dc2bcd08bba76c035f6ed9b879715ddad45f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 18 Aug 2026 11:21:45 +0200 Subject: [PATCH 175/235] :construction: Added handling of multiple conditions in a new branch --- .../ClassicalArithOperation.h | 38 ++++++++++++ .../Optimizations/ConstantPropagation.cpp | 60 ++++++++++++++++++- .../test_qco_constant_propagation.cpp | 44 ++++++++++++++ 3 files changed, 139 insertions(+), 3 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 11d6ae82ef..980b94b267 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -66,6 +66,44 @@ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, return mlir::TypeSwitch(operation) .Case([&](auto) { return (a + b).getSExtValue(); }) .Case([&](auto) { return (a & b).getSExtValue(); }) + .Case([&](mlir::arith::CmpIOp cmp) { + using Pred = mlir::arith::CmpIPredicate; + bool result = false; + switch (cmp.getPredicate()) { + case Pred::eq: + result = (a == b); + break; + case Pred::ne: + result = (a != b); + break; + case Pred::slt: + result = a.slt(b); + break; + case Pred::sle: + result = a.sle(b); + break; + case Pred::sgt: + result = a.sgt(b); + break; + case Pred::sge: + result = a.sge(b); + break; + case Pred::ult: + result = a.ult(b); + break; + case Pred::ule: + result = a.ule(b); + break; + case Pred::ugt: + result = a.ugt(b); + break; + case Pred::uge: + result = a.uge(b); + break; + } + + return result ? int64_t{1} : int64_t{0}; + }) .Case( [&](auto) { return a.sdiv(b).getSExtValue(); }) .Case([&](auto) { diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 47f68cb20f..23abbba11b 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -634,10 +634,64 @@ putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, const std::span negClassicalCtrls, ControlsToModify ctrlsToMod, PatternRewriter& rewriter, std::span& worklist) { + Value condition = ctrlsToMod.classicalNegCtrlsToAdd.empty() + ? *ctrlsToMod.classicalPosCtrlsToAdd.begin() + : *ctrlsToMod.classicalNegCtrlsToAdd.begin(); + const auto numberOfNewConditions = ctrlsToMod.classicalPosCtrlsToAdd.size() + + ctrlsToMod.classicalNegCtrlsToAdd.size(); + if (numberOfNewConditions > 1) { + // Create classical operations to consider all new classical controls + std::vector conditions; + conditions.reserve(ctrlsToMod.classicalPosCtrlsToAdd.size()); + std::ranges::copy(ctrlsToMod.classicalPosCtrlsToAdd, + std::back_inserter(conditions)); + bool firstNegVal = true; + arith::ConstantOp zero; + for (const auto negVal : ctrlsToMod.classicalNegCtrlsToAdd) { + if (firstNegVal) { + firstNegVal = false; + auto i1Ty = rewriter.getI1Type(); + zero = arith::ConstantOp::create(rewriter, op->getLoc(), + rewriter.getIntegerAttr(i1Ty, 0)); + ut->propagateIntAlloc(zero, 0); + } + const auto negatedValOp = arith::CmpIOp::create( + rewriter, op->getLoc(), arith::CmpIPredicate::eq, negVal, zero); + const auto negatedVal = negatedValOp->getResult(0); + std::vector negationOperands = { + negatedValOp->getOperands().begin(), + negatedValOp->getOperands().end()}; + std::vector negationResults = {negatedValOp->getResults().begin(), + negatedValOp->getResults().end()}; + ut->propagateClassicalOperation(negatedValOp, negationOperands, + negationResults, posClassicalCtrls, + negClassicalCtrls); + conditions.push_back(negatedVal); + } + auto andOp = arith::AndIOp::create(rewriter, op->getLoc(), conditions[0], + conditions[1]); + condition = andOp->getResult(0); + std::vector operands = {andOp->getOperands().begin(), + andOp->getOperands().end()}; + std::vector results = {andOp->getResults().begin(), + andOp->getResults().end()}; + ut->propagateClassicalOperation(andOp, operands, results, posClassicalCtrls, + negClassicalCtrls); + + for (unsigned int classicalConditionIndex = 2; + classicalConditionIndex < conditions.size(); + ++classicalConditionIndex) { + andOp = arith::AndIOp::create(rewriter, op->getLoc(), condition, + conditions[classicalConditionIndex]); + condition = andOp->getResult(0); + operands = {andOp->getOperands().begin(), andOp->getOperands().end()}; + results = {andOp->getResults().begin(), andOp->getResults().end()}; + ut->propagateClassicalOperation(andOp, operands, results, + posClassicalCtrls, negClassicalCtrls); + } + } + 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 = 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 040f12132e..46a47c6691 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 @@ -595,6 +595,50 @@ TEST_F(QCOConstantPropagationTest, testRemoveSomeQuantumControl) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks that multiple quantum controls are replaced by + * multiple classical controls. + */ +TEST_F(QCOConstantPropagationTest, + testReplaceQuantumCtrlsByMultipleClassicalCtrls) { + auto q = programBuilder.allocQubitRegister(3); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.h(q[1]); + auto [q0, b0] = programBuilder.measure(q[0]); + auto [q1, b1] = programBuilder.measure(q[1]); + q[1] = programBuilder.x(q1); + programBuilder.ctrl({q0, q[1]}, {q[2]}, [&](const ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(3); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + auto [qRef0, bRef0] = referenceBuilder.measure(qRef[0]); + auto [qRef1, bRef1] = referenceBuilder.measure(qRef[1]); + qRef[1] = referenceBuilder.x(qRef1); + const auto zero = arith::ConstantOp::create( + referenceBuilder, referenceBuilder.getBoolAttr(false)) + .getResult(); + const auto negated1 = + arith::CmpIOp::create(referenceBuilder, bRef1.getType(), + arith::CmpIPredicate::eq, bRef1, zero) + .getResult(); + const auto andOp = + arith::AndIOp::create(referenceBuilder, bRef0.getType(), bRef0, negated1) + .getResult(); + referenceBuilder.qcoIf(andOp, {qRef[2]}, [&](const ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.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. From 90c2d5db0d96bae65f99daaefb6507b07dd17091 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 18 Aug 2026 11:52:40 +0200 Subject: [PATCH 176/235] :construction: Removed check if arguments are empty --- .../Optimizations/ConstantPropagation.cpp | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 23abbba11b..430ed4acff 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -564,28 +564,26 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, // 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); - } + 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(elseArgs.begin(), it); - if (!thenArgs.empty()) { - implicitSwap |= order.at(i) != pos; - } else { - order.push_back(pos); - } + + 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(elseArgs.begin(), it); + if (!thenArgs.empty()) { + implicitSwap |= order.at(i) != pos; + } else { + order.push_back(pos); } } } + if (implicitSwap) { llvm::report_fatal_error("Constant propagation does not allow implicit " "swapping of qubits in branching."); From 4c3d5b306a69d84856dbbd61c2b44888d6faed0c Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Tue, 18 Aug 2026 14:43:58 +0200 Subject: [PATCH 177/235] :construction: Do not insert in empty vector anymore --- .../Transforms/Optimizations/ConstantPropagation.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 430ed4acff..76e7166301 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -921,26 +921,24 @@ static WalkResult handleCtrlOp(UnionTable* ut, CtrlOp* op, // propagation of the body unitary std::vector targetQubits; const auto numTargets = op->getNumTargets(); - targetQubits.reserve(numTargets); + targetQubits.resize(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[argIndex] == body.getInputTarget(i)) { - targetQubits.insert(targetQubits.begin() + i, - op->getInputTarget(argIndex)); + targetQubits[i] = op->getInputTarget(argIndex); break; } } } std::vector resultQubits; - resultQubits.reserve(numTargets); + resultQubits.resize(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)); + resultQubits[i] = op->getOutputTarget(uOpOutIndex); break; } } From b73d4414d08bf238437eeb6052cf21ee6de41860 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 08:11:15 +0200 Subject: [PATCH 178/235] :construction: Prevented thenArgs and elseArgs from staying empty --- .../Optimizations/ConstantPropagation.cpp | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 76e7166301..d0018be6ef 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -516,19 +516,19 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (resThen.failed()) { return WalkResult::interrupt(); } - 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]); - } - }); } + 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()) { elseArgs.push_back(arg); } - ut->replaceValuesGlobally(thenArgs.empty() ? targets : thenArgs, elseArgs); + ut->replaceValuesGlobally(thenEmpty ? targets : thenArgs, elseArgs); std::span wl(newElseWorklist.data(), newElseWorklist.size()); std::vector newNegClassicalCtrls = {negClassicalCtrls.begin(), @@ -540,16 +540,17 @@ static 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]); - } - }); } + 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()}; From 66fe88525df1d98118b0c94f7e0bf2895acda7e6 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 09:16:16 +0200 Subject: [PATCH 179/235] :construction: Prevented thenArgs and elseArgs from staying empty --- .../Optimizations/ConstantPropagation.cpp | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index d0018be6ef..3402580301 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -516,14 +516,18 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (resThen.failed()) { return WalkResult::interrupt(); } + 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]); + } + }); + } else { + const auto thenYieldOperands = op->thenYield()->getOperands(); + thenArgs = {thenYieldOperands.begin(), thenYieldOperands.end()}; } - 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()) { elseArgs.push_back(arg); @@ -540,16 +544,19 @@ static 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]); + } + }); + } else { + const auto elseYieldOperands = op->elseYield()->getOperands(); + elseArgs = {elseYieldOperands.begin(), elseYieldOperands.end()}; } - 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()}; @@ -604,7 +611,7 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, inputQubitVec); rewriter.eraseOp(*op); } else { - ut->replaceValuesGlobally(elseArgs.empty() ? thenArgs : elseArgs, results); + ut->replaceValuesGlobally(elseEmpty ? thenArgs : elseArgs, results); } return WalkResult::advance(); From b5dcdf6375f9e6af473bf0b58761a7f767315828 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 12:49:55 +0200 Subject: [PATCH 180/235] :memo: Fixed issues in CHANGELOG.md --- CHANGELOG.md | 142 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a004cdecad..f8b903ca41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,16 +36,16 @@ releases may include breaking changes. Slurm license environment value ([#2025]) ([**@burgholzer**]) - ✨ Add an `unroll-modifiers` pass for unrolling multi-operation modifiers ([#2015]) ([**@denialhaag**], [**@burgholzer**]) -- ✨ Add Qiskit circuit import and export to the compiler collection ([#2031]) - ([**@burgholzer**]) +- ✨ Add Qiskit circuit import and export to the compiler collection ([#2031], + [#2133], [#2136], [#2140]) ([**@burgholzer**], [**@simon1hofmann**]) - ✨ Add generic C++ and Python FoMaC support for custom device properties that contain operation handles ([#2042]) ([**@burgholzer**]) - ✨ Support retrieving existing jobs by ID through the QDMI client API and C++ and Python FoMaC APIs, and expose optional device queue length and job queue position ([#2008], [#2010]) ([**@burgholzer**]) - 🐍 Start building CPython 3.15 wheels ([#2011]) ([**@denialhaag**]) -- ✨ Add PennyLane support for gate-based QDMI devices ([#2005]) - ([**@burgholzer**]) +- ✨ Add PennyLane support for gate-based QDMI devices ([#2005], [#2147]) + ([**@burgholzer**], [**@marcelwa**]) - ✨ Integrate QDMI devices as MLIR compiler targets across C++, Python, and `mqt-cc` ([#1687]) ([**@MatthiasReumann**], [**@burgholzer**]) - ✨ Add structured OpenQASM emission from the QC dialect to the C++ and Python @@ -130,9 +130,9 @@ releases may include breaking changes. [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], [#1781], [#1782], [#1806], [#1807], [#1815], [#1808], [#1824], [#1869], [#1872], [#1886], [#1914], [#1925], [#1927], [#1935], [#1936], [#1938], [#1975], - [#1976], [#2006], [#2014], [#2015], [#2017], [#2026], [#2028], [#2058]) - ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], - [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], + [#1976], [#2006], [#2014], [#2015], [#2017], [#2026], [#2028], [#2058], + [#2125]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], + [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**]) ### Changed @@ -167,12 +167,27 @@ releases may include breaking changes. ### Removed +- 💥 Remove `nlohmann_json` from the public package contract. MQT Core no longer + installs or exports the library, no installed header exposes a `nlohmann` + type, and the decision-diagram statistics report through strings and streams + ([#2138]) ([**@denialhaag**]) +- 💥 Remove the neutral-atom stack, which moves to MQT QMAP. This drops the + neutral-atom computation model, the neutral-atom FoMaC device session, the + neutral-atom QDMI device and its configuration, the `mqt.core.na` Python + module, `AodOperation`, and the `Move`, `Bridge`, `AodActivate`, + `AodDeactivate`, and `AodMove` operation kinds ([#2137]) ([**@denialhaag**]) +- 💥 Remove the random-number generator, seed, and `getGenerator()` method from + `QuantumComputation`; randomized algorithms now own generators initialized + from their seed arguments ([#2111]) ([**@simon1hofmann**]) - 💥 Remove QDMI device configuration through `[tool.qdmi]` in `pyproject.toml` and the vendored toml++ header ([#2116]) ([**@denialhaag**]) - 💥 Remove the FoMaC compatibility name from the C++ and Python QDMI APIs. Use the `qdmi` C++ namespace, headers, libraries, and CMake targets; the `mqt.core.qdmi` and `mqt.core.na.qdmi` Python modules; and the module-level Python driver functions ([#2115]) ([**@burgholzer**]) +- 💥 Remove the legacy `QuantumComputation`-to-MLIR translator and its C++ and + Python compiler inputs. Use OpenQASM, Qiskit circuits, or typed MLIR programs + as compiler inputs ([#2054]) ([**@burgholzer**]) - 💥 Remove the ZX-calculus library, including the `mqt-core-zx` target, `MQT::CoreZX` alias, `zx` headers and namespace, and its Boost.Multiprecision and GMP build support. Equivalence-checking users should use [MQT QCEC]; its @@ -603,7 +618,7 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#320)._ ### Added -- 🐍 Build Python 3.14 wheels ([#1076]) ([**@denialhaag**]) +- 🐍 Start building CPython 3.14 wheels ([#1076]) ([**@denialhaag**]) - ✨ Add MQT-internal MLIR dialect conversions ([#1001]) ([**@li-mingbao**]) ### Changed @@ -756,13 +771,14 @@ _If you are upgrading: please see [`UPGRADING.md`](UPGRADING.md#300)._ ## [2.7.0] - 2024-10-08 -_📚 Refer to the [GitHub Release -Notes](https://github.com/munich-quantum-toolkit/core/releases) for previous -changelogs._ +_📚 Refer to the +[GitHub Release Notes](https://github.com/munich-quantum-toolkit/core/releases) +for previous changelogs._ -[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.7.0...HEAD +[unreleased]: https://github.com/munich-quantum-toolkit/core/compare/v3.8.0...HEAD +[3.8.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.8.0 [3.7.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.7.0 [3.6.1]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.1 [3.6.0]: https://github.com/munich-quantum-toolkit/core/releases/tag/v3.6.0 @@ -784,18 +800,112 @@ changelogs._ +[#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 +[#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 +[#2140]: https://github.com/munich-quantum-toolkit/core/pull/2140 +[#2138]: https://github.com/munich-quantum-toolkit/core/pull/2138 +[#2137]: https://github.com/munich-quantum-toolkit/core/pull/2137 +[#2136]: https://github.com/munich-quantum-toolkit/core/pull/2136 +[#2133]: https://github.com/munich-quantum-toolkit/core/pull/2133 +[#2125]: https://github.com/munich-quantum-toolkit/core/pull/2125 +[#2124]: https://github.com/munich-quantum-toolkit/core/pull/2124 +[#2118]: https://github.com/munich-quantum-toolkit/core/pull/2118 +[#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 +[#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 +[#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 +[#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 +[#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 +[#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 +[#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 +[#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 +[#2084]: https://github.com/munich-quantum-toolkit/core/pull/2084 +[#2074]: https://github.com/munich-quantum-toolkit/core/pull/2074 +[#2073]: https://github.com/munich-quantum-toolkit/core/pull/2073 +[#2066]: https://github.com/munich-quantum-toolkit/core/pull/2066 +[#2060]: https://github.com/munich-quantum-toolkit/core/pull/2060 +[#2058]: https://github.com/munich-quantum-toolkit/core/pull/2058 +[#2054]: https://github.com/munich-quantum-toolkit/core/pull/2054 +[#2049]: https://github.com/munich-quantum-toolkit/core/pull/2049 +[#2043]: https://github.com/munich-quantum-toolkit/core/pull/2043 +[#2042]: https://github.com/munich-quantum-toolkit/core/pull/2042 +[#2039]: https://github.com/munich-quantum-toolkit/core/pull/2039 +[#2038]: https://github.com/munich-quantum-toolkit/core/pull/2038 +[#2036]: https://github.com/munich-quantum-toolkit/core/pull/2036 +[#2035]: https://github.com/munich-quantum-toolkit/core/pull/2035 +[#2031]: https://github.com/munich-quantum-toolkit/core/pull/2031 +[#2030]: https://github.com/munich-quantum-toolkit/core/pull/2030 +[#2028]: https://github.com/munich-quantum-toolkit/core/pull/2028 +[#2026]: https://github.com/munich-quantum-toolkit/core/pull/2026 +[#2025]: https://github.com/munich-quantum-toolkit/core/pull/2025 +[#2018]: https://github.com/munich-quantum-toolkit/core/pull/2018 +[#2017]: https://github.com/munich-quantum-toolkit/core/pull/2017 +[#2016]: https://github.com/munich-quantum-toolkit/core/pull/2016 +[#2015]: https://github.com/munich-quantum-toolkit/core/pull/2015 +[#2014]: https://github.com/munich-quantum-toolkit/core/pull/2014 +[#2011]: https://github.com/munich-quantum-toolkit/core/pull/2011 +[#2010]: https://github.com/munich-quantum-toolkit/core/pull/2010 +[#2008]: https://github.com/munich-quantum-toolkit/core/pull/2008 +[#2007]: https://github.com/munich-quantum-toolkit/core/pull/2007 +[#2006]: https://github.com/munich-quantum-toolkit/core/pull/2006 +[#2005]: https://github.com/munich-quantum-toolkit/core/pull/2005 +[#2003]: https://github.com/munich-quantum-toolkit/core/pull/2003 +[#2002]: https://github.com/munich-quantum-toolkit/core/pull/2002 +[#2001]: https://github.com/munich-quantum-toolkit/core/pull/2001 +[#2000]: https://github.com/munich-quantum-toolkit/core/pull/2000 +[#1999]: https://github.com/munich-quantum-toolkit/core/pull/1999 +[#1998]: https://github.com/munich-quantum-toolkit/core/pull/1998 +[#1997]: https://github.com/munich-quantum-toolkit/core/pull/1997 +[#1996]: https://github.com/munich-quantum-toolkit/core/pull/1996 +[#1995]: https://github.com/munich-quantum-toolkit/core/pull/1995 +[#1994]: https://github.com/munich-quantum-toolkit/core/pull/1994 +[#1993]: https://github.com/munich-quantum-toolkit/core/pull/1993 +[#1992]: https://github.com/munich-quantum-toolkit/core/pull/1992 +[#1987]: https://github.com/munich-quantum-toolkit/core/pull/1987 +[#1986]: https://github.com/munich-quantum-toolkit/core/pull/1986 +[#1983]: https://github.com/munich-quantum-toolkit/core/pull/1983 +[#1980]: https://github.com/munich-quantum-toolkit/core/pull/1980 +[#1979]: https://github.com/munich-quantum-toolkit/core/pull/1979 +[#1978]: https://github.com/munich-quantum-toolkit/core/pull/1978 +[#1976]: https://github.com/munich-quantum-toolkit/core/pull/1976 +[#1975]: https://github.com/munich-quantum-toolkit/core/pull/1975 +[#1972]: https://github.com/munich-quantum-toolkit/core/pull/1972 +[#1967]: https://github.com/munich-quantum-toolkit/core/pull/1967 +[#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 +[#1961]: https://github.com/munich-quantum-toolkit/core/pull/1961 +[#1957]: https://github.com/munich-quantum-toolkit/core/pull/1957 +[#1953]: https://github.com/munich-quantum-toolkit/core/pull/1953 +[#1952]: https://github.com/munich-quantum-toolkit/core/pull/1952 +[#1951]: https://github.com/munich-quantum-toolkit/core/pull/1951 +[#1950]: https://github.com/munich-quantum-toolkit/core/pull/1950 +[#1938]: https://github.com/munich-quantum-toolkit/core/pull/1938 +[#1936]: https://github.com/munich-quantum-toolkit/core/pull/1936 +[#1935]: https://github.com/munich-quantum-toolkit/core/pull/1935 +[#1934]: https://github.com/munich-quantum-toolkit/core/pull/1934 +[#1933]: https://github.com/munich-quantum-toolkit/core/pull/1933 +[#1927]: https://github.com/munich-quantum-toolkit/core/pull/1927 +[#1925]: https://github.com/munich-quantum-toolkit/core/pull/1925 +[#1924]: https://github.com/munich-quantum-toolkit/core/pull/1924 +[#1923]: https://github.com/munich-quantum-toolkit/core/pull/1923 +[#1915]: https://github.com/munich-quantum-toolkit/core/pull/1915 +[#1914]: https://github.com/munich-quantum-toolkit/core/pull/1914 +[#1912]: https://github.com/munich-quantum-toolkit/core/pull/1912 +[#1911]: https://github.com/munich-quantum-toolkit/core/pull/1911 +[#1910]: https://github.com/munich-quantum-toolkit/core/pull/1910 [#1904]: https://github.com/munich-quantum-toolkit/core/pull/1904 [#1897]: https://github.com/munich-quantum-toolkit/core/pull/1897 [#1895]: https://github.com/munich-quantum-toolkit/core/pull/1895 [#1887]: https://github.com/munich-quantum-toolkit/core/pull/1887 +[#1886]: https://github.com/munich-quantum-toolkit/core/pull/1886 [#1877]: https://github.com/munich-quantum-toolkit/core/pull/1877 [#1873]: https://github.com/munich-quantum-toolkit/core/pull/1873 [#1872]: https://github.com/munich-quantum-toolkit/core/pull/1872 [#1870]: https://github.com/munich-quantum-toolkit/core/pull/1870 [#1869]: https://github.com/munich-quantum-toolkit/core/pull/1869 +[#1865]: https://github.com/munich-quantum-toolkit/core/pull/1865 [#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 [#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 +[#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 [#1836]: https://github.com/munich-quantum-toolkit/core/pull/1836 @@ -808,6 +918,7 @@ changelogs._ [#1817]: https://github.com/munich-quantum-toolkit/core/pull/1817 [#1815]: https://github.com/munich-quantum-toolkit/core/pull/1815 [#1814]: https://github.com/munich-quantum-toolkit/core/pull/1814 +[#1810]: https://github.com/munich-quantum-toolkit/core/pull/1810 [#1809]: https://github.com/munich-quantum-toolkit/core/pull/1809 [#1808]: https://github.com/munich-quantum-toolkit/core/pull/1808 [#1807]: https://github.com/munich-quantum-toolkit/core/pull/1807 @@ -815,6 +926,7 @@ changelogs._ [#1805]: https://github.com/munich-quantum-toolkit/core/pull/1805 [#1803]: https://github.com/munich-quantum-toolkit/core/pull/1803 [#1802]: https://github.com/munich-quantum-toolkit/core/pull/1802 +[#1799]: https://github.com/munich-quantum-toolkit/core/pull/1799 [#1787]: https://github.com/munich-quantum-toolkit/core/pull/1787 [#1786]: https://github.com/munich-quantum-toolkit/core/pull/1786 [#1782]: https://github.com/munich-quantum-toolkit/core/pull/1782 @@ -825,6 +937,7 @@ changelogs._ [#1766]: https://github.com/munich-quantum-toolkit/core/pull/1766 [#1765]: https://github.com/munich-quantum-toolkit/core/pull/1765 [#1762]: https://github.com/munich-quantum-toolkit/core/pull/1762 +[#1756]: https://github.com/munich-quantum-toolkit/core/pull/1756 [#1755]: https://github.com/munich-quantum-toolkit/core/pull/1755 [#1751]: https://github.com/munich-quantum-toolkit/core/pull/1751 [#1749]: https://github.com/munich-quantum-toolkit/core/pull/1749 @@ -840,9 +953,11 @@ changelogs._ [#1710]: https://github.com/munich-quantum-toolkit/core/pull/1710 [#1709]: https://github.com/munich-quantum-toolkit/core/pull/1709 [#1706]: https://github.com/munich-quantum-toolkit/core/pull/1706 +[#1705]: https://github.com/munich-quantum-toolkit/core/pull/1705 [#1702]: https://github.com/munich-quantum-toolkit/core/pull/1702 [#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700 [#1694]: https://github.com/munich-quantum-toolkit/core/pull/1694 +[#1687]: https://github.com/munich-quantum-toolkit/core/pull/1687 [#1676]: https://github.com/munich-quantum-toolkit/core/pull/1676 [#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675 [#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674 @@ -852,6 +967,7 @@ changelogs._ [#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662 [#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660 [#1652]: https://github.com/munich-quantum-toolkit/core/pull/1652 +[#1648]: https://github.com/munich-quantum-toolkit/core/pull/1648 [#1638]: https://github.com/munich-quantum-toolkit/core/pull/1638 [#1637]: https://github.com/munich-quantum-toolkit/core/pull/1637 [#1635]: https://github.com/munich-quantum-toolkit/core/pull/1635 @@ -861,6 +977,7 @@ changelogs._ [#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 +[#1603]: https://github.com/munich-quantum-toolkit/core/pull/1603 [#1602]: https://github.com/munich-quantum-toolkit/core/pull/1602 [#1600]: https://github.com/munich-quantum-toolkit/core/pull/1600 [#1596]: https://github.com/munich-quantum-toolkit/core/pull/1596 @@ -970,6 +1087,7 @@ changelogs._ [#1164]: https://github.com/munich-quantum-toolkit/core/pull/1164 [#1157]: https://github.com/munich-quantum-toolkit/core/pull/1157 [#1151]: https://github.com/munich-quantum-toolkit/core/pull/1151 +[#1150]: https://github.com/munich-quantum-toolkit/core/pull/1150 [#1148]: https://github.com/munich-quantum-toolkit/core/pull/1148 [#1147]: https://github.com/munich-quantum-toolkit/core/pull/1147 [#1140]: https://github.com/munich-quantum-toolkit/core/pull/1140 From 3b0a3b2cd2adbb6ab34c47d0fbcf3df0dcd7e001 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 13:23:31 +0200 Subject: [PATCH 181/235] :construction: Fixed files that rebase broke --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b903ca41..34d6241595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,6 @@ releases may include breaking changes. ### Added -- ✨ Add a `constant propagation` pass for reducing superfluous quantum resources by propagating the quantum - machine state ([#1845]) ([**@lirem101**]) - ✨ Let a package register a program serializer for a program format through the `mqt.core.qiskit.program_serializers` entry point group ([#2114]) ([**@marcelwa**]) @@ -801,21 +799,33 @@ for previous changelogs._ [#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 + [#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 + [#2140]: https://github.com/munich-quantum-toolkit/core/pull/2140 + [#2138]: https://github.com/munich-quantum-toolkit/core/pull/2138 + [#2137]: https://github.com/munich-quantum-toolkit/core/pull/2137 + [#2136]: https://github.com/munich-quantum-toolkit/core/pull/2136 + [#2133]: https://github.com/munich-quantum-toolkit/core/pull/2133 [#2125]: https://github.com/munich-quantum-toolkit/core/pull/2125 + [#2124]: https://github.com/munich-quantum-toolkit/core/pull/2124 + [#2118]: https://github.com/munich-quantum-toolkit/core/pull/2118 [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 + [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 + [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 [#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 + [#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 + [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 [#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 [#2084]: https://github.com/munich-quantum-toolkit/core/pull/2084 From 0fa42bb9aafd808a2526ed49a28230bf331509f3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 13:52:38 +0200 Subject: [PATCH 182/235] :construction: Fixed conversion to float and DivSIOp --- .../ClassicalArithOperation.h | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 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 980b94b267..a54c658345 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -104,8 +104,19 @@ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, return result ? int64_t{1} : int64_t{0}; }) - .Case( - [&](auto) { return a.sdiv(b).getSExtValue(); }) + .Case([&](auto) { + if (a.isZero()) { + llvm::report_fatal_error( + "Handling of division by zero in mlir::arith::DivSIOp is not " + "supported by constant propagation."); + } + if (a.isAllOnes() && b.isMinSignedValue()) { + llvm::report_fatal_error( + "Handling of INT_MIN / -1 in mlir::arith::DivSIOp is not " + "supported by constant propagation."); + } + return a.sdiv(b).getSExtValue(); + }) .Case([&](auto) { return a.getSExtValue() > b.getSExtValue() ? a.getSExtValue() : b.getSExtValue(); @@ -220,12 +231,14 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, "mlir::qco::classicalarithoperation"); } - const double folded = result.convertToDouble(); - - if (!llvm::APFloat(folded).bitwiseIsEqual(result)) { + llvm::APFloat resultInDouble = result; + bool loseInfoResult = false; + result.convert(llvm::APFloat::IEEEdouble(), rm, &loseInfoResult); + if (loseInfoResult) { llvm::report_fatal_error( "Floating-point fold result cannot be represented safely as double."); } + const double folded = result.convertToDouble(); return folded; } From 84a852e8b841cf8c93717c2dca5f46c02cbc52d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:24:13 +0000 Subject: [PATCH 183/235] =?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 | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d6241595..5884db3618 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**]) - ✨ Let a package register a program serializer for a program format through the `mqt.core.qiskit.program_serializers` entry point group ([#2114]) ([**@marcelwa**]) @@ -71,8 +73,10 @@ releases may include breaking changes. [#1979], [#2007], [#2026], [#2030], [#2066]) ([**@burgholzer**], [**@denialhaag**], [**@simon1hofmann**], [**@li-mingbao**], [**@DRovara**], [**@MatthiasReumann**]) -- ✨ Add decision diagram-based construction and simulation of static unitary - QCO functions ([#1915]) ([**@simon1hofmann**]) +- ✨ Add decision diagram-based construction, simulation, and sampling of QCO + functions, including static unitaries, mid-circuit `measure`/`reset`, concrete + `if`/`index_switch`, initial classical SSA evaluation, dense `k>3` wire + embedding, and multi-shot `sample` ([#1915], [#1973]) ([**@simon1hofmann**]) - ✨ Add target-independent two-qubit gate fusion, target-native post-routing synthesis, and operation-capability and static-site conformance ([#1865], [#1961], [#1998]) ([**@simon1hofmann**], [**@burgholzer**]) @@ -163,8 +167,22 @@ releases may include breaking changes. - 📦 Build MLIR by default for C++ library builds ([#1356]) ([**@burgholzer**], [**@denialhaag**]) +### Fixed + +- 🐛 Distinguish scalar OpenQASM qubits from one-element qubit registers and + reject indexing scalar qubits ([#2157]) ([**@DRovara**], [**@burgholzer**]) +- 🐛 Preserve the original OpenQASM type error when an assignment's right-hand + expression cannot be typed ([#2156]) ([**@DRovara**], [**@burgholzer**]) + ### Removed +- 💥 Remove batch job submission from the QDMI client. `Device::submitJob` now + states that MQT Core does not support batch jobs ([#2148]) ([**@marcelwa**]) +- 💥 Remove the IQM JSON converter `qiskit_to_iqm_json` and the `MoveGate` from + the Qiskit plugin, which [QDMI-on-IQM] now owns ([#2114]) ([**@marcelwa**]) +- 💥 Remove the unused decision-diagram approximation algorithm, including the + `dd/Approximation.hpp` header, `dd::ApproximationMetadata`, and + `dd::approximate`. No replacement is provided ([#2154]) ([**@burgholzer**]) - 💥 Remove `nlohmann_json` from the public package contract. MQT Core no longer installs or exports the library, no installed header exposes a `nlohmann` type, and the decision-diagram statistics report through strings and streams @@ -798,34 +816,27 @@ for previous changelogs._ -[#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 +[#2157]: https://github.com/munich-quantum-toolkit/core/pull/2157 -[#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 +[#2156]: https://github.com/munich-quantum-toolkit/core/pull/2156 +[#2154]: https://github.com/munich-quantum-toolkit/core/pull/2154 +[#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148 +[#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147 [#2140]: https://github.com/munich-quantum-toolkit/core/pull/2140 - [#2138]: https://github.com/munich-quantum-toolkit/core/pull/2138 - [#2137]: https://github.com/munich-quantum-toolkit/core/pull/2137 - [#2136]: https://github.com/munich-quantum-toolkit/core/pull/2136 - [#2133]: https://github.com/munich-quantum-toolkit/core/pull/2133 [#2125]: https://github.com/munich-quantum-toolkit/core/pull/2125 - [#2124]: https://github.com/munich-quantum-toolkit/core/pull/2124 - [#2118]: https://github.com/munich-quantum-toolkit/core/pull/2118 [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 - [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 - [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 [#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 - [#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 - [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 [#2105]: https://github.com/munich-quantum-toolkit/core/pull/2105 [#2084]: https://github.com/munich-quantum-toolkit/core/pull/2084 @@ -878,6 +889,8 @@ for previous changelogs._ [#1978]: https://github.com/munich-quantum-toolkit/core/pull/1978 [#1976]: https://github.com/munich-quantum-toolkit/core/pull/1976 [#1975]: https://github.com/munich-quantum-toolkit/core/pull/1975 + +[#1973]: https://github.com/munich-quantum-toolkit/core/pull/1973 [#1972]: https://github.com/munich-quantum-toolkit/core/pull/1972 [#1967]: https://github.com/munich-quantum-toolkit/core/pull/1967 [#1965]: https://github.com/munich-quantum-toolkit/core/pull/1965 @@ -915,6 +928,7 @@ for previous changelogs._ [#1850]: https://github.com/munich-quantum-toolkit/core/pull/1850 [#1849]: https://github.com/munich-quantum-toolkit/core/pull/1849 [#1848]: https://github.com/munich-quantum-toolkit/core/pull/1848 + [#1845]: https://github.com/munich-quantum-toolkit/core/pull/1845 [#1844]: https://github.com/munich-quantum-toolkit/core/pull/1844 [#1842]: https://github.com/munich-quantum-toolkit/core/pull/1842 @@ -1208,6 +1222,8 @@ for previous changelogs._ [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ [Common Changelog]: https://common-changelog.org + +[QDMI-on-IQM]: https://github.com/iqm-finland/QDMI-on-IQM [Semantic Versioning]: https://semver.org/spec/v2.0.0.html [munich-quantum-toolkit]: https://github.com/munich-quantum-toolkit [PEP 639]: https://peps.python.org/pep-0639/ From 3b4041b51afa66cd612f101ebc86c2963ed12db3 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 14:16:17 +0200 Subject: [PATCH 184/235] :construction: Removed redundant guard --- .../Optimizations/ConstantPropagation/GateToMap.h | 8 ++------ 1 file changed, 2 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 eb3e68ab53..9d0df93903 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -10,13 +10,11 @@ #pragma once -#include -#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 @@ -226,5 +224,3 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { llvm::report_fatal_error("Unsupported gate in mlir::qco::gatetomap"); }); } - -#endif // MQT_CORE_GATETOMAP_H From bc9deb0473800a71c0bc980969b26ce3b3856aba Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 17:39:38 +0200 Subject: [PATCH 185/235] :boom: Removed Optimization folders --- .../Optimization/HadamardLifting.cpp | 350 ----------- .../Transforms/Optimization/CMakeLists.txt | 28 - .../test_qco_hadamard_lifting.cpp | 572 ------------------ 3 files changed, 950 deletions(-) delete mode 100644 mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp delete mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt delete mode 100644 mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp deleted file mode 100644 index f75527c89a..0000000000 --- a/mlir/lib/Dialect/QCO/Transforms/Optimization/HadamardLifting.cpp +++ /dev/null @@ -1,350 +0,0 @@ -/* - * 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 "mlir/Dialect/QCO/Transforms/Passes.h" - -#include -#include -#include -#include -#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 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 - 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. - */ -struct LiftHadamardsAbovePauliGatesPattern final - : OpInterfaceRewritePattern { - explicit LiftHadamardsAbovePauliGatesPattern(MLIRContext* context) - : OpInterfaceRewritePattern(context) {} - - /** - * @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 - gPhase(pi) - - * - Z - H - = - H - X - - * - * @param gate The Pauli gate. - * @param hadamardGate The Hadamard gate. - * @param rewriter The used rewriter. - * @return success() if circuit was changed, failure() otherwise - */ - static LogicalResult swapPauliWithHadamard(UnitaryOpInterface gate, - HOp hadamardGate, - PatternRewriter& rewriter) { - auto* op = gate.getOperation(); - 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)); - auto yGate = rewriter.replaceOpWithNewOp( - hadamardGate, hadamardGate.getInputQubit(0)); - GPhaseOp::create(rewriter, yGate.getLoc(), std::numbers::pi); - return success(); - }) - .Default([&](auto) { return failure(); }); - } - - /** - * @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 { - // op needs to be an uncontrolled Pauli gate - if (!llvm::isa(op) && !llvm::isa(op) && !llvm::isa(op)) { - return failure(); - } - - // op needs to be in front of a Hadamard gate - auto hadamardGate = llvm::dyn_cast(*op->getUsers().begin()); - - if (!hadamardGate || - op.getOutputTarget(0) != hadamardGate.getInputTarget(0)) { - return failure(); - } - - return swapPauliWithHadamard(op, hadamardGate, rewriter); - } -}; - -/** - * @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 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. - */ -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 - * before the new control. - * - * @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 - */ - 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(); - auto hadamardGate = llvm::dyn_cast(predecessor); - if (!hadamardGate) { - return failure(); - } - - // 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); - if (!cnotGate) { - return failure(); - } - if (cnotGate.getNumTargets() != 1 || - cnotGate.getOutputTarget(0) != inQubitHadamard || - !llvm::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( - *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()) { - rewriter.replaceAllUsesWith(outQubit, - hadamardGate.getInputForOutput(outQubit)); - } - rewriter.eraseOp(hadamardGate); - - // Add Hadamard gates to the other in- and output gates of CNOT - addHadamardGatesBeforeGate( - cnotGate, - {cnotGate.getInputTarget(0), cnotGate.getInputControl(controlIndex)}, - rewriter); - - const HOp newHOPAfterCtrl = addHadamardGatesAfterGate( - cnotGate, - cnotGate.getOutputForInput(cnotGate.getInputControl(controlIndex)), - rewriter); - - // Flip CNOT targets and ctrl - swapQubits(cnotGate, cnotGate.getInputControl(controlIndex), - cnotGate.getInputTarget(0), op, newHOPAfterCtrl, rewriter); - - return success(); - } -}; - -/** - * @brief Pass raises Hadamard gates above controlled and uncontrolled Pauli - * gates. - */ -struct HadamardLifting final : impl::HadamardLiftingBase { - using HadamardLiftingBase::HadamardLiftingBase; - -protected: - void runOnOperation() override { - const auto op = getOperation(); - auto* ctx = &getContext(); - - // Define the set of patterns to use. - RewritePatternSet patterns(ctx); - 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 diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt deleted file mode 100644 index 5d6043aaad..0000000000 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -# 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 - MLIRQCOTransforms - MLIRSupportMQT - MLIRQTensorDialect - MLIRQCOUtils - MLIRIR - MLIRPass - MLIRSupport - LLVMSupport) - -mqt_mlir_configure_unittest_target(${target_name}) - -gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) 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 deleted file mode 100644 index 13c95c1c87..0000000000 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimization/test_qco_hadamard_lifting.cpp +++ /dev/null @@ -1,572 +0,0 @@ -/* - * 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 "mlir/Support/Passes.h" - -#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(createHadamardLifting()); - return pm.run(module); - } -}; - -} // namespace - -// ################################################## -// # Raise Hadamard over uncontrolled Pauli gate Tests -// ################################################## - -/** - * @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); - 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]); - referenceBuilder.gphase(std::numbers::pi); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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]); - 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(std::numbers::pi); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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, liftHadamardOnlyOverPrecedingPauliGate) { - 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(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).succeeded()); - - EXPECT_TRUE( - 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. - */ -TEST_F(QCOHadamardLiftingTest, liftHadamardOverPauliGateIfControlled) { - auto q = programBuilder.allocQubitRegister(2); - q[0] = programBuilder.x(q[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 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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).succeeded()); - - EXPECT_TRUE( - 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. - */ -TEST_F(QCOHadamardLiftingTest, doNotLiftHadamardIfDifferentControls) { - auto q = programBuilder.allocQubitRegister(3); - 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.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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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 [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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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); - 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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).succeeded()); - - EXPECT_TRUE( - 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 - * a measurement is following directly after it. - */ -TEST_F(QCOHadamardLiftingTest, liftHadamardOverCNOTGate) { - auto q = programBuilder.allocQubitRegister(2); - const auto b = programBuilder.allocClassicalBitRegister(1); - q[0] = programBuilder.s(q[0]); - 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); - 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 [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()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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); - 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])}; - }); - q[1] = programBuilder.h(q0[0]); - programBuilder.measure(q[1], b[0]); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(3); - const auto bRef = referenceBuilder.allocClassicalBitRegister(1); - qRef[0] = referenceBuilder.h(qRef[0]); - 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])}; - }); - referenceBuilder.h(q1Ref[0]); - referenceBuilder.measure(q02Ref[0], bRef[0]); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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); - const auto b = programBuilder.allocClassicalBitRegister(3); - programBuilder.cx(q[1], q[0]); - 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); - const auto bRef = referenceBuilder.allocClassicalBitRegister(3); - referenceBuilder.cx(qRef[1], qRef[0]); - 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]); - reference = referenceBuilder.finalize(); - - ASSERT_TRUE(runHadamardLiftingPass(module.get()).succeeded()); - runCanonicalizationPasses(reference.get()); - EXPECT_TRUE(verify(*reference).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 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 llvm::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 llvm::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 76a64707281067968ebe41fe51c769c761085a3a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 17:40:08 +0200 Subject: [PATCH 186/235] :construction: Corrected Optimizations/CMakeLists.txt --- .../Transforms/Optimizations/CMakeLists.txt | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index baeeaf7863..fdba594f73 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -8,33 +8,36 @@ set(target_name mqt-core-mlir-unittest-optimizations) add_executable( - ${target_name} - test_qco_constant_propagation.cpp - test_qco_hadamard_lifting.cpp - test_qco_measurement_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp - test_qco_remove_dead_gates.cpp - test_qco_replace_classical_controls.cpp - test_qco_reuse_qubits.cpp - test_quantum_loop_unroll.cpp - ConstantPropagation/test_quantumState.cpp - ConstantPropagation/test_hybridState.cpp - ConstantPropagation/test_unionTable.cpp) + ${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_measurement_lifting.cpp + test_qco_merge_single_qubit_rotation.cpp + test_qco_remove_dead_gates.cpp + test_qco_replace_classical_controls.cpp + test_qco_reuse_qubits.cpp + test_quantum_loop_unroll.cpp) target_link_libraries( - ${target_name} - PRIVATE GTest::gmock - GTest::gtest_main - MLIRQCOProgramBuilder - MLIRQCOTransforms - MLIRQCOUtils - MLIRParser - MLIRIR - MLIRPass - MLIRSupport - LLVMSupport - MLIRSupportMQT) + ${target_name} + PRIVATE GTest::gtest_main + GTest::gmock + MLIRControlFlowDialect + MLIRQCODDFunctionality + MLIRQCOProgramBuilder + MLIRQCOPrograms + MLIRQCOTransforms + MLIRQCOUtils + MLIRParser + MLIRIR + MLIRPass + MLIRSupport + LLVMSupport + MLIRSupportMQT) -mqt_mlir_configure_unittest_target(${target_name}) +mqt_mlir_configure_unittest_target(${target_name} REQUIRES_EH) -gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) +gtest_discover_tests(${target_name} PROPERTIES LABELS mqt-mlir-unittests DISCOVERY_TIMEOUT 60) \ No newline at end of file From 7b9a7acdb9a744dabe607b302824c6e22bf301be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:44:42 +0000 Subject: [PATCH 187/235] =?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 --- .../Transforms/Optimizations/CMakeLists.txt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index fdba594f73..4357cd4219 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -8,36 +8,36 @@ set(target_name mqt-core-mlir-unittest-optimizations) 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_measurement_lifting.cpp - test_qco_merge_single_qubit_rotation.cpp - test_qco_remove_dead_gates.cpp - test_qco_replace_classical_controls.cpp - test_qco_reuse_qubits.cpp - test_quantum_loop_unroll.cpp) + ${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_measurement_lifting.cpp + test_qco_merge_single_qubit_rotation.cpp + test_qco_remove_dead_gates.cpp + test_qco_replace_classical_controls.cpp + test_qco_reuse_qubits.cpp + test_quantum_loop_unroll.cpp) target_link_libraries( - ${target_name} - PRIVATE GTest::gtest_main - GTest::gmock - MLIRControlFlowDialect - MLIRQCODDFunctionality - MLIRQCOProgramBuilder - MLIRQCOPrograms - MLIRQCOTransforms - MLIRQCOUtils - MLIRParser - MLIRIR - MLIRPass - MLIRSupport - LLVMSupport - MLIRSupportMQT) + ${target_name} + PRIVATE GTest::gtest_main + GTest::gmock + MLIRControlFlowDialect + MLIRQCODDFunctionality + MLIRQCOProgramBuilder + MLIRQCOPrograms + MLIRQCOTransforms + MLIRQCOUtils + MLIRParser + MLIRIR + MLIRPass + MLIRSupport + LLVMSupport + MLIRSupportMQT) mqt_mlir_configure_unittest_target(${target_name} REQUIRES_EH) -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 baf7d1e75bb58a00acf2d4758ac388e450e8557e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 17:51:08 +0200 Subject: [PATCH 188/235] :construction: Corrected imports --- .../Optimizations/ConstantPropagation/HybridState.hpp | 5 +++++ 1 file changed, 5 insertions(+) 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 b8c56b5d46..8ec37e43d0 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -16,9 +16,14 @@ #include #include +#include +#include #include #include +#include +#include #include +#include #include namespace mlir::qco { From ce2b26a1ed697a85c6b8e1beabba3fbd7a2a32ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:51:50 +0000 Subject: [PATCH 189/235] =?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 --- .../Optimizations/ConstantPropagation/HybridState.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8ec37e43d0..b4a591bfd9 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -16,9 +16,9 @@ #include #include +#include #include #include -#include #include #include #include From ed6fbb3ac970b1df488886099f1e85772d8b65bc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 17:54:23 +0200 Subject: [PATCH 190/235] :construction: Corrected nodiscard message --- .../Optimizations/ConstantPropagation/HybridState.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b4a591bfd9..28d0466c1a 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.hpp @@ -293,7 +293,7 @@ class HybridState { * @param classicalValues The classical values to check. * @returns True if the amplitude is always zero, false otherwise. */ - [[nodiscard("HybridState::hasAlwaysZeroAmplitude called but ignored")]] bool + [[nodiscard("HybridState::hasAlwaysZeroProbability called but ignored")]] bool hasAlwaysZeroProbability( const std::unordered_map& qubitValues, const llvm::DenseMap& classicalValues) const; From 681fcc927e95cf14def469ffc89966166beab4a9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:09:40 +0200 Subject: [PATCH 191/235] :construction: Prevented coping of unordered_map --- .../ConstantPropagation/QuantumState.hpp | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 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 ccc5ab004e..ea24dbedfd 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -98,9 +98,9 @@ class QuantumState { */ std::unordered_map> getNewMappingFromQubitGate( - std::unordered_map>> - gateMapping, + const std::unordered_map< + unsigned int, + std::unordered_map>>& gateMapping, const std::span positionOfTargetQubits, const unsigned int bitmaskForCtrls) { std::unordered_map> newValues; @@ -137,10 +137,17 @@ 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.at(i)] += valueToI * value; + auto outerIt = gateMapping.find(mapFrom); + if (outerIt != gateMapping.end()) { + const auto &mapForThisQubit = outerIt->second; + for (unsigned int i = 0; i < numberOfTargetValues; i++) { + auto innerIt = mapForThisQubit.find(i); + if (innerIt == mapForThisQubit.end()) { + continue; + } + if (auto valueToI = innerIt->second; abs(valueToI) > 1e-4) { + newValues[keysForNewValue.at(i)] += valueToI * value; + } } } } From 35ad068e01fc46bb9d527dcb21b7813f0e202d76 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:10:24 +0000 Subject: [PATCH 192/235] =?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 --- .../Optimizations/ConstantPropagation/QuantumState.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 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 ea24dbedfd..89e895195c 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -99,8 +99,8 @@ class QuantumState { std::unordered_map> getNewMappingFromQubitGate( const std::unordered_map< - unsigned int, - std::unordered_map>>& gateMapping, + unsigned int, std::unordered_map>>& + gateMapping, const std::span positionOfTargetQubits, const unsigned int bitmaskForCtrls) { std::unordered_map> newValues; @@ -139,7 +139,7 @@ class QuantumState { auto outerIt = gateMapping.find(mapFrom); if (outerIt != gateMapping.end()) { - const auto &mapForThisQubit = outerIt->second; + const auto& mapForThisQubit = outerIt->second; for (unsigned int i = 0; i < numberOfTargetValues; i++) { auto innerIt = mapForThisQubit.find(i); if (innerIt == mapForThisQubit.end()) { From 69fa7a36873ed4565f782ace42d161dc5f804662 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:13:49 +0200 Subject: [PATCH 193/235] :construction: Prevented multiplication by zero and change of span, corrected and operation. --- .../ConstantPropagation/UnionTable.hpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 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 3b48331404..58680a2f91 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -167,8 +167,9 @@ class UnionTable { // Check if the number of entries would be too large unsigned int numberOfNewEntries = 1; for (const auto& e : entriesToUnify) { - if (e.states.size() != 0 && - numberOfNewEntries > maximumHybridEntries / e.states.size()) { + if (e.top || + (!e.states.empty() && + numberOfNewEntries > maximumHybridEntries / e.states.size())) { putEntriesToTop(entriesToUnify); return true; } @@ -184,7 +185,7 @@ class UnionTable { newEntry.participatingClassicalValues.insert(classicalValues.begin(), classicalValues.end()); newEntry.participatingQubits.insert(qubits.begin(), qubits.end()); - if (!newEntry.top & newEntry.states.empty()) { + if (!newEntry.top && newEntry.states.empty()) { newEntry.states = e.states; continue; } @@ -243,8 +244,10 @@ class UnionTable { 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); + std::vector reversedNewTargets(newQuantumTargets.begin(), + newQuantumTargets.end()); + std::ranges::reverse(reversedNewTargets); + replaceValuesGlobally(targets, reversedNewTargets); } /** From d276db4966d4d77b2f4234c8212675f593b24699 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:17:01 +0200 Subject: [PATCH 194/235] :construction: Corrected Passes.td --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index c15f5e7ffe..a2eb7700a0 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -181,7 +181,8 @@ def MappingPass : Pass<"place-and-route", "mlir::ModuleOp"> { //===----------------------------------------------------------------------===// def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { - let dependentDialects = ["mlir::qco::QCODialect"]; + let dependentDialects = ["mlir::qco::QCODialect", + "::mlir::arith::ArithDialect"]; let summary = "This pass applies constant propagation to a circuit. It " "assumes that all input qubits are |0>. It propagates the " @@ -227,11 +228,11 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { phase gate is removed and replaced by a global phase gate instead. }]; - let options = [Option<"maximumNonzeroAmplitudes", "maximumNonzeroAmplitudes", + let options = [Option<"maximumNonzeroAmplitudes", "maximum-nonzero-amplitudes", "std::size_t", "4", "The maximum number of non-zero amplitudes in the " "tracted quantum states before reaching top.">, - Option<"maximumHybridStates", "maximumHybridStates", + Option<"maximumHybridStates", "maximum-hybrid-states", "std::size_t", "4", "The maximum number of hybrid states which have a " "non-zero probability.">]; From 8c58376699bc952a99090a49dec8ba95866be0c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:17:41 +0000 Subject: [PATCH 195/235] =?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 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index a2eb7700a0..5b8491e421 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -228,8 +228,8 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { phase gate is removed and replaced by a global phase gate instead. }]; - let options = [Option<"maximumNonzeroAmplitudes", "maximum-nonzero-amplitudes", - "std::size_t", "4", + let options = [Option<"maximumNonzeroAmplitudes", + "maximum-nonzero-amplitudes", "std::size_t", "4", "The maximum number of non-zero amplitudes in the " "tracted quantum states before reaching top.">, Option<"maximumHybridStates", "maximum-hybrid-states", From 08ed321f8c8fc68e0c1b3c32b8bbe6b234d16a22 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:18:38 +0200 Subject: [PATCH 196/235] :memo: Removed redundant copyright header --- .../Transforms/Optimizations/ConstantPropagation.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 3402580301..1537aa3551 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -8,16 +8,6 @@ * Licensed under the MIT License */ -/* - * 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 "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp" From 3826b814ae556f6fc5697003de3ce42dfee96c1f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:21:50 +0200 Subject: [PATCH 197/235] :construction: Safeguard against nullptr --- .../QCO/Transforms/Optimizations/ConstantPropagation.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 1537aa3551..4b1d1b03c2 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -144,7 +144,10 @@ static void moveMeasurementsToFront(ModuleOp module, MLIRContext* ctx) { module.walk([&](MeasureOp op) { Operation* previousInstruction = op.getQubitIn().getDefiningOp(); Operation* previousNode = op->getPrevNode(); - while (isa(previousNode) && + if (previousInstruction == nullptr) { + return; + } + while (previousNode != nullptr && isa(previousNode) && previousInstruction != previousNode) { previousNode = previousNode->getPrevNode(); } From b19709a14c208b88a507ba7c38c05310385f54e7 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:48:47 +0200 Subject: [PATCH 198/235] :boom: Remove constant propagation test --- .../Compiler/test_compiler_pipeline.cpp | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/mlir/unittests/Compiler/test_compiler_pipeline.cpp b/mlir/unittests/Compiler/test_compiler_pipeline.cpp index a3eb6ea2ae..d7f929fb0f 100644 --- a/mlir/unittests/Compiler/test_compiler_pipeline.cpp +++ b/mlir/unittests/Compiler/test_compiler_pipeline.cpp @@ -1480,31 +1480,6 @@ TEST_F(CompilerPipelineTest, PopulateDecomposeMultiControlledPipeline) { EXPECT_NE(after, before); } -/** - * @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( NativeQCPrograms, CompilerPipelineTest, testing::Values( From 80ea8107acd2fd130d5f742212adba8b252f7c2e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:49:29 +0200 Subject: [PATCH 199/235] :construction: Prevent inverted branches and clean formatting --- .../Optimizations/ConstantPropagation.cpp | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 4b1d1b03c2..cddf3ac61f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -297,44 +297,47 @@ static bool addsOnlyGlobalPhase(UnionTable* ut, UnitaryOpInterface* op, static Operation* createOperationFromUnitaryOperation(Operation* op, PatternRewriter& rewriter, const std::span qubitsIn) { - auto* const newOp = - 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( - 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_PLUS_MINUS_OPS( - XXPlusYYOp) - CREATE_OP_CASE_PLUS_MINUS_OPS( - XXMinusYYOp) - .Default([&](auto) -> Operation* { - llvm::report_fatal_error("Unsu" - "ppor" - "ted " - "oper" - "atio" - "n"); - }); + // clang-format off + Operation *newOp = 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(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_PLUS_MINUS_OPS(XXPlusYYOp) + CREATE_OP_CASE_PLUS_MINUS_OPS(XXMinusYYOp) + .Default([&](Operation *unsupported) -> Operation * { + llvm::report_fatal_error( + ("Unsupported operation: " + unsupported->getName() + .getStringRef() + .str()) + .c_str()); + }); + // clang-format on return newOp; } @@ -690,7 +693,8 @@ putOperationIntoBranch(UnionTable* ut, UnitaryOpInterface op, } } - const bool createThenBranch = !ctrlsToMod.classicalPosCtrlsToAdd.empty(); + const bool createThenBranch = + numberOfNewConditions > 1 || !ctrlsToMod.classicalPosCtrlsToAdd.empty(); ValueRange insertedQubits = op.getInputQubits(); const SmallVector locs(insertedQubits.size(), op->getLoc()); auto newIfOp = From f2cd251ab5c302f82f9939e8acbea42f62f8a12d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:51:58 +0200 Subject: [PATCH 200/235] :boom: Removed unnecessary guards --- .../Optimizations/ConstantPropagation/HybridState.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp index 0871983962..738efc03b0 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/HybridState.cpp @@ -8,8 +8,6 @@ * 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" @@ -326,5 +324,3 @@ HybridState::getValueThatIsEquivalentToQubit(const unsigned int qubit) const { } } // namespace mlir::qco - -#endif // MQT_CORE_HYBRIDSTATE From d83a32fefe25419979670c7a5272353eefdcf503 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 18:58:29 +0200 Subject: [PATCH 201/235] :construction: Correct constructor for QuantumState --- .../ConstantPropagation/QuantumState.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 998732565d..238f2f5d81 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -40,13 +41,16 @@ QuantumState::QuantumState(const std::span globalQubitNumber, : nQubits(globalQubitNumber.size()), maxNonzeroAmplitudes(maxNonzeroAmplitudes) { constexpr auto maxBitNumber = sizeof(unsigned int) * 8; - if (maxBitNumber < globalQubitNumber.size() || - maxBitNumber < maxNonzeroAmplitudes) { - // Number of qubits or number of maximum nonzero amplitudes exceeds amount - // of qubits/amplitudes that can be managed in the union table. + if (maxBitNumber < globalQubitNumber.size()) { + // Number of qubits exceeds amount of qubits/amplitudes that can be managed + // in the union table. top = true; return; } + if (maxNonzeroAmplitudes > std::numeric_limits::digits) { + // Clamp to the largest meaningful value. + this->maxNonzeroAmplitudes = std::numeric_limits::digits; + } top = false; std::vector qubits; qubits.reserve(globalQubitNumber.size()); From 5c6c9937e35920f3dbee6e69e685091bee27545a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 19:07:43 +0200 Subject: [PATCH 202/235] :construction: Preventing certain answers if top and adapt to all llvm versions --- .../ConstantPropagation/UnionTable.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index c9bdd3117b..3cdfc93c50 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -282,7 +282,6 @@ void UnionTable::propagateReset(const Value quantumTarget, vecOfNewStates.insert(vecOfNewStates.end(), newStates.begin(), newStates.end()); } - ute->states = vecOfNewStates; if (vecOfNewStates.size() > maximumHybridEntries) { putEntriesToTop({*ute}); } else { @@ -294,7 +293,14 @@ void UnionTable::propagateReset(const Value quantumTarget, void UnionTable::propagateQubitAlloc(const Value qubit) { unsigned int maxIndex = 0; if (!qubitsToGlobalIndices.empty()) { - maxIndex = std::ranges::max(qubitsToGlobalIndices.values()) + 1; + auto it = qubitsToGlobalIndices.begin(); + maxIndex = it->second; // first index present + + ++it; + for (; it != qubitsToGlobalIndices.end(); ++it) { + maxIndex = std::max(maxIndex, it->second); + } + ++maxIndex; } std::vector globalQubitIndex = {maxIndex}; const auto hs = HybridState(globalQubitIndex, maxNonzeroAmplitudes); @@ -503,6 +509,9 @@ UnionTable::globalPhaseThatIsAdded(Operation* op, const Value target, bool highestStateReachable = alwaysOne; bool highestStateAlwaysReached = alwaysOne; for (const auto& ute : participatingEntries) { + if (ute.top) { + return {}; + } std::unordered_map qubitCtrlThisEntry; llvm::DenseMap classicalCtrlThisEntry; for (const auto q : ctrlsQuantum) { @@ -564,6 +573,11 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, bool alwaysOne = true; bool alwaysZero = true; for (const auto& hs : valuesToEntries.at(qCtrl)->states) { + if (hs.isHybridStateTop()) { + alwaysOne = false; + alwaysZero = false; + break; + } if (alwaysZero && !hs.isQubitAlwaysZero(qIndex)) { alwaysZero = false; } From 5c15e9a63248d1f5e118d33c6f1dc39be9370366 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:08:23 +0000 Subject: [PATCH 203/235] =?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 --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 3cdfc93c50..ee52c99954 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -294,7 +294,7 @@ void UnionTable::propagateQubitAlloc(const Value qubit) { unsigned int maxIndex = 0; if (!qubitsToGlobalIndices.empty()) { auto it = qubitsToGlobalIndices.begin(); - maxIndex = it->second; // first index present + maxIndex = it->second; // first index present ++it; for (; it != qubitsToGlobalIndices.end(); ++it) { From 7db5031e3b7779af99644073dcc029e125bfb274 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 19:10:32 +0200 Subject: [PATCH 204/235] :boom: Removed empty teardown --- .../Optimizations/ConstantPropagation/test_quantumState.cpp | 2 -- 1 file changed, 2 deletions(-) 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 3889a16607..4e53d2fa2f 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -123,8 +123,6 @@ class QuantumStateTest : public testing::Test { q[1], q[2], q[3]); ; } - - void TearDown() override {} }; TEST_F(QuantumStateTest, applyHGate) { From b4195b25214834515e61752029597e127e43c9bc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 19:16:41 +0200 Subject: [PATCH 205/235] :construction: Adapted docstring and used reference value --- .../Optimizations/test_qco_constant_propagation.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 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 46a47c6691..1a7d9c2a89 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 @@ -55,7 +55,7 @@ class QCOConstantPropagationTest : public testing::Test { } /** - * @brief Adds the hadamardLiftingPass to the current context and runs it. + * @brief Adds the ConstantPropagation pass to the pass manager and runs it. */ static LogicalResult runConstantPropagationPass(ModuleOp module) { PassManager pm(module.getContext()); @@ -378,7 +378,8 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionals) { arith::ConstantOp::create(referenceBuilder, referenceBuilder.getBoolAttr(false)) .getResult(); - arith::OrIOp::create(referenceBuilder, bTrue.getType(), bTrueRef, bFalseRef) + arith::OrIOp::create(referenceBuilder, bTrueRef.getType(), bTrueRef, + bFalseRef) .getResult(); qRef[0] = referenceBuilder.h(qRef[0]); qRef[0] = referenceBuilder.u2(1.4, 2.7, qRef[0]); From ab4bc4a2e42f4bfba66ddf0d881062ca896beabf Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 19:22:53 +0200 Subject: [PATCH 206/235] :white_check_mark: Added tests for double handling --- .../ConstantPropagation/test_hybridState.cpp | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) 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 aac3ef03ce..a086435b5f 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -194,7 +194,68 @@ TEST_F(HybridStateTest, ApplyTwoTimesClassicalControlledGate) { testing::HasSubstr("integerValue1 = 1"))); } -TEST_F(HybridStateTest, handleErrorIfTwoManyAmplitudesAreNonzero) { +TEST_F(HybridStateTest, ApplyClassicalDoubleControlledGateThatsFalse) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrl = {v1}; + hState.addDoubleValue(v1, 0.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}: doubleValue0 = 0.00; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyClassicalDoubleControlledGateThatsTrue) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrl = {v1}; + hState.addDoubleValue(v1, 1.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}: doubleValue0 = 1.10; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyNegClassicalDoubleControlledGateThatsFalse) { + auto hState = HybridState(fourQubits, 4); + constexpr auto v1 = mlir::Value(); + std::vector ctrl = {v1}; + hState.addDoubleValue(v1, 0.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}: doubleValue0 = 0.00; p = 1.00")); +} + +TEST_F(HybridStateTest, ApplyTwoTimesClassicalDoubleControlledGate) { + auto hState = HybridState(fourQubits, 4); + std::vector ctrls = {v1, v2}; + hState.addDoubleValue(v1, 1.2); + hState.addDoubleValue(v2, 3.2); + 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("doubleValue0 = 3.20"), + testing::HasSubstr("doubleValue1 = 3.20"))); + EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("doubleValue0 = 1.20"), + testing::HasSubstr("doubleValue1 = 1.20"))); +} + +TEST_F(HybridStateTest, handleErrorIfTooManyAmplitudesAreNonzero) { auto hState = HybridState(fourQubits, 2); hState.propagateGate(hOp.getOperation(), vectorThree); hState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); From 41373e0ffbb45e169eea84e8862d1c1efd1f0dd3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:23:43 +0000 Subject: [PATCH 207/235] =?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/test_hybridState.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 a086435b5f..a0d2f96307 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -249,10 +249,12 @@ TEST_F(HybridStateTest, ApplyTwoTimesClassicalDoubleControlledGate) { 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("doubleValue0 = 3.20"), - testing::HasSubstr("doubleValue1 = 3.20"))); - EXPECT_THAT(resStr, testing::AnyOf(testing::HasSubstr("doubleValue0 = 1.20"), - testing::HasSubstr("doubleValue1 = 1.20"))); + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = 3.20"), + testing::HasSubstr("doubleValue1 = 3.20"))); + EXPECT_THAT(resStr, + testing::AnyOf(testing::HasSubstr("doubleValue0 = 1.20"), + testing::HasSubstr("doubleValue1 = 1.20"))); } TEST_F(HybridStateTest, handleErrorIfTooManyAmplitudesAreNonzero) { From f963806802f22c7825d86c8f88af2d357b0cbe21 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 19:27:40 +0200 Subject: [PATCH 208/235] :white_check_mark: Changed test for handling of classical operations --- .../test_qco_constant_propagation.cpp | 30 ++----------------- 1 file changed, 3 insertions(+), 27 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 1a7d9c2a89..545a050020 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 @@ -399,7 +399,7 @@ TEST_F(QCOConstantPropagationTest, testRemoveClassicalConditionals) { /** * @brief Test: This test checks if classical integer and float operations are - * applied correctly. + * applied without error. */ TEST_F(QCOConstantPropagationTest, testApplyClassicalOperations) { auto q = programBuilder.allocQubitRegister(1); @@ -410,35 +410,11 @@ TEST_F(QCOConstantPropagationTest, testApplyClassicalOperations) { .getResult(); const auto intTwo = programBuilder.intConstant(2); const auto intTen = programBuilder.intConstant(10); - auto intRes = - arith::AddIOp::create(programBuilder, intTwo.getType(), intTwo, intTen) - .getResult(); - q[0] = programBuilder.h(q[0]); - programBuilder.qcoIf(intRes, {q[0]}, [&](const ValueRange args) { - const auto q0 = programBuilder.t(args[0]); - return SmallVector{q0}; - }); - module = programBuilder.finalize(); - - auto qRef = referenceBuilder.allocQubitRegister(1); - const auto floatSevenRef = referenceBuilder.floatConstant(7.2); - const auto floatZeroRef = referenceBuilder.floatConstant(0.0); - arith::MulFOp::create(referenceBuilder, floatSevenRef.getType(), - floatSevenRef, floatZeroRef) - .getResult(); - const auto intTwoRef = referenceBuilder.intConstant(2); - const auto intTenRef = referenceBuilder.intConstant(10); - arith::AddIOp::create(referenceBuilder, intTwoRef.getType(), intTwoRef, - intTenRef) + arith::AddIOp::create(programBuilder, intTwo.getType(), intTwo, intTen) .getResult(); - qRef[0] = referenceBuilder.h(qRef[0]); - referenceBuilder.t(qRef[0]); - reference = referenceBuilder.finalize(); + module = programBuilder.finalize(); ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); - - EXPECT_TRUE( - areModulesEquivalentWithPermutations(module.get(), reference.get())); } /** From d5b5876aee8e41aa3610addab93e903562c7044a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 20:18:49 +0200 Subject: [PATCH 209/235] :boom: Removed empty TearDown() --- .../Optimizations/ConstantPropagation/test_hybridState.cpp | 1 - .../Optimizations/ConstantPropagation/test_unionTable.cpp | 4 ---- 2 files changed, 5 deletions(-) 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 a0d2f96307..cf68008efc 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -82,7 +82,6 @@ class HybridStateTest : public testing::Test { v4 = q[3]; } - void TearDown() override {} }; TEST_F(HybridStateTest, ApplyHGate) { 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 b874b5b822..e797851390 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -123,7 +123,6 @@ class UnionTableTest : public testing::Test { ut.propagateQubitAlloc(v3); } - void TearDown() override {} }; TEST_F(UnionTableTest, ApplyHGate) { @@ -436,7 +435,6 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { programBuilder.getLoc(), iAttr); } - void TearDown() override {} }; TEST_F(UnionTableWithoutSetupAllocationsTest, propagateQubitAlloc) { @@ -680,7 +678,6 @@ class UnionTablePropertiesTest : public testing::Test { ut.propagateIntAlloc(i0, 0); } - void TearDown() override {} }; TEST_F(UnionTablePropertiesTest, alwaysZeroOneAreFalse) { @@ -1250,7 +1247,6 @@ class UnionTableSuperfluousTest : public testing::Test { ut.propagateMeasurement(v15, v16, i3); // classical value 3 = true } - void TearDown() override {} }; TEST_F(UnionTableSuperfluousTest, oneSuperfluousEach) { From b897e37d02c2456b2d981d5d1f3c10b1dd23b7b9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 20:23:39 +0200 Subject: [PATCH 210/235] :construction: Ctrl not superfluous if it is TOP --- .../Optimizations/ConstantPropagation/UnionTable.cpp | 4 ++++ .../ConstantPropagation/test_unionTable.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index ee52c99954..911851df78 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -572,6 +572,10 @@ UnionTable::getSuperfluousControls(const std::span qubitCtrls, const auto qIndex = qubitsToGlobalIndices.at(qCtrl); bool alwaysOne = true; bool alwaysZero = true; + if (valuesToEntries.at(qCtrl)->top) { + alwaysOne = false; + alwaysZero = false; + } for (const auto& hs : valuesToEntries.at(qCtrl)->states) { if (hs.isHybridStateTop()) { alwaysOne = false; 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 e797851390..6a7c9ec44b 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -1101,6 +1101,18 @@ TEST_F(SmallUnionTableTest, applyGatesOnPartiallyTopQState) { testing::HasSubstr("Qubits: 321, HybridStates: {TOP}")); } +TEST_F(SmallUnionTableTest, nonSuperfluousBecauseStatesAreTop) { + ut.propagateGate(hOp, q2, q4); + ut.propagateGate(hOp, q3, q5); + ut.propagateGate(xOp, q4, q6, q5, q7); // Qubit 2 and 3 enter TOP + std::vector quantumCtrl = {v7}; + auto [completelySuperfluous, superfluousQubits, superfluousClassicalValues] = + ut.getSuperfluousControls(quantumCtrl); + + EXPECT_FALSE(completelySuperfluous); + EXPECT_TRUE(superfluousQubits.empty()); +} + class UnionTableSuperfluousTest : public testing::Test { protected: mlir::MLIRContext context; From 18f2b59eae3c327b65885d3c53b462fbeffef4bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:24:36 +0000 Subject: [PATCH 211/235] =?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 --- .../Optimizations/ConstantPropagation/test_hybridState.cpp | 1 - .../Optimizations/ConstantPropagation/test_unionTable.cpp | 4 ---- 2 files changed, 5 deletions(-) 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 cf68008efc..3545e11850 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -81,7 +81,6 @@ class HybridStateTest : public testing::Test { v3 = q[2]; v4 = q[3]; } - }; TEST_F(HybridStateTest, ApplyHGate) { 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 6a7c9ec44b..77973d3aca 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -122,7 +122,6 @@ class UnionTableTest : public testing::Test { ut.propagateQubitAlloc(v2); ut.propagateQubitAlloc(v3); } - }; TEST_F(UnionTableTest, ApplyHGate) { @@ -434,7 +433,6 @@ class UnionTableWithoutSetupAllocationsTest : public testing::Test { i1 = mlir::arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); } - }; TEST_F(UnionTableWithoutSetupAllocationsTest, propagateQubitAlloc) { @@ -677,7 +675,6 @@ class UnionTablePropertiesTest : public testing::Test { ut.propagateQubitAlloc(v2); ut.propagateIntAlloc(i0, 0); } - }; TEST_F(UnionTablePropertiesTest, alwaysZeroOneAreFalse) { @@ -1258,7 +1255,6 @@ class UnionTableSuperfluousTest : public testing::Test { ut.propagateGate(hOp, q14, q15); ut.propagateMeasurement(v15, v16, i3); // classical value 3 = true } - }; TEST_F(UnionTableSuperfluousTest, oneSuperfluousEach) { From f2049d46c5ac00484300549ad244739ae227394e Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Wed, 19 Aug 2026 20:47:09 +0200 Subject: [PATCH 212/235] :construction: Continue to propagate ctrls if TOP is reached --- .../Optimizations/ConstantPropagation/UnionTable.cpp | 2 ++ .../Optimizations/ConstantPropagation/test_unionTable.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 911851df78..b7356e6065 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -136,10 +136,12 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, const auto becameTop = unifyEntries(participatingEntries); if (becameTop) { replaceValuesGlobally(targets, newQuantumTargets); + replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); return; } if (valuesToEntries.at(targets[0])->top) { replaceValuesGlobally(targets, newQuantumTargets); + replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); return; } 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 77973d3aca..77d919a69a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -1023,6 +1023,7 @@ class SmallUnionTableTest : public testing::Test { mlir::Value v6; mlir::Value v7; mlir::Value v8; + mlir::Value v9; std::vector q0; std::vector q1; @@ -1033,6 +1034,7 @@ class SmallUnionTableTest : public testing::Test { std::vector q6; std::vector q7; std::vector q8; + std::vector q9; SmallUnionTableTest() : programBuilder(&context) {} @@ -1045,7 +1047,7 @@ class SmallUnionTableTest : public testing::Test { programBuilder.initialize(); - auto q = programBuilder.allocQubitRegister(9); + 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(), @@ -1060,6 +1062,7 @@ class SmallUnionTableTest : public testing::Test { v6 = q[6]; v7 = q[7]; v8 = q[8]; + v9 = q[9]; q0 = {v0}; q1 = {v1}; @@ -1070,6 +1073,7 @@ class SmallUnionTableTest : public testing::Test { q6 = {v6}; q7 = {v7}; q8 = {v8}; + q9 = {v9}; ut.propagateQubitAlloc(v0); ut.propagateQubitAlloc(v1); @@ -1092,7 +1096,7 @@ TEST_F(SmallUnionTableTest, applyGatesOnPartiallyTopQState) { ut.propagateGate(hOp, q2, q4); ut.propagateGate(hOp, q3, q5); ut.propagateGate(xOp, q4, q6, q5, q7); // Qubit 2 and 3 enter TOP - ut.propagateGate(xOp, q1, q8, q6); + ut.propagateGate(xOp, q1, q8, q6, q9); EXPECT_THAT(ut.toString(), testing::HasSubstr("Qubits: 321, HybridStates: {TOP}")); From d49b6df96e10de819fa87e269f49405f5924ae80 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 07:42:56 +0200 Subject: [PATCH 213/235] :rotating_light: Removed unused references --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f69d8b723..8d43119004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -897,7 +897,6 @@ for previous changelogs._ [#2116]: https://github.com/munich-quantum-toolkit/core/pull/2116 [#2115]: https://github.com/munich-quantum-toolkit/core/pull/2115 [#2114]: https://github.com/munich-quantum-toolkit/core/pull/2114 -[#2112]: https://github.com/munich-quantum-toolkit/core/pull/2112 [#2111]: https://github.com/munich-quantum-toolkit/core/pull/2111 [#2108]: https://github.com/munich-quantum-toolkit/core/pull/2108 [#2106]: https://github.com/munich-quantum-toolkit/core/pull/2106 @@ -952,7 +951,6 @@ for previous changelogs._ [#1978]: https://github.com/munich-quantum-toolkit/core/pull/1978 [#1976]: https://github.com/munich-quantum-toolkit/core/pull/1976 [#1975]: https://github.com/munich-quantum-toolkit/core/pull/1975 -[#1974]: https://github.com/munich-quantum-toolkit/core/pull/1974 [#1973]: https://github.com/munich-quantum-toolkit/core/pull/1973 [#1972]: https://github.com/munich-quantum-toolkit/core/pull/1972 [#1967]: https://github.com/munich-quantum-toolkit/core/pull/1967 From 3e7f235e676922788ae2e8ff4f6f4feeb342171f Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 11:07:07 +0200 Subject: [PATCH 214/235] :construction: Adapted ClassicalArithOperation.h to review --- .../ConstantPropagation/ClassicalArithOperation.h | 7 +++---- 1 file changed, 3 insertions(+), 4 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 a54c658345..4e4db76275 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/ClassicalArithOperation.h @@ -105,12 +105,12 @@ inline int64_t getArithIntegerOpResult(mlir::Operation* operation, return result ? int64_t{1} : int64_t{0}; }) .Case([&](auto) { - if (a.isZero()) { + if (b.isZero()) { llvm::report_fatal_error( "Handling of division by zero in mlir::arith::DivSIOp is not " "supported by constant propagation."); } - if (a.isAllOnes() && b.isMinSignedValue()) { + if (b.isAllOnes() && a.isMinSignedValue()) { llvm::report_fatal_error( "Handling of INT_MIN / -1 in mlir::arith::DivSIOp is not " "supported by constant propagation."); @@ -217,7 +217,7 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, }) .Case([&](auto) { result = lhs; - auto status = result.remainder(rhs); + const auto status = result.mod(rhs); return (status & llvm::APFloat::opInvalidOp) == 0; }) .Case([&](auto) { @@ -231,7 +231,6 @@ inline double getArithDoubleOpResult(mlir::Operation* operation, "mlir::qco::classicalarithoperation"); } - llvm::APFloat resultInDouble = result; bool loseInfoResult = false; result.convert(llvm::APFloat::IEEEdouble(), rm, &loseInfoResult); if (loseInfoResult) { From d9be1d19ab369cc587a540ac8deb2e4a1854cd6d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 11:21:16 +0200 Subject: [PATCH 215/235] :construction: Put constants into namespace --- .../Transforms/Optimizations/ConstantPropagation/GateToMap.h | 2 ++ 1 file changed, 2 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 9d0df93903..e650591116 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -27,6 +27,7 @@ * multiplied with to get the new amplitudes after a gate application. */ +namespace mlir::qco { using Complex = std::complex; using ResultMap = @@ -224,3 +225,4 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { llvm::report_fatal_error("Unsupported gate in mlir::qco::gatetomap"); }); } +} // namespace mlir::qco \ No newline at end of file From 9bebec928f8611b654edb8bb3465bf81638e7699 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 11:21:30 +0200 Subject: [PATCH 216/235] :construction: Preventing race condition --- .../Optimizations/ConstantPropagation/UnionTable.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 58680a2f91..de5e795d9f 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_UNIONTABLE_H -#define MQT_CORE_UNIONTABLE_H +#pragma once #include "HybridState.hpp" #include @@ -17,6 +16,7 @@ #include #include +#include #include #include #include @@ -61,8 +61,8 @@ struct UnionTableEntry { private: static std::uint64_t nextId() { - static unsigned int counter = 0; - return ++counter; + static std::atomic counter{0}; + return counter.fetch_add(1, std::memory_order_relaxed) + 1; } }; @@ -542,5 +542,3 @@ class UnionTable { std::span classicalNegative) const; }; } // namespace mlir::qco - -#endif // MQT_CORE_UNIONTABLE_H From 59d6ae7b1700376609b813eb12df0b54ffbb9231 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 11:23:34 +0200 Subject: [PATCH 217/235] :pencil2: Fixed typos --- mlir/include/mlir/Dialect/QCO/Transforms/Passes.td | 8 ++++---- .../ConstantPropagation/test_quantumState.cpp | 2 +- .../Optimizations/test_qco_constant_propagation.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 5b8491e421..6b28edfb7e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -191,7 +191,7 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { "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 + This pass applies quantum constant propagation. This optimization routine 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. Additionally, quantum controls can be replaced by equivalent classical control. To do this most efficiently, the measurements are moved @@ -206,9 +206,9 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { 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 + If a controlling qubit or classical value is always true, the control is removed. Classical values are considered true if they are not zero. - If a controlling qubit or classical value is alaways false, the complete gate is removed. + If a controlling qubit or classical value is always 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 @@ -231,7 +231,7 @@ def ConstantPropagation : Pass<"constant-propagation", "mlir::ModuleOp"> { let options = [Option<"maximumNonzeroAmplitudes", "maximum-nonzero-amplitudes", "std::size_t", "4", "The maximum number of non-zero amplitudes in the " - "tracted quantum states before reaching top.">, + "tracked quantum states before reaching top.">, Option<"maximumHybridStates", "maximum-hybrid-states", "std::size_t", "4", "The maximum number of hybrid states which have a " 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 4e53d2fa2f..1f7b63861a 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_quantumState.cpp @@ -262,7 +262,7 @@ TEST_F(QuantumStateTest, applyControlledTwoQubitGate) { "|0000> -> 0.50, |0100> -> 0.50, |1000> -> 0.50, |1010> -> 0.50")); } -TEST_F(QuantumStateTest, propagateGateCheckErrorIfTwoManyAmplitudesAreNonzero) { +TEST_F(QuantumStateTest, propagateGateCheckErrorIfTooManyAmplitudesAreNonzero) { auto qState = QuantumState(fourQubits, 2); qState.propagateGate(hOp.getOperation(), vectorThree); qState.propagateGate(xOp.getOperation(), vectorTwo, vectorThree); 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 545a050020..5d8c569b4e 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 @@ -129,8 +129,8 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { /** * @brief Test: This test checks that CNOTs are not changed if a reset is - * between to Hadamards, i.e. the qubits are in a superposition after the second - * Hadamard. + * between two Hadamards, i.e. the qubits are in a superposition after the + * second Hadamard. */ TEST_F(QCOConstantPropagationTest, testApplyReset) { auto q = programBuilder.allocQubitRegister(2); From 63526b792c48ad4b4f328efd900e73940d5abddd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 09:24:52 +0000 Subject: [PATCH 218/235] =?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 --- .../Transforms/Optimizations/ConstantPropagation/GateToMap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e650591116..88360cd29a 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h @@ -225,4 +225,4 @@ getQubitMappingOfGates(mlir::Operation* gate, const std::span& params) { llvm::report_fatal_error("Unsupported gate in mlir::qco::gatetomap"); }); } -} // namespace mlir::qco \ No newline at end of file +} // namespace mlir::qco From ae8bb0c0651623f75d6fd977fc7a5aadf0b93fb9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 13:05:50 +0200 Subject: [PATCH 219/235] :construction: Fixed return type of id --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 de5e795d9f..86cf2494dc 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -60,7 +60,7 @@ struct UnionTableEntry { UnionTableEntry() : index(nextId()) {} private: - static std::uint64_t nextId() { + static unsigned int nextId() { static std::atomic counter{0}; return counter.fetch_add(1, std::memory_order_relaxed) + 1; } From 0132bfbee1484e565fc516f7a26a60f6dd88ae75 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 14:43:44 +0200 Subject: [PATCH 220/235] :construction: Consider GPhase and classical yields --- .../ConstantPropagation/QuantumState.hpp | 11 ++++ .../Optimizations/ConstantPropagation.cpp | 25 ++++++-- .../ConstantPropagation/QuantumState.cpp | 19 ++++++ .../ConstantPropagation/UnionTable.cpp | 6 +- .../test_qco_constant_propagation.cpp | 62 +++++++++++++++++++ 5 files changed, 115 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 89e895195c..d6f0d28d4e 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp @@ -263,6 +263,17 @@ class QuantumState { */ void changeGlobalIndex(unsigned int target, unsigned int newIndex); + /** + * @brief This method applies a global phase gate to the qubits. + * + * This method changes the amplitudes of a QuantumState according to the + * applied global phase, that can become a relative phase if it is controlled. + * + * @param ctrls A span of the global indices of the ctrl qubits. + * @param phase The phase of the gate. + */ + void propagateGPhaseGate(std::span ctrls, double phase); + /** * @brief This method applies a gate to the qubits. * diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index cddf3ac61f..088d720c5d 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -516,12 +516,17 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, 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 (mlir::isa(input[i].getType())) { + std::ranges::replace(thenArgs, input[i], output[i]); + } } }); } else { - const auto thenYieldOperands = op->thenYield()->getOperands(); - thenArgs = {thenYieldOperands.begin(), thenYieldOperands.end()}; + for (Value original : op->thenYield()->getOperands()) { + if (mlir::isa(original.getType())) { + thenArgs.push_back(original); + } + } } if (!elseEmpty) { @@ -546,12 +551,17 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, 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]); + if (mlir::isa(input[i].getType())) { + std::ranges::replace(elseArgs, input[i], output[i]); + } } }); } else { - const auto elseYieldOperands = op->elseYield()->getOperands(); - elseArgs = {elseYieldOperands.begin(), elseYieldOperands.end()}; + for (Value original : op->elseYield()->getOperands()) { + if (mlir::isa(original.getType())) { + elseArgs.push_back(original); + } + } } const auto resultQubits = op->getResults(); @@ -809,6 +819,9 @@ handleUncontrolledUnitary(UnionTable* ut, UnitaryOpInterface* op, const std::span negClassicalCtrls, PatternRewriter& rewriter, std::span& worklist) { + if (isa(op)) { + return WalkResult::advance(); + } const auto targets = op->getInputTargets(); std::vector targetVecs = {targets.begin(), targets.end()}; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 238f2f5d81..28edfc6e22 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -205,10 +206,28 @@ void QuantumState::changeGlobalIndex(const unsigned int target, globalToLocalQubitNumber[newIndex] = localIndex; } +void QuantumState::propagateGPhaseGate(const std::span ctrls, + const double phase) { + unsigned int ctrlMask = 0; + for (unsigned int const ctrl : ctrls) { + ctrlMask |= 1U << globalToLocalQubitNumber.at(ctrl); + } + + for (auto& [key, value] : amplitudeMap) { + if ((ctrlMask & key) != ctrlMask) { + value *= exp(Complex(0, phase)); + } + } +} + void QuantumState::propagateGate(Operation* gate, const std::span targets, const std::span ctrls, const std::span params) { + if (isa(gate)) { + propagateGPhaseGate(ctrls, *params.begin()); + return; + } const auto gateMapping = getQubitMappingOfGates(gate, params); unsigned int ctrlMask = 0; diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index b7356e6065..fc03549d77 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -139,7 +139,9 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); return; } - if (valuesToEntries.at(targets[0])->top) { + // for GPhase, targets and quantum controls do not have to be filled + const auto participatingValue = !targets.empty() ? targets[0] : ctrlsQuantum[0]; + if (valuesToEntries.at(participatingValue)->top) { replaceValuesGlobally(targets, newQuantumTargets); replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); return; @@ -154,7 +156,7 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, ctrlQubitIndices.push_back(qubitsToGlobalIndices.at(q)); } - const auto ute = valuesToEntries.at(*targets.begin()); + const auto ute = valuesToEntries.at(participatingValue); for (auto hs : ute->states) { hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, posCtrlsClassical, negCtrlsClassical, params); 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 5d8c569b4e..97a24d29b5 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 @@ -127,6 +127,68 @@ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks that an uncontrolled GPhase gate does not have + * an effect. + */ +TEST_F(QCOConstantPropagationTest, testApplyUncontrolledGPhase) { + auto q = programBuilder.allocQubitRegister(2); + const auto iAttr = programBuilder.getF64FloatAttr(1.0); + Value i0 = + arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); + q[0] = programBuilder.h(q[0]); + programBuilder.gphase(i0); + q[0] = programBuilder.h(q[0]); + programBuilder.cx(q[0], q[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + const auto iAttrRef = referenceBuilder.getF64FloatAttr(1.0); + Value i0Ref = arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getLoc(), iAttrRef); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.gphase(i0Ref); + qRef[0] = referenceBuilder.h(qRef[0]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: This test checks that a controlled GPhase gate has an effect. + */ +TEST_F(QCOConstantPropagationTest, testApplyControlledGPhase) { + auto q = programBuilder.allocQubitRegister(2); + const auto iAttr = programBuilder.getF64FloatAttr(1.0); + Value i0 = + arith::ConstantOp::create(programBuilder, programBuilder.getLoc(), iAttr); + q[0] = programBuilder.h(q[0]); + q[1] = programBuilder.h(q[1]); + q[0] = programBuilder.cgphase(i0, q[0]); + q[0] = programBuilder.h(q[0]); + programBuilder.cx(q[0], q[1]); + module = programBuilder.finalize(); + + auto qRef = referenceBuilder.allocQubitRegister(2); + const auto iAttrRef = referenceBuilder.getF64FloatAttr(1.0); + Value i0Ref = arith::ConstantOp::create(referenceBuilder, + referenceBuilder.getLoc(), iAttrRef); + qRef[0] = referenceBuilder.h(qRef[0]); + qRef[1] = referenceBuilder.h(qRef[1]); + qRef[0] = referenceBuilder.cgphase(i0Ref, qRef[0]); + qRef[0] = referenceBuilder.h(qRef[0]); + referenceBuilder.cx(qRef[0], qRef[1]); + reference = referenceBuilder.finalize(); + + ASSERT_TRUE(runConstantPropagationPass(module.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + /** * @brief Test: This test checks that CNOTs are not changed if a reset is * between two Hadamards, i.e. the qubits are in a superposition after the From dd44774c251daea817585700c586aa8f2988c9ff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:44:26 +0000 Subject: [PATCH 221/235] =?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 --- .../Optimizations/ConstantPropagation/UnionTable.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index fc03549d77..1d2c0d4852 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -140,7 +140,8 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, return; } // for GPhase, targets and quantum controls do not have to be filled - const auto participatingValue = !targets.empty() ? targets[0] : ctrlsQuantum[0]; + const auto participatingValue = + !targets.empty() ? targets[0] : ctrlsQuantum[0]; if (valuesToEntries.at(participatingValue)->top) { replaceValuesGlobally(targets, newQuantumTargets); replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); From b50e7d5505c07304b8b03b53fe521002ab526e1b Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 14:44:54 +0200 Subject: [PATCH 222/235] :construction: Changed guard to pragma once --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 28edfc6e22..3fcaa719ff 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -8,8 +8,7 @@ * Licensed under the MIT License */ -#ifndef MQT_CORE_QUANTUMSTATE -#define MQT_CORE_QUANTUMSTATE +#pragma once #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" @@ -307,5 +306,3 @@ bool QuantumState::hasAlwaysZeroAmplitude( } } // namespace mlir::qco - -#endif // MQT_CORE_QUANTUMSTATE From 7fc46c6d347294a8ebabecf985488a2dbdf344ca Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:00:25 +0200 Subject: [PATCH 223/235] :construction: Prevents copying of hybridState --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index fc03549d77..dcb7436512 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -157,7 +157,7 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, } const auto ute = valuesToEntries.at(participatingValue); - for (auto hs : ute->states) { + for (auto& hs : ute->states) { hs.propagateGate(gate, targetQubitIndices, ctrlQubitIndices, posCtrlsClassical, negCtrlsClassical, params); if (hs.isHybridStateTop()) { From b3e86f1439343205d974d5bdc2a6fbdd5b236ce8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:01:12 +0200 Subject: [PATCH 224/235] :construction: Fixed value shadowing --- .../Optimizations/ConstantPropagation/test_hybridState.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3545e11850..717ec1c8f1 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_hybridState.cpp @@ -161,7 +161,7 @@ TEST_F(HybridStateTest, ApplyClassicalControlledGateThatsTrue) { TEST_F(HybridStateTest, ApplyNegClassicalControlledGateThatsFalse) { auto hState = HybridState(fourQubits, 4); - constexpr auto v1 = mlir::Value(); + v1 = mlir::Value(); std::vector ctrl = {v1}; hState.addIntegerValue(v1, 0); hState.propagateGate(xOp.getOperation(), vectorThree); From 8590c36ac5b5ab2a6ef0c5f9fc9067ead1603084 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:02:46 +0200 Subject: [PATCH 225/235] :construction: Verify module before applying pass, fixed docstring --- .../Optimizations/test_qco_constant_propagation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 97a24d29b5..0121604ced 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 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ class QCOConstantPropagationTest : public testing::Test { * @brief Adds the ConstantPropagation pass to the pass manager and runs it. */ static LogicalResult runConstantPropagationPass(ModuleOp module) { + verify(module); PassManager pm(module.getContext()); pm.addPass(createConstantPropagation()); return pm.run(module); @@ -107,8 +109,8 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { } /** - * @brief Test: This test checks that CNOTs are not changed if the target is not - * in |0> or |1>. + * @brief Test: This test checks that CNOTs are not changed if the control is + * not in |0> or |1>. */ TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { auto q = programBuilder.allocQubitRegister(2); From 6c383f1bc99d1f78fdaa823b26cd08edab0f2e81 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:03:46 +0200 Subject: [PATCH 226/235] :construction: Correct test name --- .../Transforms/Optimizations/test_qco_constant_propagation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0121604ced..cf85d12fd9 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 @@ -112,7 +112,7 @@ TEST_F(QCOConstantPropagationTest, reducePosCtrls) { * @brief Test: This test checks that CNOTs are not changed if the control is * not in |0> or |1>. */ -TEST_F(QCOConstantPropagationTest, testDontRemoveIfTargetInSuperposition) { +TEST_F(QCOConstantPropagationTest, testDontRemoveIfControlInSuperposition) { auto q = programBuilder.allocQubitRegister(2); q[0] = programBuilder.h(q[0]); programBuilder.cx(q[0], q[1]); From e4522278dcda39bc395a6333e4144fc73ece9b64 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:06:40 +0200 Subject: [PATCH 227/235] :construction: Changed counter type to unsigned int --- .../Transforms/Optimizations/ConstantPropagation/UnionTable.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 86cf2494dc..eff338d7d2 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.hpp @@ -61,7 +61,7 @@ struct UnionTableEntry { private: static unsigned int nextId() { - static std::atomic counter{0}; + static std::atomic counter{0}; return counter.fetch_add(1, std::memory_order_relaxed) + 1; } }; From c7e1bc64fe23b35edc963eced32cbfe40dde2c10 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:39:55 +0200 Subject: [PATCH 228/235] :boom: Removed unnecessary guard --- .../Optimizations/ConstantPropagation/QuantumState.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp index 3fcaa719ff..67b3cec4a6 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#pragma once #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/QuantumState.hpp" #include "mlir/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/GateToMap.h" From aa880eea61bacb2d420c1a39c80ac0d9ea036fc9 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:40:16 +0200 Subject: [PATCH 229/235] :construction: Use linearResults for results of ifOp --- .../Optimizations/ConstantPropagation.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 088d720c5d..73bb7e6b72 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -522,11 +522,8 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } }); } else { - for (Value original : op->thenYield()->getOperands()) { - if (mlir::isa(original.getType())) { - thenArgs.push_back(original); - } - } + const auto linearThenResults = op->getLinearResults(); + thenArgs = {linearThenResults.begin(), linearThenResults.end()}; } if (!elseEmpty) { @@ -557,11 +554,8 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } }); } else { - for (Value original : op->elseYield()->getOperands()) { - if (mlir::isa(original.getType())) { - elseArgs.push_back(original); - } - } + const auto linearElseResults = op->getLinearResults(); + elseArgs = {linearElseResults.begin(), linearElseResults.end()}; } const auto resultQubits = op->getResults(); From 5be615e4f251f3a441ca810022e53b45d8ebbc9a Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 15:52:07 +0200 Subject: [PATCH 230/235] :construction: Prevent to apply only classically controlled gphase --- .../Optimizations/ConstantPropagation/UnionTable.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp index 3d42b3ab24..8c3a927148 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/UnionTable.cpp @@ -121,6 +121,11 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, const std::span posCtrlsClassical, const std::span negCtrlsClassical, const std::span params) { + // a global phase gate could only depend on classical control, but in this + // case only a global phase is added + if (targets.empty() && ctrlsQuantum.empty()) { + return; + } const std::set participatingEntries = collectParticipatingEntries(targets, ctrlsQuantum, posCtrlsClassical, negCtrlsClassical, params); @@ -139,7 +144,7 @@ void UnionTable::propagateGate(Operation* gate, const std::span targets, replaceValuesGlobally(ctrlsQuantum, newCtrlsQuantum); return; } - // for GPhase, targets and quantum controls do not have to be filled + // for GPhase, targets are not filled const auto participatingValue = !targets.empty() ? targets[0] : ctrlsQuantum[0]; if (valuesToEntries.at(participatingValue)->top) { From 9c73508098d2aae5f28712de2b6bc03c7e03f3f4 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 16:05:04 +0200 Subject: [PATCH 231/235] =?UTF-8?q?=E2=9C=85=20Added=20test=20for=20classi?= =?UTF-8?q?cal=20controlled=20gphase=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConstantPropagation/test_unionTable.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 77d919a69a..d70770b841 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -37,6 +37,7 @@ class UnionTableTest : public testing::Test { HOp hOp; XOp xOp; SWAPOp swapOp; + GPhaseOp gPhaseOp; mlir::Value v0; mlir::Value v1; @@ -81,6 +82,7 @@ class UnionTableTest : public testing::Test { q[0]); xOp = XOp::create(programBuilder, programBuilder.getLoc(), q[0].getType(), q[0]); + gPhaseOp = GPhaseOp::create(programBuilder, programBuilder.getLoc(), 1.0); swapOp = SWAPOp::create(programBuilder, programBuilder.getLoc(), {q[0].getType(), q[1].getType()}, {q[0], q[1]}); @@ -186,6 +188,20 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGateThatsTrue) { "-> 0.71, |10> -> 0.71}: integerValue0 = 1; p = 1.00;}")); } +TEST_F(UnionTableTest, ApplyClassicalControlledGPhase) { + std::vector classicalControl0 = {i0}; + std::vector classicalControl1 = {i1}; + ut.propagateIntAlloc(i0, 1); + ut.propagateIntAlloc(i1, 0); + ut.propagateGate(hOp, q1, q5); + ut.propagateGate(gPhaseOp, {}, {}, {}, {}, classicalControl0); + ut.propagateGate(gPhaseOp, {}, {}, {}, {}, {}, classicalControl1); + + EXPECT_THAT(ut.toString(), + testing::HasSubstr("Qubits: 1, HybridStates: {{|0> " + "-> 0.71, |1> -> 0.71}")); +} + TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { std::vector classicalControl = {i0}; ut.propagateIntAlloc(i0, 1); From 823dcb0f6c19457252e773a2d2b78f4d378c5ab8 Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 16:46:28 +0200 Subject: [PATCH 232/235] :white_check_mark: Check if whole union table remains unchanged by global phase gate --- .../Optimizations/ConstantPropagation/test_unionTable.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 d70770b841..23e09cc040 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/ConstantPropagation/test_unionTable.cpp @@ -194,12 +194,13 @@ TEST_F(UnionTableTest, ApplyClassicalControlledGPhase) { ut.propagateIntAlloc(i0, 1); ut.propagateIntAlloc(i1, 0); ut.propagateGate(hOp, q1, q5); + + const auto stateBeforeGPhase = ut.toString(); + ut.propagateGate(gPhaseOp, {}, {}, {}, {}, classicalControl0); ut.propagateGate(gPhaseOp, {}, {}, {}, {}, {}, classicalControl1); - EXPECT_THAT(ut.toString(), - testing::HasSubstr("Qubits: 1, HybridStates: {{|0> " - "-> 0.71, |1> -> 0.71}")); + EXPECT_EQ(stateBeforeGPhase, ut.toString()); } TEST_F(UnionTableTest, ApplyNegClassicalControlledGateThatsTrue) { From 9cff859d9a692ac1d9c2fca1c6c9df3e1c8feb1d Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 17:04:54 +0200 Subject: [PATCH 233/235] :construction: Make sure only linear result types are used from then and else yield --- .../Transforms/Optimizations/ConstantPropagation.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 73bb7e6b72..b613a24770 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -522,8 +522,9 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } }); } else { - const auto linearThenResults = op->getLinearResults(); - thenArgs = {linearThenResults.begin(), linearThenResults.end()}; + const auto linearThenValues = op->thenYield().getTargets().drop_front( + op->getClassicalResults().size()); + thenArgs = {linearThenValues.begin(), linearThenValues.end()}; } if (!elseEmpty) { @@ -554,8 +555,9 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, } }); } else { - const auto linearElseResults = op->getLinearResults(); - elseArgs = {linearElseResults.begin(), linearElseResults.end()}; + const auto linearElseValues = op->elseYield().getTargets().drop_front( + op->getClassicalResults().size()); + elseArgs = {linearElseValues.begin(), linearElseValues.end()}; } const auto resultQubits = op->getResults(); From 91d5b85e6301146c64f8591a4915648ddf1511cc Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 17:39:15 +0200 Subject: [PATCH 234/235] :construction: Conder order of yielded values. --- .../Optimizations/ConstantPropagation.cpp | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index b613a24770..7e9a05680f 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -512,20 +512,10 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, if (resThen.failed()) { return WalkResult::interrupt(); } - 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) { - if (mlir::isa(input[i].getType())) { - std::ranges::replace(thenArgs, input[i], output[i]); - } - } - }); - } else { - const auto linearThenValues = op->thenYield().getTargets().drop_front( - op->getClassicalResults().size()); - thenArgs = {linearThenValues.begin(), linearThenValues.end()}; } + const auto linearThenValues = + op->thenYield().getTargets().drop_front(op->getClassicalResults().size()); + thenArgs = {linearThenValues.begin(), linearThenValues.end()}; if (!elseEmpty) { for (const Value arg : elseBlock->getArguments()) { @@ -543,22 +533,10 @@ static 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) { - if (mlir::isa(input[i].getType())) { - std::ranges::replace(elseArgs, input[i], output[i]); - } - } - }); - } else { - const auto linearElseValues = op->elseYield().getTargets().drop_front( - op->getClassicalResults().size()); - elseArgs = {linearElseValues.begin(), linearElseValues.end()}; } + const auto linearElseValues = + op->elseYield().getTargets().drop_front(op->getClassicalResults().size()); + elseArgs = {linearElseValues.begin(), linearElseValues.end()}; const auto resultQubits = op->getResults(); std::vector results = {resultQubits.begin(), resultQubits.end()}; From fc833274e834aca69e8239726e89f15f812f45db Mon Sep 17 00:00:00 2001 From: "Remme, Lian (lirem101)" Date: Thu, 20 Aug 2026 19:01:23 +0200 Subject: [PATCH 235/235] :construction: Added test for swapped yielded values --- .../Optimizations/ConstantPropagation.cpp | 17 +++++-- .../test_qco_constant_propagation.cpp | 49 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp index 7e9a05680f..81b6e7f570 100644 --- a/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/ConstantPropagation.cpp @@ -509,13 +509,24 @@ static WalkResult handleIfOp(UnionTable* ut, IfOp* op, const auto resThen = iterateThroughWorklist( rewriter, ut, wl, newPosClassicalCtrls, negClassicalCtrls); + 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) { + if (mlir::isa(input[i].getType())) { + std::ranges::replace(thenArgs, input[i], output[i]); + } + } + }); + if (resThen.failed()) { return WalkResult::interrupt(); } + } else { + const auto linearThenValues = op->thenYield().getTargets().drop_front( + op->getClassicalResults().size()); + thenArgs = {linearThenValues.begin(), linearThenValues.end()}; } - const auto linearThenValues = - op->thenYield().getTargets().drop_front(op->getClassicalResults().size()); - thenArgs = {linearThenValues.begin(), linearThenValues.end()}; if (!elseEmpty) { for (const Value arg : elseBlock->getArguments()) { 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 cf85d12fd9..1c72902b0e 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 @@ -778,6 +778,55 @@ TEST_F(QCOConstantPropagationTest, testPropagatingThroughClassicalBranching) { areModulesEquivalentWithPermutations(module.get(), reference.get())); } +/** + * @brief Test: This test checks if propagation through classical branching is + * done correctly if yield yields the values in a different order than the input + * order. + */ +TEST_F(QCOConstantPropagationTest, + testPropagatingThroughClassicalBranchingWithSwappedYieldedValues) { + 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{qubit, args[0]}; + }, + [&](const ValueRange args) { + const auto qubit = programBuilder.x(args[0]); + return SmallVector{args[1], qubit}; + }); + programBuilder.cx(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); + const auto qRangeRef = referenceBuilder.qcoIf( + bRef0, {qRef01, qRef1}, + [&](const ValueRange args) { + const auto qubit = referenceBuilder.x(args[1]); + return SmallVector{qubit, args[0]}; + }, + [&](const ValueRange args) { + const auto qubit = referenceBuilder.x(args[0]); + return SmallVector{args[1], qubit}; + }); + referenceBuilder.x(qRangeRef[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.