diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e47345b5..0d0d690789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,15 +50,17 @@ releases may include breaking changes. restricted topologies ([#1537], [#1547], [#1568], [#1581], [#1583], [#1588], [#1600], [#1664], [#1709], [#1716], [#1748], [#1805], [#1870], [#1904]) ([**@MatthiasReumann**], [**@burgholzer**]) +- ✨ Add a pass for qubit reuse in quantum programs, as well as related + auxiliary passes and patterns ([#1705], [#1755]) ([**@DRovara**]) - ✨ 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], [#1755], [#1762], [#1765], - [#1780], [#1781], [#1782], [#1787], [#1806], [#1807], [#1815], [#1808], - [#1823], [#1824], [#1830], [#1869], [#1872], [#1914]) ([**@burgholzer**], + [#1717], [#1728], [#1730], [#1749], [#1751], [#1762], [#1765], [#1780], + [#1781], [#1782], [#1787], [#1806], [#1807], [#1815], [#1808], [#1823], + [#1824], [#1830], [#1869], [#1872], [#1914]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**]) @@ -694,6 +696,7 @@ 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 diff --git a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h index d88e12dfa7..14ea2e85f9 100644 --- a/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h +++ b/mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h @@ -138,6 +138,21 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder { */ Value floatConstant(double value); + /** + * @brief Create a constant boolean value + * @param value The value to store in the constant + * @return The value produced by the constant operation + * + * @par Example: + * ```c++ + * auto c = builder.boolConstant(true); + * ``` + * ```mlir + * %c = arith.constant 1 : i1 + * ``` + */ + Value boolConstant(bool value); + //===--------------------------------------------------------------------===// // Memory Management //===--------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td index 971dc18f71..00d9ae535f 100644 --- a/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/QCO/Transforms/Passes.td @@ -201,4 +201,37 @@ def HadamardLifting : Pass<"hadamard-lifting", "mlir::ModuleOp"> { }]; } +def MeasurementLifting : Pass<"measurement-lifting", "mlir::ModuleOp"> { + let dependentDialects = ["mlir::qco::QCODialect", + "::mlir::arith::ArithDialect"]; + let summary = "This pass attempts to move measurements as far up as " + "possible, shifting them above gates that commute with them. " + "This is done to enable qubit reuse and other optimizations."; + let description = [{ + This pass applies measurement lifting, moving measurements up the code as far as possible. + Measurement lifting is a subroutine of the qubit reuse routine. The goal is to measure qubits earlier in the + circuit to reuse them and to potentially remove some quantum gates. + + Measurement lifting uses the following commutation rules: + ┌──────┐ ┌──────┐ + ──■──┤ Meas │────── ─┤ Meas ├──■─── + │ └──────┘ └──────┘ │ + ┌─┴─┐ => ┌─┴─┐ + ┤ U ├──────────────── ─────────┤ U ├─ + └───┘ └───┘ + (Where U is any (controlled) unitary gate) + + ┌───┐┌──────┐ ┌──────┐┌───┐ + ┤ P ├┤ Meas ├ => ┤ Meas ├┤ P ├ + └───┘└──────┘ └──────┘└───┘ + (Where P is any diagonal gate, e.g., `z`, `s`, ...) + + ┌───┐┌──────┐ ┌───────┐┌───┐ + ┤ X ├┤ Meas ├ => ┤ Meas* ├┤ X ├ + └───┘└──────┘ └───────┘└───┘ + (Where Meas* is a measurement after which the outcome is classically negated and X can be replaced by Y, which is also anti-diagonal) + + }]; +} + #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD diff --git a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp index f766602a24..5d800fb99e 100644 --- a/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp +++ b/mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp @@ -92,6 +92,11 @@ Value QCOProgramBuilder::floatConstant(const double value) { return arith::ConstantOp::create(*this, getF64FloatAttr(value)).getResult(); } +Value QCOProgramBuilder::boolConstant(const bool value) { + checkFinalized(); + return arith::ConstantOp::create(*this, getBoolAttr(value)).getResult(); +} + Value& QCOProgramBuilder::QubitRegister::operator[](const size_t index) { if (index >= qubits.size()) { llvm::reportFatalUsageError("Qubit index out of bounds"); diff --git a/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp new file mode 100644 index 0000000000..b8d6fbb571 --- /dev/null +++ b/mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp @@ -0,0 +1,225 @@ +/* + * 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 + */ + +// +// Created by damian on 5/13/26. +// + +#include "mlir/Dialect/QCO/IR/QCOInterfaces.h" +#include "mlir/Dialect/QCO/IR/QCOOps.h" +#include "mlir/Dialect/QCO/Transforms/Passes.h" +#include "mlir/Dialect/Utils/Utils.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mlir::qco { + +#define GEN_PASS_DEF_MEASUREMENTLIFTING +#include "mlir/Dialect/QCO/Transforms/Passes.h.inc" + +/** + * @brief Checks if the given operation is an inverting gate. + * @param op The operation to check. + * @return True if the operation is an inverting gate, false otherwise. + */ +static bool isInverting(Operation* op) { return isa(op); } + +/** + * @brief Checks if the given operation is a diagonal gate. + * @param op The operation to check. + * @return True if the operation is a diagonal gate, false otherwise. + */ +static bool isDiagonal(Operation* op) { + if (op == nullptr) { + return false; + } + if (isa(op)) { + return isDiagonal(utils::getSoleBodyUnitary( + *op->getRegion(0).getBlocks().begin())); + } + return isa(op); +} + +/** + * @brief This method swaps a gate with a measurement. + * @param gate The gate to swap. + * @param measurement The measurement to swap. + * @param rewriter The used rewriter. + */ +static void swapGateWithMeasurement(UnitaryOpInterface gate, + MeasureOp measurement, + mlir::PatternRewriter& rewriter) { + auto measurementInput = measurement.getQubitIn(); + auto gateInput = gate.getInputForOutput(measurementInput); + rewriter.replaceUsesWithIf(measurementInput, gateInput, + [&](mlir::OpOperand& operand) { + // We only replace the single use by the + // measure op + return operand.getOwner() == measurement; + }); + rewriter.replaceUsesWithIf(gateInput, measurement.getQubitOut(), + [&](mlir::OpOperand& operand) { + // We only replace the single use by the + // predecessor + return operand.getOwner() == gate; + }); + rewriter.replaceUsesWithIf(measurement.getQubitOut(), measurementInput, + [&](mlir::OpOperand& operand) { + // All further uses of the measurement output now + // use the gate output + return operand.getOwner() != gate; + }); + rewriter.moveOpBefore(measurement, gate); +} + +namespace { +/** + * @brief This pattern is responsible for lifting measurements above any phase + * gates. + */ +struct LiftMeasurementsAbovePhaseGatesPattern final + : mlir::OpRewritePattern { + + explicit LiftMeasurementsAbovePhaseGatesPattern(mlir::MLIRContext* context) + : OpRewritePattern(context) {} + + mlir::LogicalResult + matchAndRewrite(MeasureOp op, + mlir::PatternRewriter& rewriter) const override { + const auto qubitVariable = op.getQubitIn(); + auto* predecessor = qubitVariable.getDefiningOp(); + + auto predecessorUnitary = mlir::dyn_cast(predecessor); + + if (!predecessorUnitary) { + return mlir::failure(); + } + + if (isDiagonal(predecessor)) { + swapGateWithMeasurement(predecessorUnitary, op, rewriter); + return mlir::success(); + } + + return mlir::failure(); + } +}; + +/** + * @brief This pattern is responsible for lifting measurements above any + * anti-diagonal gates. + */ +struct LiftMeasurementsAboveInvertingGatesPattern final + : mlir::OpRewritePattern { + + explicit LiftMeasurementsAboveInvertingGatesPattern( + mlir::MLIRContext* context) + : OpRewritePattern(context) {} + + mlir::LogicalResult + matchAndRewrite(MeasureOp op, + mlir::PatternRewriter& rewriter) const override { + const auto qubitVariable = op.getQubitIn(); + auto* predecessor = qubitVariable.getDefiningOp(); + + auto predecessorUnitary = mlir::dyn_cast(predecessor); + + if (!predecessorUnitary) { + return mlir::failure(); + } + + if (isInverting(predecessor)) { + swapGateWithMeasurement(predecessorUnitary, op, rewriter); + rewriter.setInsertionPointAfter(op); + const mlir::Value trueConstant = rewriter.create( + op.getLoc(), rewriter.getBoolAttr(true)); + auto inversion = rewriter.create( + op.getLoc(), op.getResult(), trueConstant); + // We need `replaceUsesWithIf` so that we can replace all uses except for + // the one use that defines the inverted bit. + rewriter.replaceUsesWithIf(op.getResult(), inversion.getResult(), + [&](mlir::OpOperand& operand) { + return operand.getOwner() != inversion; + }); + return mlir::success(); + } + + return mlir::failure(); + } +}; + +/** + * @brief This pattern is responsible for applying the "deferred measurement + * principle", lifting measurements above controls. + */ +struct LiftMeasurementsAboveControlsPattern final + : mlir::OpRewritePattern { + + explicit LiftMeasurementsAboveControlsPattern(mlir::MLIRContext* context) + : OpRewritePattern(context) {} + + mlir::LogicalResult + matchAndRewrite(MeasureOp op, + mlir::PatternRewriter& rewriter) const override { + const auto qubitVariable = op.getQubitIn(); + auto* predecessor = qubitVariable.getDefiningOp(); + auto predecessorCtrl = mlir::dyn_cast(predecessor); + + if (!predecessorCtrl) { + return mlir::failure(); + } + + if (llvm::find(predecessorCtrl.getControlsOut(), qubitVariable) == + predecessorCtrl.getControlsOut().end()) { + // The measured qubit is a target, not a control of the gate. + return mlir::failure(); + } + + swapGateWithMeasurement(predecessorCtrl, op, rewriter); + + return mlir::success(); + } +}; + +/** + * @brief Pass raises Measurements above controlled and uncontrolled gates. + */ +struct MeasurementLifting final + : impl::MeasurementLiftingBase { + using MeasurementLiftingBase::MeasurementLiftingBase; + +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()); + + // 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/Optimizations/CMakeLists.txt b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt index e80e957680..bdc78401c8 100644 --- a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License set(target_name mqt-core-mlir-unittest-optimizations) -add_executable(${target_name} test_qco_hadamard_lifting.cpp +add_executable(${target_name} test_qco_hadamard_lifting.cpp test_qco_measurement_lifting.cpp test_qco_merge_single_qubit_rotation.cpp test_quantum_loop_unroll.cpp) target_link_libraries( diff --git a/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp new file mode 100644 index 0000000000..00f19a9530 --- /dev/null +++ b/mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_measurement_lifting.cpp @@ -0,0 +1,604 @@ +/* + * 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 + +#include +#include + +namespace { + +using namespace mlir; +using namespace mlir::qco; + +class QCOMeasurementLiftingTest : public testing::Test { + +protected: + MLIRContext context; + QCOProgramBuilder programBuilder; + QCOProgramBuilder referenceBuilder; + OwningOpRef module; + OwningOpRef reference; + + QCOMeasurementLiftingTest() + : programBuilder(&context), referenceBuilder(&context) {} + + void SetUp() override { + // Register all necessary dialects + DialectRegistry registry; + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + } + + /** + * @brief Adds the measurementLiftingPass to the current context and runs it. + */ + static LogicalResult runMeasurementLiftingPass(ModuleOp module) { + PassManager pm(module.getContext()); + pm.addPass(createMeasurementLifting()); + pm.addPass(createCanonicalizerPass()); + 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: Measurements on control bits can be lifted over the controlled + * gates. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPositiveControl) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + + std::tie(q1, q0) = programBuilder.cx(q1, q0); + std::tie(q0, q1) = programBuilder.ch(q0, q1); + std::tie(q0, q1) = programBuilder.cx(q0, q1); + + Value c0; + Value c1; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q1, c1) = programBuilder.measure(q1); + + programBuilder.sink(q0); + programBuilder.sink(q1); + module = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + + std::tie(r1, r0) = referenceBuilder.cx(r1, r0); + Value cr0; + Value cr1; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + std::tie(r0, r1) = referenceBuilder.ch(r0, r1); + std::tie(r0, r1) = referenceBuilder.cx(r0, r1); + + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests that lifting also works if there are multiple controls in + * a controlled gate. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverOneOfMultipleControls) { + programBuilder.initialize({programBuilder.getI1Type(), + programBuilder.getI1Type(), + programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + + SmallVector q12; + SmallVector q0Vec; + std::tie(q12, q0Vec) = + programBuilder.ctrl({q1, q2}, {q0}, [&](ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + std::tie(q12, q0Vec) = + programBuilder.ctrl({q12[1], q12[0]}, q0Vec, [&](ValueRange target) { + return SmallVector{programBuilder.h(target[0])}; + }); + std::tie(q12, q0Vec) = + programBuilder.ctrl({q12[1], q12[0]}, q0Vec, [&](ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + + Value c1; + std::tie(q1, c1) = programBuilder.measure(q12[0]); + + q0 = programBuilder.h(q0Vec[0]); + q2 = programBuilder.h(q12[1]); + + Value c0; + Value c2; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q2, c2) = programBuilder.measure(q2); + + programBuilder.sink(q0); + programBuilder.sink(q1); + programBuilder.sink(q2); + + module = programBuilder.finalize({c0, c1, c2}); + + referenceBuilder.initialize({referenceBuilder.getI1Type(), + referenceBuilder.getI1Type(), + referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + + Value cr1; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + SmallVector r12; + SmallVector r0Vec; + std::tie(r12, r0Vec) = + referenceBuilder.ctrl({r1, r2}, {r0}, [&](ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + std::tie(r12, r0Vec) = + referenceBuilder.ctrl({r12[1], r12[0]}, r0Vec, [&](ValueRange target) { + return SmallVector{referenceBuilder.h(target[0])}; + }); + std::tie(r12, r0Vec) = + referenceBuilder.ctrl({r12[1], r12[0]}, r0Vec, [&](ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + + r0 = referenceBuilder.h(r0Vec[0]); + r2 = referenceBuilder.h(r12[1]); + + Value cr0; + Value cr2; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + std::tie(r2, cr2) = referenceBuilder.measure(r2); + + referenceBuilder.sink(r0); + referenceBuilder.sink(r12[0]); + referenceBuilder.sink(r2); + + reference = referenceBuilder.finalize({cr0, cr1, cr2}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests that multiple measurements that each target a control + * qubit of a controlled gate can be lifted over the controlled gate. + */ +TEST_F(QCOMeasurementLiftingTest, + liftMeasurementMultipleOverOneControlledGate) { + + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + auto q2 = programBuilder.allocQubit(); + + SmallVector q12; + SmallVector q0Vec; + std::tie(q12, q0Vec) = + programBuilder.ctrl({q1, q2}, {q0}, [&](ValueRange target) { + return SmallVector{programBuilder.x(target[0])}; + }); + + Value c1; + Value c2; + std::tie(q1, c1) = programBuilder.measure(q12[0]); + std::tie(q2, c2) = programBuilder.measure(q12[1]); + + programBuilder.sink(q0Vec[0]); + programBuilder.sink(q1); + programBuilder.sink(q2); + module = programBuilder.finalize({c1, c2}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + auto r2 = referenceBuilder.allocQubit(); + + Value cr1; + Value cr2; + std::tie(r1, cr1) = referenceBuilder.measure(r1); + std::tie(r2, cr2) = referenceBuilder.measure(r2); + + SmallVector r12; + SmallVector r0Vec; + std::tie(r12, r0Vec) = + referenceBuilder.ctrl({r1, r2}, {r0}, [&](ValueRange target) { + return SmallVector{referenceBuilder.x(target[0])}; + }); + + referenceBuilder.sink(r0Vec[0]); + referenceBuilder.sink(r12[0]); + referenceBuilder.sink(r12[1]); + reference = referenceBuilder.finalize({cr1, cr2}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests that a measurement can also be lifted over the control of + * a parametrized gate. + */ +TEST_F(QCOMeasurementLiftingTest, + liftMeasurementOverControlledParametrizedGate) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + + std::tie(q0, q1) = programBuilder.crx(std::numbers::pi / 2, q0, q1); + + Value c0; + Value c1; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q1, c1) = programBuilder.measure(q1); + + programBuilder.sink(q0); + programBuilder.sink(q1); + module = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + + Value cr0; + Value cr1; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + std::tie(r0, r1) = referenceBuilder.crx(std::numbers::pi / 2, r0, r1); + + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over a single X (anti-diagonal) + * gate. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleX) { + + programBuilder.initialize({programBuilder.getI1Type()}); + auto q = programBuilder.allocQubit(); + q = programBuilder.x(q); + Value c; + std::tie(q, c) = programBuilder.measure(q); + q = programBuilder.h(q); + programBuilder.sink(q); + module = programBuilder.finalize(c); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r = referenceBuilder.allocQubit(); + auto trueConstant = referenceBuilder.boolConstant(true); + Value cr; + std::tie(r, cr) = referenceBuilder.measure(r); + r = referenceBuilder.h(r); + + auto xorOp = arith::XOrIOp::create( + referenceBuilder, referenceBuilder.getLoc(), cr, trueConstant); + referenceBuilder.sink(r); + reference = referenceBuilder.finalize(xorOp.getResult()); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over a single Y (anti-diagonal) + * gate. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverSingleY) { + programBuilder.initialize({programBuilder.getI1Type()}); + auto q = programBuilder.allocQubit(); + q = programBuilder.y(q); + Value c; + std::tie(q, c) = programBuilder.measure(q); + programBuilder.sink(q); + module = programBuilder.finalize({c}); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r = referenceBuilder.allocQubit(); + auto trueConstant = referenceBuilder.boolConstant(true); + Value cr; + std::tie(r, cr) = referenceBuilder.measure(r); + auto xorOp = arith::XOrIOp::create( + referenceBuilder, referenceBuilder.getLoc(), cr, trueConstant); + referenceBuilder.sink(r); + reference = referenceBuilder.finalize({xorOp.getResult()}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over different diagonal phase-gates. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverPhaseGates) { + programBuilder.initialize({programBuilder.getI1Type()}); + auto q = programBuilder.allocQubit(); + q = programBuilder.id(q); + q = programBuilder.z(q); + q = programBuilder.s(q); + q = programBuilder.sdg(q); + q = programBuilder.t(q); + q = programBuilder.tdg(q); + q = programBuilder.p(std::numbers::pi / 2, q); + q = programBuilder.rz(std::numbers::pi / 2, q); + Value c; + std::tie(q, c) = programBuilder.measure(q); + programBuilder.sink(q); + module = programBuilder.finalize({c}); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r = referenceBuilder.allocQubit(); + Value cr; + std::tie(r, cr) = referenceBuilder.measure(r); + referenceBuilder.sink(r); + reference = referenceBuilder.finalize({cr}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over multiple anti-diagonal gates. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverMultipleXY) { + programBuilder.initialize({programBuilder.getI1Type()}); + auto q = programBuilder.allocQubit(); + q = programBuilder.x(q); + q = programBuilder.y(q); + Value c; + std::tie(q, c) = programBuilder.measure(q); + programBuilder.sink(q); + module = programBuilder.finalize({c}); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r = referenceBuilder.allocQubit(); + Value cr; + std::tie(r, cr) = referenceBuilder.measure(r); + referenceBuilder.sink(r); + reference = referenceBuilder.finalize({cr}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over multiple anti-diagonal and + * controlled gates. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverXAndControlledGates) { + programBuilder.initialize({programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + + std::tie(q0, q1) = programBuilder.cy(q0, q1); + q0 = programBuilder.x(q0); + std::tie(q0, q1) = programBuilder.cy(q0, q1); + q0 = programBuilder.x(q0); + + Value c0; + std::tie(q0, c0) = programBuilder.measure(q0); + + programBuilder.sink(q0); + programBuilder.sink(q1); + module = programBuilder.finalize({c0}); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + + Value cr0; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + + std::tie(r0, r1) = referenceBuilder.cy(r0, r1); + r0 = referenceBuilder.x(r0); + std::tie(r0, r1) = referenceBuilder.cy(r0, r1); + + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + reference = referenceBuilder.finalize({cr0}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over a controlled diagonal gate. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverDiagonalGateInControl) { + programBuilder.initialize( + {programBuilder.getI1Type(), programBuilder.getI1Type()}); + auto q0 = programBuilder.allocQubit(); + auto q1 = programBuilder.allocQubit(); + + std::tie(q0, q1) = programBuilder.cz(q0, q1); + + Value c0; + Value c1; + std::tie(q0, c0) = programBuilder.measure(q0); + std::tie(q1, c1) = programBuilder.measure(q1); + + programBuilder.sink(q0); + programBuilder.sink(q1); + module = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), programBuilder.getI1Type()}); + auto r0 = referenceBuilder.allocQubit(); + auto r1 = referenceBuilder.allocQubit(); + + Value cr0; + Value cr1; + std::tie(r0, cr0) = referenceBuilder.measure(r0); + std::tie(r1, cr1) = referenceBuilder.measure(r1); + + referenceBuilder.sink(r0); + referenceBuilder.sink(r1); + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests that a measurement is not lifted over a controlled + * sequence gate if there are multiple gates inside the control block. + */ +TEST_F(QCOMeasurementLiftingTest, dontLiftMeasurementMultipleGatesInControl) { + programBuilder.initialize( + {programBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto q0S0 = programBuilder.allocQubit(); + auto q1S0 = programBuilder.allocQubit(); + + auto [q0S1, q1S1] = programBuilder.ctrl({q0S0}, q1S0, [&](Value target) { + auto t = programBuilder.z(target); + return programBuilder.x(t); + }); + + auto [q0S2, c0] = programBuilder.measure(q0S1); + auto [q1S2, c1] = programBuilder.measure(q1S1); + + programBuilder.sink(q0S2); + programBuilder.sink(q1S2); + module = programBuilder.finalize({c0, c1}); + + referenceBuilder.initialize( + {referenceBuilder.getI1Type(), referenceBuilder.getI1Type()}); + auto r0S0 = referenceBuilder.allocQubit(); + auto r1S0 = referenceBuilder.allocQubit(); + + auto [r0S1, cr0] = referenceBuilder.measure(r0S0); + + auto [r0S2, r1S1] = referenceBuilder.ctrl({r0S1}, r1S0, [&](Value target) { + auto t = referenceBuilder.z(target); + return referenceBuilder.x(t); + }); + + auto [r1S2, cr1] = referenceBuilder.measure(r1S1); + + referenceBuilder.sink(r0S2); + referenceBuilder.sink(r1S2); + reference = referenceBuilder.finalize({cr0, cr1}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +} + +/** + * @brief Test: Tests lifting a measurement over an inverted phase gate. + */ +TEST_F(QCOMeasurementLiftingTest, liftMeasurementOverInvertedPhaseGates) { + programBuilder.initialize({programBuilder.getI1Type()}); + auto q = programBuilder.allocQubit(); + + q = programBuilder.inv( + q, [&](Value target) { return programBuilder.s(target); }); + + Value c; + std::tie(q, c) = programBuilder.measure(q); + programBuilder.sink(q); + module = programBuilder.finalize({c}); + + referenceBuilder.initialize({referenceBuilder.getI1Type()}); + auto r = referenceBuilder.allocQubit(); + Value cr; + std::tie(r, cr) = referenceBuilder.measure(r); + referenceBuilder.sink(r); + reference = referenceBuilder.finalize({cr}); + + ASSERT_TRUE(runMeasurementLiftingPass(module.get()).succeeded()); + ASSERT_TRUE(runCanonicalizerPass(reference.get()).succeeded()); + + EXPECT_TRUE( + areModulesEquivalentWithPermutations(module.get(), reference.get())); +}