-
-
Notifications
You must be signed in to change notification settings - Fork 55
✨ Add Euler decomposition and supporting decomposition primitives #1672
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
e5280c5
01ff591
2c74981
b7307a3
e978f16
d1a85d3
1d1ba72
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 |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* | ||
| * 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 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "GateKind.h" | ||
|
|
||
| #include <llvm/ADT/SmallVector.h> | ||
| #include <mlir/Support/LLVM.h> | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| namespace mlir::qco::decomposition { | ||
| /** | ||
| * Default absolute tolerance used to treat small Euler angles as zero during | ||
| * simplification. | ||
| */ | ||
| inline constexpr auto DEFAULT_ATOL = 1e-12; | ||
|
|
||
| /** | ||
| * Supported single-qubit Euler-style output bases. | ||
| * | ||
| * The listed values describe the gate alphabet that `EulerDecomposition` | ||
| * targets when converting a 2x2 unitary into a `OneQubitGateSequence`. | ||
| * Several entries share the angle-extraction routine and only differ in how | ||
| * the final circuit is emitted (e.g. `U3` vs `U321`, or `ZSX` vs `ZSXX`). | ||
| */ | ||
| enum class EulerBasis : std::uint8_t { | ||
| U3 = 0, ///< Single `u(theta, phi, lambda)` gate. | ||
| U321 = 1, ///< `u1`/`u2`/`u3` family — picks the smallest form per angles. | ||
|
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. In order to keep things simple, let's drop the |
||
| U = 2, ///< Same ZYZ angle extraction as `U3`, emitted as a single `u`. | ||
|
Comment on lines
+36
to
+38
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. U3 and U are just the same aren't they? Can we remove one? |
||
| ZYZ = 3, ///< `rz · ry · rz`. | ||
| ZXZ = 4, ///< `rz · rx · rz`. | ||
| XZX = 5, ///< `rx · rz · rx`. | ||
| XYX = 6, ///< `rx · ry · rx`. | ||
| ZSXX = 7, ///< `rz · sx` chain, with `sx · rz(+/- pi) · sx` collapsed to `x`. | ||
| ZSX = 8, ///< Like `ZSXX` but without the `x` shortcut. | ||
|
Comment on lines
+43
to
+44
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. These docstrings feel off compared to the rest. |
||
| }; | ||
|
|
||
| /** | ||
| * Return the gate types that may appear in a circuit emitted for `eulerBasis`. | ||
| * | ||
| * The result describes the basis alphabet, not the exact gate count. Some | ||
| * decompositions emit fewer than three gates after simplification. | ||
| */ | ||
| [[nodiscard]] SmallVector<GateKind> | ||
| getGateTypesForEulerBasis(EulerBasis eulerBasis); | ||
|
Comment on lines
+47
to
+54
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. This function feels like it is not necessary now and depending on the resolution for the other comments, might not be necessary at all. |
||
|
|
||
| } // namespace mlir::qco::decomposition | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * 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 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "EulerBasis.h" | ||
| #include "GateSequence.h" | ||
|
|
||
| #include <Eigen/Core> | ||
|
|
||
| #include <array> | ||
| #include <optional> | ||
|
|
||
| namespace mlir::qco::decomposition { | ||
|
|
||
| /** | ||
| * Decompose a single-qubit unitary into a selected Euler-style gate basis. | ||
| * | ||
| * The returned sequence tracks both the emitted gates and the scalar phase | ||
| * needed to reconstruct the input matrix exactly. This is stronger than the | ||
| * usual "up to global phase" contract and is relied on by downstream | ||
| * canonicalization and testing code. | ||
| */ | ||
| class EulerDecomposition { | ||
| public: | ||
| /** | ||
| * Decompose a 2x2 unitary into the gate alphabet described by | ||
| * `targetBasis`. | ||
| * | ||
| * When `simplify` is true, near-zero angles are removed using `atol` (or | ||
| * `DEFAULT_ATOL` if no override is provided). The returned global phase keeps | ||
| * the decomposition exactly equal to `unitaryMatrix`. | ||
| */ | ||
| [[nodiscard]] static OneQubitGateSequence | ||
| generateCircuit(EulerBasis targetBasis, const Eigen::Matrix2cd& unitaryMatrix, | ||
| bool simplify, std::optional<double> atol); | ||
|
Comment on lines
+33
to
+43
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 can't help but feel that this should be a pattern rewrite rule. This doesn't really feel "MLIR" yet. |
||
|
|
||
| /** | ||
| * Extract canonical Euler parameters for `matrix` in the requested basis. | ||
| * | ||
| * Some target bases reuse the same parameter extraction routine and differ | ||
| * only during circuit emission. The returned array always contains | ||
| * `(theta, phi, lambda, phase)` in this order. | ||
| */ | ||
| [[nodiscard]] static std::array<double, 4> | ||
| anglesFromUnitary(const Eigen::Matrix2cd& matrix, EulerBasis basis); | ||
|
|
||
| private: | ||
| /// Extract parameters for a `rz(phi) · ry(theta) · rz(lambda)` factorization. | ||
| [[nodiscard]] static std::array<double, 4> | ||
| paramsZyz(const Eigen::Matrix2cd& matrix); | ||
|
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. Should functions like these be stylized as |
||
|
|
||
| /// Extract parameters for a `rz(phi) · rx(theta) · rz(lambda)` factorization. | ||
| [[nodiscard]] static std::array<double, 4> | ||
| paramsZxz(const Eigen::Matrix2cd& matrix); | ||
|
|
||
| /// Extract parameters for a `rx(phi) · ry(theta) · rx(lambda)` factorization. | ||
| [[nodiscard]] static std::array<double, 4> | ||
| paramsXyx(const Eigen::Matrix2cd& matrix); | ||
|
|
||
| /// Extract parameters for a `rx(phi) · rz(theta) · rx(lambda)` factorization. | ||
| [[nodiscard]] static std::array<double, 4> | ||
| paramsXzx(const Eigen::Matrix2cd& matrix); | ||
|
|
||
| /** | ||
| * Extract parameters for a `u1`/`p` + `sx` factorization. | ||
| * | ||
| * The returned angles are identical to `paramsZyz` but the phase is shifted | ||
| * by `-0.5 * (theta + phi + lambda)` so that the `rz`/`sx` circuits emitted | ||
| * by `decomposePsxGen` match the input matrix exactly (not only up to a | ||
| * global phase). | ||
| * | ||
| * @note Adapted from `params_u1x_inner` in the IBM Qiskit framework. | ||
| * (C) Copyright IBM 2022 | ||
| * | ||
| * This code is licensed under the Apache License, Version 2.0. You may | ||
| * obtain a copy of this license in the LICENSE.txt file in the root | ||
| * directory of this source tree or at | ||
| * https://www.apache.org/licenses/LICENSE-2.0. | ||
| * | ||
| * Any modifications or derivative works of this code must retain this | ||
| * copyright notice, and modified files need to carry a notice | ||
| * indicating that they have been altered from the originals. | ||
| */ | ||
| [[nodiscard]] static std::array<double, 4> | ||
| paramsU1x(const Eigen::Matrix2cd& matrix); | ||
|
|
||
| /** | ||
| * Emit a K-A-K circuit from already extracted Euler parameters. | ||
| * | ||
| * `kGate` is used for the outer rotations and `aGate` for the middle | ||
| * rotation. | ||
| * | ||
| * @note Adapted from circuit_kak() in the IBM Qiskit framework. | ||
| * (C) Copyright IBM 2022 | ||
| * | ||
| * This code is licensed under the Apache License, Version 2.0. You may | ||
| * obtain a copy of this license in the LICENSE.txt file in the root | ||
| * directory of this source tree or at | ||
| * https://www.apache.org/licenses/LICENSE-2.0. | ||
| * | ||
| * Any modifications or derivative works of this code must retain this | ||
| * copyright notice, and modified files need to carry a notice | ||
| * indicating that they have been altered from the originals. | ||
| */ | ||
| [[nodiscard]] static OneQubitGateSequence | ||
| decomposeKAK(double theta, double phi, double lambda, double phase, | ||
| GateKind kGate, GateKind aGate, bool simplify, | ||
| std::optional<double> atol); | ||
|
|
||
| /** | ||
| * Emit an `rz`/`sx`-style circuit for the `ZSX` and `ZSXX` bases. | ||
| * | ||
| * The emitted sequence is structurally identical to the one produced by | ||
| * Qiskit's `circuit_psx_gen`. When `simplify` is enabled the number of `sx` | ||
| * gates shrinks based on `theta`: zero `sx` gates for `theta ~= 0`, one | ||
| * `sx` gate for `theta ~= pi/2`, and two `sx` gates otherwise. | ||
| * | ||
| * When `allowXShortcut` is true (i.e. for `ZSXX`), the general-case 2-`sx` | ||
| * path additionally collapses `sx · rz(+/- pi) · sx` into a single `x` | ||
| * gate when the middle rotation is congruent to +/- pi modulo 2 pi. | ||
| * | ||
| * @note Adapted from `circuit_psx_gen` in the IBM Qiskit framework. | ||
| * (C) Copyright IBM 2022 | ||
| * | ||
| * This code is licensed under the Apache License, Version 2.0. You | ||
| * may obtain a copy of this license in the LICENSE.txt file in the | ||
| * root directory of this source tree or at | ||
| * https://www.apache.org/licenses/LICENSE-2.0. | ||
| * | ||
| * Any modifications or derivative works of this code must retain | ||
| * this copyright notice, and modified files need to carry a notice | ||
| * indicating that they have been altered from the originals. | ||
| */ | ||
| [[nodiscard]] static OneQubitGateSequence | ||
| decomposePsxGen(double theta, double phi, double lambda, double phase, | ||
| bool allowXShortcut, bool simplify, | ||
| std::optional<double> atol); | ||
|
Comment on lines
+142
to
+145
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. In other parts of the library, the tolerance is passed as a double with a suitable default. The interfaces should be consistent across the library. I'd have a personal preference for avoiding optional here. |
||
| }; | ||
| } // namespace mlir::qco::decomposition | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
| * 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 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "GateKind.h" | ||
|
|
||
| #include <llvm/ADT/SmallVector.h> | ||
| #include <mlir/Support/LLVM.h> | ||
|
|
||
| #include <cstddef> | ||
|
|
||
| namespace mlir::qco::decomposition { | ||
|
|
||
| using QubitId = std::size_t; | ||
|
|
||
| /** | ||
| * Lightweight decomposition-time gate record. | ||
| * | ||
| * This struct is intentionally independent from MLIR operations so helper code | ||
| * can build and manipulate abstract one- and two-qubit circuits before they | ||
| * are materialized back into the IR. | ||
| */ | ||
| struct Gate { | ||
| /// Operation kind represented by this gate. | ||
| GateKind type{GateKind::I}; | ||
|
|
||
| /// Gate parameters in operation-specific order. | ||
| SmallVector<double> parameter; | ||
|
|
||
| /// Logical qubit ids used by the gate, in operand order. | ||
| SmallVector<QubitId> qubitId = {0}; | ||
| }; | ||
|
Comment on lines
+24
to
+40
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 would have hoped that we can implement the decomposition logic itself without adding a dedicated class that duplicates information. |
||
|
|
||
| } // namespace mlir::qco::decomposition | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * 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 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <cstdint> | ||
|
|
||
| namespace mlir::qco::decomposition { | ||
|
|
||
| /** | ||
| * Lightweight gate identifiers used by decomposition utilities. | ||
| * | ||
| * These kinds intentionally stay independent from the core IR `qc::OpType` | ||
| * enum so the MLIR/QCO decomposition layer does not depend on the `ir` | ||
| * package. | ||
| */ | ||
| enum class GateKind : std::uint8_t { | ||
| I = 0, | ||
| H, | ||
| P, | ||
| U, | ||
| U2, | ||
| X, | ||
| Y, | ||
| Z, | ||
| SX, | ||
| RX, | ||
| RY, | ||
| RZ, | ||
| R, | ||
| RXX, | ||
| RYY, | ||
| RZZ, | ||
| GPhase, | ||
| }; | ||
|
Comment on lines
+17
to
+42
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 would hope that we do not need this kind of enumeration as we can simply reuse the type information of the MLIR operations. I'd really like to avoid adding too many separate enumerations and classes for concepts that are already well represented in the IR. |
||
|
|
||
| } // namespace mlir::qco::decomposition | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| /* | ||
| * 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 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include "Gate.h" | ||
|
|
||
| #include <Eigen/Core> | ||
| #include <llvm/ADT/SmallVector.h> | ||
| #include <mlir/Support/LLVM.h> | ||
|
|
||
| #include <cstddef> | ||
|
|
||
| namespace mlir::qco::decomposition { | ||
|
|
||
| /** | ||
| * Sequence of abstract decomposition gates plus a residual global phase. | ||
| * | ||
| * `gates` is stored in execution order: for a column state vector, the first | ||
| * gate in the vector is applied first. The reconstructed 4x4 unitary | ||
| * is therefore `U = e^{i * phi} * M_{n-1} * ... * M_0`, where `M_i` is the | ||
| * two-qubit matrix for `gates[i]` and `phi` is `globalPhase` in radians (via | ||
| * `helpers::globalPhaseFactor`). | ||
| */ | ||
| struct QubitGateSequence { | ||
| /// Gates in execution order (see struct comment). | ||
| SmallVector<Gate> gates; | ||
|
|
||
| /// Residual global phase in radians, not represented by explicit gates. | ||
| double globalPhase{}; | ||
|
|
||
| /// True when `std::abs(globalPhase)` exceeds `DEFAULT_ATOL` in | ||
| /// `EulerBasis.h`. | ||
| [[nodiscard]] bool hasGlobalPhase() const; | ||
|
|
||
| /// Heuristic complexity from `helpers::getComplexity()` for each gate, plus a | ||
| /// synthetic global-phase term when `hasGlobalPhase()` is true. | ||
| [[nodiscard]] std::size_t complexity() const; | ||
|
|
||
| /** | ||
| * Reconstruct the overall two-qubit unitary represented by the sequence. | ||
| * | ||
| * Single-qubit gates are expanded to the two-qubit workspace convention used | ||
| * throughout the decomposition utilities. | ||
| */ | ||
| [[nodiscard]] Eigen::Matrix4cd getUnitaryMatrix() const; | ||
| }; | ||
|
|
||
|
Comment on lines
+22
to
+55
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. As you probably already gathered from the other comments, I am not too big of a fan of adding so many extra classes and structs here and I would rather explore the "MLIR-way" of doing this. A couple of thoughts on that
|
||
| /// Documents intent only; same type as `QubitGateSequence`. | ||
| /// `QubitGateSequence::getUnitaryMatrix()` still returns an `Eigen::Matrix4cd` | ||
| /// in the shared two-qubit workspace convention, even for one-qubit sequences. | ||
| using OneQubitGateSequence = QubitGateSequence; | ||
|
Comment on lines
+56
to
+59
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. Reads like LLM slop |
||
|
|
||
| } // namespace mlir::qco::decomposition | ||
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.
We already define a default tolerance in
mlir/include/mlir/Dialect/Utils/Utils.h. Does it make sense to define a new one here?