Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
89f8ee3
feat(mlir): :sparkles: add pass and patterns for measurement lifting
DRovara May 13, 2026
6d4332d
test(mlir/hybrid-opt): :construction: set up tests for measurement li…
DRovara Jun 1, 2026
1a7664c
fix(mlir): :white_check_mark: fix tests
DRovara Jun 2, 2026
770e55c
style(mlir): :rotating_light: fix linter issues
DRovara Jun 2, 2026
64b162c
style(mlir): :recycle: implement coderabbit suggestions
DRovara Jun 2, 2026
137d93c
fix(mlir): :bug: fix all measurement lifting tests
DRovara Jun 2, 2026
559c9ff
style(mlir): :rotating_light: fix linter issues
DRovara Jun 2, 2026
679f454
style(mlir): :recycle: minor code clean-up
DRovara Jun 2, 2026
5510fe8
style(mlir): :rotating_light: fix linter issue regarding unused include
DRovara Jun 2, 2026
64cffab
🎨 pre-commit fixes
pre-commit-ci[bot] Jul 14, 2026
df2301b
chore(mlir): :pencil2: clean up minor typo
DRovara Jul 14, 2026
8ca81d6
fix(mlir): :pencil2: fix some further typos
DRovara Jul 14, 2026
c55987b
Merge branch 'mlir/measurement-lifting' of github.com:munich-quantum-…
DRovara Jul 14, 2026
653a8f4
test(mlir): :white_check_mark: add further special cases to tests
DRovara Jul 14, 2026
1611c1b
chore(mlir): :recycle: add docstrings to new tests
DRovara Jul 14, 2026
4a158fb
test(mlir): :white_check_mark: add test for inverse gates
DRovara Jul 14, 2026
4d98606
chore(mlir): :recycle: slight refactoring in tests
DRovara Jul 14, 2026
2deb2a4
chore(mlir): :recycle: further tests clean up
DRovara Jul 14, 2026
096075c
fix(mlir): :recycle: address coderabbit issues
DRovara Jul 14, 2026
b1a24cd
chore(mlir): :recycle: some further coderabbit suggestions
DRovara Jul 14, 2026
6d4ce73
docs(mlir): :memo: add to changelog
DRovara Jul 14, 2026
70de05f
Merge branch 'main' into mlir/measurement-lifting
DRovara Jul 15, 2026
102663b
Merge branch 'main' into mlir/measurement-lifting
DRovara Jul 16, 2026
91fe289
chore(mlir): :recycle: refactor `isDiagonal` check to use `getSoleBod…
DRovara Jul 16, 2026
317ebfd
chore(mlir): :recycle: remove unneeded check
DRovara Jul 17, 2026
d392185
chore(mlir): :recycle: combine lifting of measurements
DRovara Jul 17, 2026
9253715
docs(mlir): :memo: fix up changelog
DRovara Jul 17, 2026
10b0f89
fix(mlir): :rotating_light: fix linter issues
DRovara Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**])

Expand Down Expand Up @@ -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
Comment thread
burgholzer marked this conversation as resolved.
[#1700]: https://github.com/munich-quantum-toolkit/core/pull/1700
[#1694]: https://github.com/munich-quantum-toolkit/core/pull/1694
Expand Down
15 changes: 15 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
//===--------------------------------------------------------------------===//
Expand Down
33 changes: 33 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Comment thread
DRovara marked this conversation as resolved.
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)
Comment on lines +229 to +232

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one thought that came to mind: If a Y gate is followed by a measurement, could that also equivalently be replaced by meas* and an X gate? That sounds like a reasonable simplification to me if possible 😌

@DRovara DRovara Jul 16, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I don't understand you correctly, but most likely you misread my description here (because I have written it up badly).

X; meas; is transformed into meas*; X;, not meas*; Y;. My description below makes it sound like the X was replaced with a Y after the transformation, but what I actually meant was that the same transformation can be done for Y gates, as in: Y; meas; => meas*; Y.

But if you didn't misunderstand my documentation there, I'm a bit confused why Y; meas could become meas*; X. That way, we would drop a Z phase and the constant factor i 🤔

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't misunderstand your documented (hopefully 🤪).
The measurement collapses the state to a basis state. At that point, I would have guessed that the phase does not make a difference anymore.

If I remember correctly Y = -iXZ. The global phase in front of a measurement does not make a difference and the Z in front of the measurement is simply eliminated.
But this is not too important supposedly. Y gates are pretty rare in quantum algorithms anyway and are mostly broken down into X and Z.
So this was more of a thought experiment 🧪 feel free to resolve.


}];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD
5 changes: 5 additions & 0 deletions mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
225 changes: 225 additions & 0 deletions mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
Original file line number Diff line number Diff line change
@@ -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 <llvm/ADT/STLExtras.h>
#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/IR/MLIRContext.h>
#include <mlir/IR/PatternMatch.h>
#include <mlir/IR/Value.h>
#include <mlir/Support/LLVM.h>
#include <mlir/Transforms/GreedyPatternRewriteDriver.h>

#include <utility>

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<XOp, YOp>(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<CtrlOp, InvOp>(op)) {
return isDiagonal(utils::getSoleBodyUnitary<UnitaryOpInterface>(
*op->getRegion(0).getBlocks().begin()));
}
return isa<ZOp, SOp, TOp, POp, RZOp, SdgOp, TdgOp, IdOp>(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<MeasureOp> {

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<UnitaryOpInterface>(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.
*/
Comment thread
coderabbitai[bot] marked this conversation as resolved.
struct LiftMeasurementsAboveInvertingGatesPattern final
: mlir::OpRewritePattern<MeasureOp> {

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<UnitaryOpInterface>(predecessor);

if (!predecessorUnitary) {
return mlir::failure();
}

if (isInverting(predecessor)) {
swapGateWithMeasurement(predecessorUnitary, op, rewriter);
rewriter.setInsertionPointAfter(op);
const mlir::Value trueConstant = rewriter.create<mlir::arith::ConstantOp>(
op.getLoc(), rewriter.getBoolAttr(true));
auto inversion = rewriter.create<mlir::arith::XOrIOp>(
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<MeasureOp> {

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<CtrlOp>(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
Comment thread
coderabbitai[bot] marked this conversation as resolved.
: impl::MeasurementLiftingBase<MeasurementLifting> {
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<LiftMeasurementsAboveControlsPattern,
LiftMeasurementsAboveInvertingGatesPattern,
LiftMeasurementsAbovePhaseGatesPattern>(patterns.getContext());

// Apply patterns in an iterative and greedy manner.
if (failed(applyPatternsGreedily(op, std::move(patterns)))) {
signalPassFailure();
}
}
};

} // namespace

} // namespace mlir::qco
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading
Loading