-
-
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
Open
DRovara
wants to merge
19
commits into
main
Choose a base branch
from
mlir/measurement-lifting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
89f8ee3
feat(mlir): :sparkles: add pass and patterns for measurement lifting
DRovara 6d4332d
test(mlir/hybrid-opt): :construction: set up tests for measurement li…
DRovara 1a7664c
fix(mlir): :white_check_mark: fix tests
DRovara 770e55c
style(mlir): :rotating_light: fix linter issues
DRovara 64b162c
style(mlir): :recycle: implement coderabbit suggestions
DRovara 137d93c
fix(mlir): :bug: fix all measurement lifting tests
DRovara 559c9ff
style(mlir): :rotating_light: fix linter issues
DRovara 679f454
style(mlir): :recycle: minor code clean-up
DRovara 5510fe8
style(mlir): :rotating_light: fix linter issue regarding unused include
DRovara 64cffab
🎨 pre-commit fixes
pre-commit-ci[bot] df2301b
chore(mlir): :pencil2: clean up minor typo
DRovara 8ca81d6
fix(mlir): :pencil2: fix some further typos
DRovara c55987b
Merge branch 'mlir/measurement-lifting' of github.com:munich-quantum-…
DRovara 653a8f4
test(mlir): :white_check_mark: add further special cases to tests
DRovara 1611c1b
chore(mlir): :recycle: add docstrings to new tests
DRovara 4a158fb
test(mlir): :white_check_mark: add test for inverse gates
DRovara 4d98606
chore(mlir): :recycle: slight refactoring in tests
DRovara 2deb2a4
chore(mlir): :recycle: further tests clean up
DRovara 096075c
fix(mlir): :recycle: address coderabbit issues
DRovara File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
231 changes: 231 additions & 0 deletions
231
mlir/lib/Dialect/QCO/Transforms/Optimizations/MeasurementLifting.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| /* | ||
| * 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 <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 (auto c = dyn_cast<CtrlOp>(op)) { | ||
| if (c.getNumBodyUnitaries() != 1) { | ||
| return false; | ||
| } | ||
| return isDiagonal(c.getBodyUnitary(0)); | ||
| } | ||
| if (auto i = dyn_cast<InvOp>(op)) { | ||
| if (i.getNumBodyUnitaries() != 1) { | ||
| return false; | ||
| } | ||
| return isDiagonal(i.getBodyUnitary(0)); | ||
| } | ||
| 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) && | ||
| predecessorUnitary.getInputQubits().size() == 1) { | ||
| 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>(patterns.getContext()); | ||
| patterns.add<LiftMeasurementsAboveInvertingGatesPattern>( | ||
| patterns.getContext()); | ||
| patterns.add<LiftMeasurementsAbovePhaseGatesPattern>(patterns.getContext()); | ||
|
|
||
| // Apply patterns in an iterative and greedy manner. | ||
| if (failed(applyPatternsGreedily(op, std::move(patterns)))) { | ||
| signalPassFailure(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| } // namespace mlir::qco | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Format the
dependentDialectslist.Consider removing the trailing comma and bringing the closing bracket up for consistency with the formatting of other passes in this file.
🛠️ Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents