Skip to content

Commit 8d38d03

Browse files
Ectrasburgholzer
andauthored
✨ Add inv Modifier for MLIR Redesign (#1330)
## Description Adds a `qc.inv` and `qco.inv` Op, similar to the existing `CtrlOp`. **Example**: QC: ```mlir qc.inv { qc.s %q0 : !qc.qubit } ``` QCO: ```mlir %targets_out = qco.inv (%arg0 = %targets_in) { %targets_res = qco.s %arg0 : !qco.qubit -> !qco.qubit qco.yield %targets_res : !qco.qubit } : {!qco.qubit} -> {!qco.qubit} ``` **Things to note:** - The canonicalizations are currently in QCO (such as replacing known gates with their inverse). Could happen already in QC or in both. - QuantumComputing (which eagerly replaces gates by their inverse) is not touched by this PR. Afaik, the QC and QCO dialects are not directly used by QuantumComputing yet. - Missing: Handling of invs of unknown gates, for example iSWAP (the only standard gate for which there's no "trivial" inverse). `inv { iSWAP }` will currently error on the translation to QIR. A reasonable tactic could be to get the inverse unitary matrix and decompose it into native gates. I think this is outside the scope of this PR, though. Fixes #1130 ## Checklist: - [x] The pull request only contains commits that are focused and relevant to this change. - [x] I have added appropriate tests that cover the new/changed functionality. - [x] I have updated the documentation to reflect these changes. - [ ] I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals. - [ ] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. --------- Signed-off-by: burgholzer <[email protected]> Co-authored-by: burgholzer <[email protected]>
1 parent 03e1f99 commit 8d38d03

File tree

15 files changed

+1195
-78
lines changed

15 files changed

+1195
-78
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
1111

1212
### Added
1313

14-
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects ([#1264], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475]) ([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**])
14+
- ✨ Add initial infrastructure for new QC and QCO MLIR dialects
15+
([#1264], [#1330], [#1402], [#1428], [#1430], [#1436], [#1443], [#1446], [#1464], [#1465], [#1470], [#1471], [#1472], [#1474], [#1475])
16+
([**@burgholzer**], [**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**])
1517

1618
### Changed
1719

@@ -365,6 +367,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
365367
[#1355]: https://github.com/munich-quantum-toolkit/core/pull/1355
366368
[#1338]: https://github.com/munich-quantum-toolkit/core/pull/1338
367369
[#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336
370+
[#1330]: https://github.com/munich-quantum-toolkit/core/pull/1330
368371
[#1328]: https://github.com/munich-quantum-toolkit/core/pull/1328
369372
[#1327]: https://github.com/munich-quantum-toolkit/core/pull/1327
370373
[#1310]: https://github.com/munich-quantum-toolkit/core/pull/1310
@@ -502,6 +505,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
502505
[**@lsschmid**]: https://github.com/lsschmid
503506
[**@marcelwa**]: https://github.com/marcelwa
504507
[**@lirem101**]: https://github.com/lirem101
508+
[**@Ectras**]: https://github.com/Ectras
505509

506510
<!-- General links -->
507511

cmake/SetupMLIR.cmake

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ set(LLVM_ENABLE_RTTI ON)
3333
set(LLVM_ENABLE_EH ON)
3434
include(HandleLLVMOptions)
3535

36-
include_directories(${LLVM_INCLUDE_DIRS})
37-
include_directories(${MLIR_INCLUDE_DIRS})
36+
include_directories(SYSTEM ${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS})
3837
include_directories(${MQT_MLIR_SOURCE_INCLUDE_DIR})
3938
include_directories(${MQT_MLIR_BUILD_INCLUDE_DIR})
4039
link_directories(${LLVM_BUILD_LIBRARY_DIR})

mlir/.clang-tidy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ Checks: |
66
llvm-prefer-static-over-anonymous-namespace,
77
-misc-use-anonymous-namespace,
88
llvm-twine-local,
9-
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access
9+
-cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
10+
-*-const-correctness,
1011
1112
CheckOptions:
1213
- key: misc-include-cleaner.IgnoreHeaders

mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,25 @@ class QCProgramBuilder final : public ImplicitLocOpBuilder {
844844
QCProgramBuilder& ctrl(ValueRange controls,
845845
const std::function<void()>& body);
846846

847+
/**
848+
* @brief Apply an inverse (i.e., adjoint) operation.
849+
*
850+
* @param body Function that builds the body containing the operation to
851+
* invert
852+
* @return Reference to this builder for method chaining
853+
*
854+
* @par Example:
855+
* ```c++
856+
* builder.inv([&] { builder.s(q0); });
857+
* ```
858+
* ```mlir
859+
* qc.inv {
860+
* qc.s %q0 : !qc.qubit
861+
* }
862+
* ```
863+
*/
864+
QCProgramBuilder& inv(const std::function<void()>& body);
865+
847866
//===--------------------------------------------------------------------===//
848867
// Deallocation
849868
//===--------------------------------------------------------------------===//

mlir/include/mlir/Dialect/QC/IR/QCOps.td

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,4 +973,48 @@ def CtrlOp : QCOp<"ctrl",
973973
let hasVerifier = 1;
974974
}
975975

976+
def InvOp : QCOp<"inv",
977+
traits = [
978+
UnitaryOpInterface,
979+
SingleBlockImplicitTerminator<"::mlir::qc::YieldOp">,
980+
RecursiveMemoryEffects
981+
]> {
982+
let summary = "Invert a unitary operation";
983+
let description = [{
984+
A modifier operation that inverts the unitary operation defined in its body
985+
region.
986+
987+
Example:
988+
```mlir
989+
qc.inv {
990+
qc.s %q0 : !qc.qubit
991+
}
992+
```
993+
}];
994+
995+
let regions = (region SizedRegion<1>:$region);
996+
let assemblyFormat = "$region attr-dict";
997+
998+
let extraClassDeclaration = [{
999+
[[nodiscard]] UnitaryOpInterface getBodyUnitary();
1000+
size_t getNumQubits();
1001+
size_t getNumTargets();
1002+
static size_t getNumControls();
1003+
Value getQubit(size_t i);
1004+
Value getTarget(size_t i);
1005+
static Value getControl(size_t i);
1006+
size_t getNumParams();
1007+
Value getParameter(size_t i);
1008+
static StringRef getBaseSymbol() { return "inv"; }
1009+
}];
1010+
1011+
let builders = [
1012+
OpBuilder<(ins "UnitaryOpInterface":$bodyUnitary)>,
1013+
OpBuilder<(ins "const std::function<void()>&":$bodyBuilder)>
1014+
];
1015+
1016+
let hasCanonicalizer = 1;
1017+
let hasVerifier = 1;
1018+
}
1019+
9761020
#endif // QC_OPS

mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,6 +1005,31 @@ class QCOProgramBuilder final : public ImplicitLocOpBuilder {
10051005
ctrl(ValueRange controls, ValueRange targets,
10061006
llvm::function_ref<llvm::SmallVector<Value>(ValueRange)> body);
10071007

1008+
/**
1009+
* @brief Apply an inverse operation
1010+
*
1011+
* @param qubits Qubits involved in the operation
1012+
* @param body Function that builds the body containing the target operation
1013+
* @return Output qubits
1014+
*
1015+
* @par Example:
1016+
* ```c++
1017+
* qubits_out = builder.inv(q0_in,
1018+
* [&](ValueRange qubits) -> llvm::SmallVector<Value> {
1019+
* return {builder.s(qubits[0])};
1020+
* }
1021+
* );
1022+
* ```
1023+
* ```mlir
1024+
* %qubits_out = qco.inv (%q = %q0_in) {
1025+
* %q_res = qco.s %q : !qco.qubit -> !qco.qubit
1026+
* qco.yield %q_res
1027+
* } : {!qco.qubit} -> {!qco.qubit}
1028+
* ```
1029+
*/
1030+
ValueRange inv(ValueRange qubits,
1031+
llvm::function_ref<llvm::SmallVector<Value>(ValueRange)> body);
1032+
10081033
//===--------------------------------------------------------------------===//
10091034
// Deallocation
10101035
//===--------------------------------------------------------------------===//

mlir/include/mlir/Dialect/QCO/IR/QCOOps.td

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1074,7 +1074,7 @@ def CtrlOp : QCOOp<"ctrl", traits =
10741074
AttrSizedResultSegments,
10751075
SameOperandsAndResultType,
10761076
SameOperandsAndResultShape,
1077-
SingleBlock,
1077+
SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">,
10781078
RecursiveMemoryEffects
10791079
]> {
10801080
let summary = "Add control qubits to a unitary operation";
@@ -1142,4 +1142,71 @@ def CtrlOp : QCOOp<"ctrl", traits =
11421142
let hasVerifier = 1;
11431143
}
11441144

1145+
def InvOp : QCOOp<"inv", traits =
1146+
[
1147+
UnitaryOpInterface,
1148+
SameOperandsAndResultType,
1149+
SameOperandsAndResultShape,
1150+
SingleBlockImplicitTerminator<"::mlir::qco::YieldOp">,
1151+
RecursiveMemoryEffects
1152+
]> {
1153+
let summary = "Invert a unitary operation";
1154+
let description = [{
1155+
A modifier operation that inverts the unitary operation defined in its body
1156+
region. The operation takes a variadic number of qubits as inputs and
1157+
produces corresponding output qubits.
1158+
1159+
Example:
1160+
```mlir
1161+
%q_out = qco.inv (%q = %q_in) {
1162+
%q_1 = qco.s %q : !qco.qubit -> !qco.qubit
1163+
qco.yield %q_1
1164+
} : {!qco.qubit} -> {!qco.qubit}
1165+
```
1166+
}];
1167+
1168+
let arguments = (ins Arg<Variadic<QubitType>, "the qubits involved in the operation", [MemRead]>:$qubits_in);
1169+
let results = (outs Variadic<QubitType>:$qubits_out);
1170+
let regions = (region SizedRegion<1>:$region);
1171+
let assemblyFormat = [{
1172+
custom<TargetAliasing>($region, $qubits_in)
1173+
attr-dict `:`
1174+
`{` type($qubits_in) `}`
1175+
`->`
1176+
`{` type($qubits_out) `}`
1177+
}];
1178+
1179+
let extraClassDeclaration = [{
1180+
UnitaryOpInterface getBodyUnitary();
1181+
size_t getNumQubits();
1182+
size_t getNumTargets();
1183+
static size_t getNumControls();
1184+
Value getInputQubit(size_t i);
1185+
OperandRange getInputQubits();
1186+
Value getOutputQubit(size_t i);
1187+
ResultRange getOutputQubits();
1188+
Value getInputTarget(size_t i);
1189+
Value getOutputTarget(size_t i);
1190+
static Value getInputControl(size_t i);
1191+
static Value getOutputControl(size_t i);
1192+
Value getInputForOutput(Value output);
1193+
Value getOutputForInput(Value input);
1194+
size_t getNumParams();
1195+
Value getParameter(size_t i);
1196+
[[nodiscard]] static StringRef getBaseSymbol() { return "inv"; }
1197+
[[nodiscard]] std::optional<Eigen::MatrixXcd> getUnitaryMatrix();
1198+
}];
1199+
1200+
let builders = [
1201+
OpBuilder<(ins "ValueRange":$qubits), [{
1202+
build($_builder, $_state, qubits.getTypes(), qubits);
1203+
}]>,
1204+
OpBuilder<(ins "ValueRange":$qubits, "UnitaryOpInterface":$bodyUnitary)>,
1205+
OpBuilder<(ins "ValueRange":$qubits, "llvm::function_ref<llvm::SmallVector<Value>(ValueRange)>":$bodyBuilder)>
1206+
];
1207+
1208+
let hasCanonicalizer = 1;
1209+
let hasVerifier = 1;
1210+
}
1211+
11451212
#endif // QCOOPS

mlir/lib/Conversion/QCOToQC/QCOToQC.cpp

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,68 @@ struct ConvertQCOCtrlOp final : OpConversionPattern<qco::CtrlOp> {
812812
}
813813
};
814814

815+
/**
816+
* @brief Converts qco.inv to qc.inv
817+
*
818+
* @par Example:
819+
* ```mlir
820+
* %q0_out = qco.inv (%q0 = %q0_in) {
821+
* %q0_res = qco.s %q0 : !qco.qubit -> !qco.qubit
822+
* qco.yield %q0_res
823+
* } : {!qco.qubit} -> {!qco.qubit}
824+
* ```
825+
* is converted to
826+
* ```mlir
827+
* qc.inv {
828+
* qc.s %q0 : !qc.qubit
829+
* }
830+
* ```
831+
*/
832+
struct ConvertQCOInvOp final : OpConversionPattern<qco::InvOp> {
833+
using OpConversionPattern::OpConversionPattern;
834+
835+
LogicalResult
836+
matchAndRewrite(qco::InvOp op, OpAdaptor adaptor,
837+
ConversionPatternRewriter& rewriter) const override {
838+
// Create qc.inv operation
839+
auto qcOp = qc::InvOp::create(rewriter, op.getLoc());
840+
841+
// Clone body region from QCO to QC
842+
auto& dstRegion = qcOp.getRegion();
843+
rewriter.cloneRegionBefore(op.getRegion(), dstRegion, dstRegion.end());
844+
845+
auto& entryBlock = dstRegion.front();
846+
const auto numArgs = entryBlock.getNumArguments();
847+
if (adaptor.getQubitsIn().size() != numArgs) {
848+
return op.emitOpError() << "qco.inv: entry block args (" << numArgs
849+
<< ") must match number of target operands ("
850+
<< adaptor.getQubitsIn().size() << ")";
851+
}
852+
853+
// Remove all block arguments in the cloned region
854+
rewriter.modifyOpInPlace(qcOp, [&] {
855+
// 1. Replace uses (Must be done BEFORE erasing)
856+
// We iterate 0..N using indices since the block args are still stable
857+
// here.
858+
for (auto i = 0UL; i < numArgs; ++i) {
859+
entryBlock.getArgument(i).replaceAllUsesWith(adaptor.getQubitsIn()[i]);
860+
}
861+
862+
// 2. Erase all block arguments
863+
// Now that they have no uses, we can safely wipe them.
864+
// We use a bulk erase for efficiency (start index 0, count N).
865+
if (numArgs > 0) {
866+
entryBlock.eraseArguments(0, numArgs);
867+
}
868+
});
869+
870+
// Replace the output qubits with the same QC references
871+
rewriter.replaceOp(op, adaptor.getOperands());
872+
873+
return success();
874+
}
875+
};
876+
815877
/**
816878
* @brief Converts qco.yield to qc.yield
817879
*
@@ -865,6 +927,7 @@ struct ConvertQCOYieldOp final : OpConversionPattern<qco::YieldOp> {
865927
struct QCOToQC final : impl::QCOToQCBase<QCOToQC> {
866928
using QCOToQCBase::QCOToQCBase;
867929

930+
protected:
868931
void runOnOperation() override {
869932
MLIRContext* context = &getContext();
870933
auto* module = getOperation();
@@ -879,18 +942,18 @@ struct QCOToQC final : impl::QCOToQCBase<QCOToQC> {
879942

880943
// Register operation conversion patterns
881944
// Note: No state tracking needed - OpAdaptors handle type conversion
882-
patterns.add<ConvertQCOAllocOp, ConvertQCODeallocOp, ConvertQCOStaticOp,
883-
ConvertQCOMeasureOp, ConvertQCOResetOp, ConvertQCOGPhaseOp,
884-
ConvertQCOIdOp, ConvertQCOXOp, ConvertQCOYOp, ConvertQCOZOp,
885-
ConvertQCOHOp, ConvertQCOSOp, ConvertQCOSdgOp, ConvertQCOTOp,
886-
ConvertQCOTdgOp, ConvertQCOSXOp, ConvertQCOSXdgOp,
887-
ConvertQCORXOp, ConvertQCORYOp, ConvertQCORZOp, ConvertQCOPOp,
888-
ConvertQCOROp, ConvertQCOU2Op, ConvertQCOUOp, ConvertQCOSWAPOp,
889-
ConvertQCOiSWAPOp, ConvertQCODCXOp, ConvertQCOECROp,
890-
ConvertQCORXXOp, ConvertQCORYYOp, ConvertQCORZXOp,
891-
ConvertQCORZZOp, ConvertQCOXXPlusYYOp, ConvertQCOXXMinusYYOp,
892-
ConvertQCOBarrierOp, ConvertQCOCtrlOp, ConvertQCOYieldOp>(
893-
typeConverter, context);
945+
patterns
946+
.add<ConvertQCOAllocOp, ConvertQCODeallocOp, ConvertQCOStaticOp,
947+
ConvertQCOMeasureOp, ConvertQCOResetOp, ConvertQCOGPhaseOp,
948+
ConvertQCOIdOp, ConvertQCOXOp, ConvertQCOYOp, ConvertQCOZOp,
949+
ConvertQCOHOp, ConvertQCOSOp, ConvertQCOSdgOp, ConvertQCOTOp,
950+
ConvertQCOTdgOp, ConvertQCOSXOp, ConvertQCOSXdgOp, ConvertQCORXOp,
951+
ConvertQCORYOp, ConvertQCORZOp, ConvertQCOPOp, ConvertQCOROp,
952+
ConvertQCOU2Op, ConvertQCOUOp, ConvertQCOSWAPOp, ConvertQCOiSWAPOp,
953+
ConvertQCODCXOp, ConvertQCOECROp, ConvertQCORXXOp, ConvertQCORYYOp,
954+
ConvertQCORZXOp, ConvertQCORZZOp, ConvertQCOXXPlusYYOp,
955+
ConvertQCOXXMinusYYOp, ConvertQCOBarrierOp, ConvertQCOCtrlOp,
956+
ConvertQCOInvOp, ConvertQCOYieldOp>(typeConverter, context);
894957

895958
// Conversion of qco types in func.func signatures
896959
// Note: This currently has limitations with signature changes

0 commit comments

Comments
 (0)