-
-
Notifications
You must be signed in to change notification settings - Fork 72
✨ Add pass and patterns for measurement lifting #1705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
89f8ee3
6d4332d
1a7664c
770e55c
64b162c
137d93c
559c9ff
679f454
5510fe8
64cffab
df2301b
8ca81d6
c55987b
653a8f4
1611c1b
4a158fb
4d98606
2deb2a4
096075c
b1a24cd
6d4ce73
70de05f
102663b
91fe289
317ebfd
d392185
9253715
10b0f89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
||
|
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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 😌
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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).
But if you didn't misunderstand my documentation there, I'm a bit confused why
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't misunderstand your documented (hopefully 🤪). If I remember correctly |
||
|
|
||
| }]; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| #endif // MLIR_DIALECT_QCO_TRANSFORMS_PASSES_TD | ||
| 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. | ||
| */ | ||
|
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 | ||
|
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 | ||
Uh oh!
There was an error while loading. Please reload this page.