Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e5280c5
✨ Add Euler decomposition and supporting decomposition primitives
simon1hofmann Apr 28, 2026
01ff591
🐇 Address Rabbit's Comments
simon1hofmann Apr 28, 2026
2c74981
📝 Update factorization documentation in EulerDecomposition
simon1hofmann Apr 28, 2026
b7307a3
📝 Update docs and comments
simon1hofmann Apr 29, 2026
e978f16
🐇 Address Rabbit's Comments
simon1hofmann Apr 29, 2026
d1a85d3
Merge branch 'main' into decomp/euler
denialhaag Apr 29, 2026
1d1ba72
Adapt to new include style
denialhaag Apr 29, 2026
3c7370d
Merge branch 'main' into decomp/euler
simon1hofmann May 19, 2026
ab35f91
✨ Add QCO UnitaryMatrixOpInterface for compile-time unitary matrices
simon1hofmann May 19, 2026
6cb4af2
♻️ Use UnitaryMatrixOpInterface in CtrlOp and InvOp matrix expansion
simon1hofmann May 19, 2026
abd87fc
♻️ Consolidate QCO Euler decomposition into Euler.h/cpp
simon1hofmann May 19, 2026
9c10cb6
✨ Add fuse-single-qubit-unitary-runs pass for Euler resynthesis
simon1hofmann May 19, 2026
54ca07d
✅ Add tests for Euler synthesis and fuse-single-qubit-unitary-runs
simon1hofmann May 19, 2026
1c01e71
📝 Document fuse-single-qubit-unitary-runs in CHANGELOG
simon1hofmann May 19, 2026
a92a2b6
🚨 Fix linter warnings
simon1hofmann May 19, 2026
577c094
🚨 Fix linter warnings
simon1hofmann May 19, 2026
6e7fa7f
🐇 Address Rabbit's comments
simon1hofmann May 19, 2026
ebc984f
Merge branch 'main' into decomp/euler
simon1hofmann Jun 2, 2026
418b17e
✅ Refactor QCO decomposition tests and update dependencies
simon1hofmann Jun 3, 2026
7da85cd
🎨 Refactor Euler basis handling in QCO transformations
simon1hofmann Jun 3, 2026
5e2cbdc
🎨 Refactor Euler decomposition operations in QCO
simon1hofmann Jun 3, 2026
8eb937c
🎨 Refactor matrix handling and wire start collection in QCO transform…
simon1hofmann Jun 3, 2026
e287b80
🎨 Enhance Euler decomposition in QCO
simon1hofmann Jun 3, 2026
20d5835
🚨 fix clang-tidy warnings
simon1hofmann Jun 3, 2026
1b541d3
🚨 fix clang-tidy warnings
simon1hofmann Jun 3, 2026
3d54872
⚡️Increase single qubit fusion performance
simon1hofmann Jun 4, 2026
a94b83f
Merge branch 'main' into decomp/euler
simon1hofmann Jun 4, 2026
608491f
🎨 Enhance WireIterator to include YieldOp in boundary checks
simon1hofmann Jun 4, 2026
98266bd
📝 Refine Euler decomposition and synthesis documentation
simon1hofmann Jun 4, 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Added

- ✨ Add a `fuse-single-qubit-unitary-runs` pass for fusing compile-time single-qubit unitary runs via Euler resynthesis ([#1672]) ([**@simon1hofmann**])
- 🚸 Add [CMake presets] to provide a standardized and reproducible way to configure builds ([#1660]) ([**@denialhaag**])
- ✨ Add a `quantum-loop-unroll` pass for unrolling for-loop operations containing quantum operations ([#1718]) ([**@MatthiasReumann**])
- ✨ Add a `hadamard-lifting` pass for lifting Hadamard gates above Pauli gates ([#1605]) ([**@lirem101**], [**@burgholzer**])
Expand Down Expand Up @@ -422,6 +423,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool
[#1675]: https://github.com/munich-quantum-toolkit/core/pull/1675
[#1674]: https://github.com/munich-quantum-toolkit/core/pull/1674
[#1673]: https://github.com/munich-quantum-toolkit/core/pull/1673
[#1672]: https://github.com/munich-quantum-toolkit/core/pull/1672
[#1664]: https://github.com/munich-quantum-toolkit/core/pull/1664
[#1662]: https://github.com/munich-quantum-toolkit/core/pull/1662
[#1660]: https://github.com/munich-quantum-toolkit/core/pull/1660
Expand Down
3 changes: 3 additions & 0 deletions mlir/include/mlir/Dialect/QCO/IR/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

add_mlir_dialect(QCOOps qco)
add_mlir_interface(QCOInterfaces)
add_mlir_interface(QCOUnitaryMatrixInterfaces)

add_mlir_doc(QCOOps QCODialect Dialects/ -gen-dialect-doc)
add_mlir_doc(QCOInterfaces QCOInterfaces Dialects/ -gen-op-interface-docs -dialect=qco)
add_mlir_doc(QCOUnitaryMatrixInterfaces QCOUnitaryMatrixInterfaces Dialects/ -gen-op-interface-docs
-dialect=qco)
1 change: 0 additions & 1 deletion mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#pragma once

#include <Eigen/Core>
#include <llvm/Support/ErrorHandling.h>
#include <mlir/IR/OpDefinition.h>
#include <mlir/Support/LLVM.h>
Expand Down
111 changes: 1 addition & 110 deletions mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -28,62 +28,6 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {

let cppNamespace = "::mlir::qco";

// Generic implementation body for getUnitaryMatrix methods
defvar unitaryMatrixMethodBody = [{
auto process = [&]<typename MatrixType>(MatrixType&& m) -> bool {
using TargetT = std::remove_cvref_t<decltype(out)>;
using SourceT = std::remove_cvref_t<MatrixType>;

constexpr bool isTargetDynamic =
(TargetT::SizeAtCompileTime == Eigen::Dynamic);
constexpr bool isSourceDynamic =
(SourceT::SizeAtCompileTime == Eigen::Dynamic);

// Case 1: Target is Dynamic. Always accepts source.
if constexpr (isTargetDynamic) {
out = std::forward<MatrixType>(m);
return true;
}
// Case 2: Target is Fixed.
else {
// Case 2a: Source is Dynamic. Runtime dimension check required.
if constexpr (isSourceDynamic) {
if (m.rows() == static_cast<Eigen::Index>(TargetT::RowsAtCompileTime) &&
m.cols() == static_cast<Eigen::Index>(TargetT::ColsAtCompileTime))
[[likely]] {
out = std::forward<MatrixType>(m);
return true;
}
}
// Case 2b: Source is Fixed. Compile-time check.
else if constexpr (static_cast<Eigen::Index>(
SourceT::RowsAtCompileTime) ==
static_cast<Eigen::Index>(
TargetT::RowsAtCompileTime) &&
static_cast<Eigen::Index>(
SourceT::ColsAtCompileTime) ==
static_cast<Eigen::Index>(
TargetT::ColsAtCompileTime)) {
out = std::forward<MatrixType>(m);
return true;
}
}
return false;
};


if constexpr (requires { $_op.getUnitaryMatrix().has_value(); }) {
if (auto&& matrix = $_op.getUnitaryMatrix()) {
return process(std::move(*matrix));
}
return false;
} else if constexpr (requires { $_op.getUnitaryMatrix(); }) {
return process($_op.getUnitaryMatrix());
} else {
llvm::reportFatalUsageError("Operation '" + $_op.getBaseSymbol() + "' has no unitary matrix definition!");
}
}];

let methods = [
// Qubit accessors
InterfaceMethod<
Expand Down Expand Up @@ -152,60 +96,7 @@ def UnitaryOpInterface : OpInterface<"UnitaryOpInterface"> {

// Identification
InterfaceMethod<"Returns the base symbol/mnemonic of the operation.",
"StringRef", "getBaseSymbol", (ins)>,

// Unitary matrix helpers
InterfaceMethod<"Populates the given 1x1 unitary matrix if possible.",
"bool", "getUnitaryMatrix1x1",
(ins "Eigen::Matrix<std::complex<double>, 1, 1>&":$out),
unitaryMatrixMethodBody>,
InterfaceMethod<"Populates the given 2x2 unitary matrix if possible.",
"bool", "getUnitaryMatrix2x2",
(ins "Eigen::Matrix2cd&":$out), unitaryMatrixMethodBody>,
InterfaceMethod<"Populates the given 4x4 unitary matrix if possible.",
"bool", "getUnitaryMatrix4x4",
(ins "Eigen::Matrix4cd&":$out), unitaryMatrixMethodBody>,
InterfaceMethod<"Populates the given dynamic unitary matrix.", "bool",
"getUnitaryMatrixDynamic", (ins "Eigen::MatrixXcd&":$out),
unitaryMatrixMethodBody>];

let extraClassDeclaration = [{
template<typename MatrixType>
std::optional<MatrixType> getUnitaryMatrix() {
MatrixType out;
bool result = false;

// Dispatch to the appropriate fixed-size or dynamic method based on the
// matrix type.
if constexpr (MatrixType::RowsAtCompileTime == 1 &&
MatrixType::ColsAtCompileTime == 1) {
result = this->getUnitaryMatrix1x1(out);
} else if constexpr (MatrixType::RowsAtCompileTime == 2 &&
MatrixType::ColsAtCompileTime == 2) {
result = this->getUnitaryMatrix2x2(out);
} else if constexpr (MatrixType::RowsAtCompileTime == 4 &&
MatrixType::ColsAtCompileTime == 4) {
result = this->getUnitaryMatrix4x4(out);
} else if constexpr (MatrixType::SizeAtCompileTime == Eigen::Dynamic) {
result = this->getUnitaryMatrixDynamic(out);
} else {
// Fallback: Try obtaining dynamic matrix and see if size matches
Eigen::MatrixXcd dynamicOut;
if (this->getUnitaryMatrixDynamic(dynamicOut)) {
if (dynamicOut.rows() == MatrixType::RowsAtCompileTime &&
dynamicOut.cols() == MatrixType::ColsAtCompileTime) {
out = dynamicOut;
result = true;
}
}
}

if (result) {
return out;
}
return std::nullopt;
}
}];
"StringRef", "getBaseSymbol", (ins)>];
}

#endif // MLIR_DIALECT_QCO_IR_QCOINTERFACES_TD
1 change: 1 addition & 0 deletions mlir/include/mlir/Dialect/QCO/IR/QCOOps.h
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to question the added value of the separate header a little bit and because I am curious: Does this really bring us a meaningful advantage in terms of not having to include Eigen (transitively) as often? How often do we only import the QCODialect or QCOInterfaces header, but not the QCOOps header?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question — I let Opus 4.8 dug into the actual include graph: TL;DR: the split buys us no measurable Eigen reduction today.

Where Eigen actually enters

Only one header pulls Eigen, and QCOOps.h hard-includes it:

  • QCODialect.h → no Eigen, doesn't reference the interfaces at all
  • QCOInterfaces.h → no Eigen (structural interfaces only)
  • QCOUnitaryMatrixInterfaces.h#include <Eigen/Core>
    So Eigen reaches a TU iff it pulls QCOUnitaryMatrixInterfaces.h, directly or via QCOOps.h.

How often is a header used without QCOOps.h?

Enumerating every includer across mlir/ (lib/, unittests/, tools/, headers):

Header Total includers …that do not also include QCOOps.h
QCOInterfaces.h 7 TUs (+2 headers) 0
QCODialect.h ~32 ~14

The decisive row is QCOInterfaces.h: every consumer also includes QCOOps.h (Drivers.h, FuseSingleQubitUnitaryRuns.cpp, HadamardLifting.cpp, MergeSingleQubitRotationGates.cpp, WireIterator.cpp, plus two tests). Since QCOOps.h unconditionally drags in QCOUnitaryMatrixInterfaces.h, splitting Eigen out of QCOInterfaces.h removes Eigen from zero translation units.
The QCODialect.h-only consumers (~14) never carried Eigen anyway, since QCODialect.h doesn't touch the matrix interface — so the split is irrelevant to them too.

Verdict

No, the separate header doesn't reduce transitive Eigen inclusion in practice. There's no TU that wants QCOInterfaces.h/QCODialect.h but can avoid QCOOps.h, and QCOOps.h must pull the Eigen-typed UnitaryMatrixOpInterface because the generated op classes implement it. Net effect: "use a QCO op ⇒ get Eigen."
What the split does give us (non-Eigen, worth keeping):

  • Keeps QCOInterfaces.h and its generated .h.inc Eigen-free, cleanly separating the structural interface (UnitaryOpInterface: wire/target/isSingleQubit) from the matrix interface.
  • Leaves the door open for a future structure-only consumer to stay Eigen-free — latent until such a consumer exists.

If the real goal is to cut Eigen's compile-time footprint, the lever isn't this split — it's making UnitaryMatrixOpInterface not expose Eigen types in its public signature (e.g. std::array<std::complex<double>, 4> at the boundary, Eigen confined to the .cpp). That would let QCOOps.h drop <Eigen/Core> entirely, which is where the actual cost is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. So the conclusion is that the split did not bring any advantages.. unfortunate.
We may want to investigate whether there are alternatives to this. It feels wrong to me to incur the include penalty on every translation unit even though very little passes will actually need the matrices.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include "mlir/Dialect/QCO/IR/QCODialect.h"
#include "mlir/Dialect/QCO/IR/QCOInterfaces.h"
#include "mlir/Dialect/QCO/IR/QCOUnitaryMatrixInterfaces.h"

#include <mlir/Bytecode/BytecodeOpInterface.h>
#include <mlir/Interfaces/ControlFlowInterfaces.h>
Expand Down
Loading
Loading