Skip to content

Commit ca031be

Browse files
simon1hofmanntaminobdenialhaagCopilotburgholzer
authored
✨ Add QCO Euler synthesis and fuse-single-qubit-unitary-runs pass (#1672)
## Description - Consolidate Euler decomposition into `Euler.h` / `Euler.cpp` (angle extraction + `synthesizeUnitary1QEuler` IR emission), replacing the previous `EulerDecomposition` / `GateSequence` / `Helpers` / `UnitaryMatrices` split. - Add the `fuse-single-qubit-unitary-runs` MLIR pass: fuse maximal runs of constant single-qubit unitaries on a wire by composing 2×2 matrices and resynthesizing in a configurable Euler basis (`zyz`, `zxz`, `xzx`, `xyx`, `u`, `zsxx`), with `gphase` for exact global phase. Split up and refactored from #1665. ## AI Assistance Used Composer via Cursor for parts of this change. I reviewed the full diff and take responsibility for everything in this PR. ## Checklist <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [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. - [x] 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. **If PR contains AI-assisted content:** - [x] I have disclosed the use of AI tools in the PR description as per our [AI Usage Guidelines](https://github.com/munich-quantum-toolkit/core/blob/main/docs/ai_usage.md). - [ ] AI-assisted commits include an `Assisted-by: [Model Name] via [Tool Name]` footer. - [x] I confirm that I have personally reviewed and understood all AI-generated content, and accept full responsibility for it. --------- Signed-off-by: simon1hofmann <119581649+simon1hofmann@users.noreply.github.com> Signed-off-by: burgholzer <burgholzer@me.com> Signed-off-by: Lukas Burgholzer <burgholzer@me.com> Co-authored-by: Tamino Bauknecht <dev@tb6.eu> Co-authored-by: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: burgholzer <burgholzer@me.com>
1 parent 6fe1f51 commit ca031be

24 files changed

Lines changed: 2471 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ with the exception that minor releases may include breaking changes.
1212

1313
### Added
1414

15+
- ✨ Add a `fuse-single-qubit-unitary-runs` pass
16+
for fusing compile-time single-qubit unitary runs via Euler resynthesis
17+
([#1672]) ([**@simon1hofmann**], [**@burgholzer**])
1518
- ✨ Add QIR program format support to the DDSIM QDMI Device ([#1766])
1619
([**@rturrado**])
1720
- 🚸 Add [CMake presets] to provide a standardized
@@ -621,6 +624,7 @@ changelogs._
621624
[#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675
622625
[#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674
623626
[#1673]: https://github.com/munich-quantum-toolkit/core/pull/1673
627+
[#1672]: https://github.com/munich-quantum-toolkit/core/pull/1672
624628
[#1664]: https://github.com/munich-quantum-toolkit/core/pull/1664
625629
[#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662
626630
[#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660

mlir/include/mlir/Dialect/QCO/IR/QCODialect.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#pragma once
1212

13+
#include "mlir/Dialect/Utils/Utils.h"
14+
1315
#include <llvm/ADT/STLExtras.h>
1416
#include <mlir/Dialect/Func/IR/FuncOps.h>
1517
#include <mlir/IR/Attributes.h>
@@ -127,6 +129,16 @@ template <size_t T, size_t P> class TargetAndParameterArityTrait {
127129
llvm::reportFatalUsageError(
128130
"Given qubit is not an input of the operation");
129131
}
132+
133+
[[nodiscard]] bool hasCompileTimeKnownUnitaryMatrix() {
134+
if constexpr (P == 0) {
135+
return true;
136+
} else {
137+
return llvm::all_of(this->getParameters(), [](Value param) {
138+
return utils::valueToDouble(param).has_value();
139+
});
140+
}
141+
}
130142
};
131143
};
132144

@@ -151,8 +163,9 @@ inline func::FuncOp getEntryPoint(ModuleOp op) {
151163
};
152164

153165
for (auto func : op.getOps<func::FuncOp>()) {
154-
const auto passthrough = func->getAttrOfType<ArrayAttr>(PASSTHROUGH_LABEL);
155-
if (passthrough && llvm::any_of(passthrough, isEntry)) {
166+
if (const auto passthrough =
167+
func->getAttrOfType<ArrayAttr>(PASSTHROUGH_LABEL);
168+
passthrough && llvm::any_of(passthrough, isEntry)) {
156169
return func;
157170
}
158171
}

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {
3939
return true;
4040
}
4141
return false;
42+
} else if constexpr (std::is_same_v<ValueT, DynamicMatrix>) {
43+
if (auto matrix = $_op.getUnitaryMatrix()) {
44+
return out.assignFrom(*matrix);
45+
}
46+
return false;
4247
} else {
4348
return false;
4449
}
@@ -63,6 +68,11 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {
6368
return true;
6469
}
6570
return false;
71+
} else if constexpr (std::is_same_v<ValueT, DynamicMatrix>) {
72+
if (auto matrix = $_op.getUnitaryMatrix()) {
73+
return out.assignFrom(*matrix);
74+
}
75+
return false;
6676
} else {
6777
return false;
6878
}
@@ -87,6 +97,11 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {
8797
return true;
8898
}
8999
return false;
100+
} else if constexpr (std::is_same_v<ValueT, DynamicMatrix>) {
101+
if (auto matrix = $_op.getUnitaryMatrix()) {
102+
return out.assignFrom(*matrix);
103+
}
104+
return false;
90105
} else {
91106
return false;
92107
}
@@ -188,6 +203,9 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {
188203
"StringRef", "getBaseSymbol", (ins)>,
189204

190205
// Unitary matrix helpers
206+
InterfaceMethod<"Returns true if the operation has a compile-time known "
207+
"unitary matrix representation, false otherwise.",
208+
"bool", "hasCompileTimeKnownUnitaryMatrix", (ins)>,
191209
InterfaceMethod<"Populates the given 1x1 unitary matrix if possible.",
192210
"bool", "getUnitaryMatrix1x1", (ins "Matrix1x1&":$out),
193211
unitaryMatrix1x1MethodBody>,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,8 @@ def BarrierOp : QCOOp<"barrier", traits = [UnitaryOpInterface]> {
10301030
static Value getParameter(size_t i) { llvm::reportFatalUsageError("BarrierOp has no parameters"); }
10311031
static OperandRange getParameters() { return {nullptr, 0}; }
10321032
[[nodiscard]] static StringRef getBaseSymbol() { return "barrier"; }
1033+
[[nodiscard]] bool hasCompileTimeKnownUnitaryMatrix() const { return true; }
1034+
[[nodiscard]] DynamicMatrix getUnitaryMatrix();
10331035
}];
10341036

10351037
let builders = [OpBuilder<(ins "ValueRange":$qubits)>];
@@ -1126,6 +1128,7 @@ def CtrlOp : QCOOp<"ctrl",
11261128
Value getParameter(size_t i) { llvm::reportFatalUsageError("CtrlOp does not have parameters"); }
11271129
OperandRange getParameters() { return {nullptr, 0}; }
11281130
[[nodiscard]] static StringRef getBaseSymbol() { return "ctrl"; }
1131+
[[nodiscard]] bool hasCompileTimeKnownUnitaryMatrix();
11291132
[[nodiscard]] std::optional<DynamicMatrix> getUnitaryMatrix();
11301133
}];
11311134

@@ -1199,6 +1202,7 @@ def InvOp : QCOOp<"inv", traits = [UnitaryOpInterface,
11991202
Value getParameter(size_t i) { llvm::reportFatalUsageError("InvOp does not have parameters"); }
12001203
OperandRange getParameters() { return {nullptr, 0}; }
12011204
[[nodiscard]] static StringRef getBaseSymbol() { return "inv"; }
1205+
[[nodiscard]] bool hasCompileTimeKnownUnitaryMatrix();
12021206
[[nodiscard]] std::optional<DynamicMatrix> getUnitaryMatrix();
12031207
}];
12041208

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
4+
* All rights reserved.
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*
8+
* Licensed under the MIT License
9+
*/
10+
11+
#pragma once
12+
13+
#include "mlir/Dialect/QCO/Utils/Matrix.h"
14+
15+
#include <mlir/IR/Builders.h>
16+
#include <mlir/IR/Location.h>
17+
#include <mlir/Support/LLVM.h>
18+
19+
#include <cstddef>
20+
#include <cstdint>
21+
#include <optional>
22+
23+
namespace mlir::qco::decomposition {
24+
25+
/**
26+
* @brief Native gate sets for single-qubit Euler synthesis.
27+
*/
28+
enum class EulerBasis : std::uint8_t {
29+
ZYZ = 0, ///< `RZ(phi) * RY(theta) * RZ(lambda)`.
30+
ZXZ = 1, ///< `RZ(phi) * RX(theta) * RZ(lambda)`.
31+
XZX = 2, ///< `RX(phi) * RZ(theta) * RX(lambda)`.
32+
XYX = 3, ///< `RX(phi) * RY(theta) * RX(lambda)`.
33+
U = 4, ///< `U(theta, phi, lambda)`.
34+
ZSXX = 5, ///< `RZ` / `SX` / `X` synthesis via ZYZ decomposition.
35+
};
36+
37+
/**
38+
* @brief Parses a basis name (e.g. `zyz`, `zsxx`; case-insensitive).
39+
*
40+
* @param basis The basis name.
41+
* @return The parsed basis, or `std::nullopt` if unrecognized.
42+
*/
43+
[[nodiscard]] std::optional<EulerBasis> parseEulerBasis(StringRef basis);
44+
45+
/**
46+
* @brief Synthesizes a composed single-qubit unitary as gates in @p basis.
47+
*
48+
* Returns `std::nullopt` when @p hasNonBasisGate is false and resynthesis
49+
* would not shorten a run of @p runSize gates; otherwise emits gates
50+
* (including `qco.gphase` when needed).
51+
*
52+
* @param builder Builder for the emitted operations.
53+
* @param loc Location for the emitted operations.
54+
* @param qubit Input qubit value.
55+
* @param composed Composed unitary to synthesize.
56+
* @param runSize Number of gates in the run.
57+
* @param hasNonBasisGate Whether the run contains a gate outside @p basis.
58+
* @param basis The target Euler basis.
59+
* @return The synthesized qubit, or `std::nullopt` if synthesis is skipped.
60+
*/
61+
[[nodiscard]] std::optional<Value>
62+
synthesizeUnitary1QEuler(OpBuilder& builder, Location loc, Value qubit,
63+
const Matrix2x2& composed, std::size_t runSize,
64+
bool hasNonBasisGate, EulerBasis basis);
65+
66+
} // namespace mlir::qco::decomposition

mlir/include/mlir/Dialect/QCO/Transforms/Passes.td

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,32 @@ def MergeSingleQubitRotationGates
4040
}];
4141
}
4242

43+
def FuseSingleQubitUnitaryRuns
44+
: Pass<"fuse-single-qubit-unitary-runs", "mlir::ModuleOp"> {
45+
let dependentDialects = ["mlir::qco::QCODialect",
46+
"::mlir::arith::ArithDialect",
47+
"::mlir::qtensor::QTensorDialect"];
48+
let summary = "Fuse single-qubit unitary runs using Euler resynthesis";
49+
let description = [{
50+
Matches maximal runs of consecutive single-qubit unitary operations on the
51+
same qubit wire (anchored at each run head), composes their constant unitary
52+
matrices, and replaces a run with an equivalent sequence of basis gates when
53+
beneficial: when the run contains a gate outside the target `basis`, or when
54+
Euler resynthesis would shorten it (`synthesizeUnitary1QEuler`). Runs that are
55+
already in the target `basis` and no shorter than the canonical synthesis
56+
length are left unchanged.
57+
58+
The emitted basis is controlled via the `basis` option (e.g. `zyz`, `zsxx`).
59+
A `gphase` correction is inserted when needed so the rewritten sequence
60+
matches the composed matrix exactly (not only up to global phase).
61+
62+
Currently, only operations whose unitary matrix can be obtained at compile
63+
time are fused.
64+
}];
65+
let options = [Option<"basis", "basis", "std::string", "\"zyz\"",
66+
"Target Euler basis (zyz, zxz, xzx, xyx, u, zsxx).">];
67+
}
68+
4369
def QuantumLoopUnroll
4470
: InterfacePass<"quantum-loop-unroll", "FunctionOpInterface"> {
4571
let dependentDialects = ["mlir::qco::QCODialect", "mlir::scf::SCFDialect"];

0 commit comments

Comments
 (0)