Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 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
b0d060d
feat(mlir): :sparkles: implement classical control replacement patter…
DRovara Jun 2, 2026
7042e3b
style(mlir): :rotating_light: fix includes
DRovara Jun 2, 2026
58d10b9
style(mlir): :recycle: apply coderabbit suggestions
DRovara Jun 3, 2026
febb216
fix(mlir): :bug: fix issues due to new version
DRovara Jul 14, 2026
6d4ce73
docs(mlir): :memo: add to changelog
DRovara Jul 14, 2026
38bbc15
Merge branch 'mlir/measurement-lifting' into mlir/replace-classical-c…
DRovara Jul 14, 2026
5e482a9
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
2c602ab
Merge branch 'mlir/measurement-lifting' into mlir/replace-classical-c…
DRovara Jul 17, 2026
8955d72
chore(mlir): :recycle: clean up tests
DRovara Jul 17, 2026
d1e452a
refactor(mlir): :recycle: small changes to test style
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
10 changes: 7 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], [#1756]) ([**@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 @@ -679,6 +681,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
Expand All @@ -694,6 +697,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
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
51 changes: 51 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,55 @@ 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)

}];
}

def ReplaceClassicalControls
: Pass<"replace-classical-controls", "mlir::ModuleOp"> {
let dependentDialects = ["mlir::qco::QCODialect",
"::mlir::arith::ArithDialect",
];
let summary =
"This pass attempts to replace controlled gates that are controlled by "
"values that are in a computational basis state and available as "
"measurement outcome by a classical "
"`qco.if` instruction. This is done to enable qubit reuse and other "
"optimizations.";
let description = [{
This pass searches for control operations that immediately follow measurements and replaces them
with classically controlled operations, represented by `qco.if` operations.
This reduces quantum interactions and allows for more optimizations, e.g., by enabling qubit reuse.
}];
}

#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.
*/
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
: 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
Loading
Loading