Skip to content

Commit e5280c5

Browse files
✨ Add Euler decomposition and supporting decomposition primitives
Co-authored-by: Tamino Bauknecht <dev@tb6.eu>
1 parent d7ced80 commit e5280c5

17 files changed

Lines changed: 1559 additions & 0 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 "GateKind.h"
14+
15+
#include <llvm/ADT/SmallVector.h>
16+
17+
#include <cstdint>
18+
19+
namespace mlir::qco::decomposition {
20+
/**
21+
* Default absolute tolerance used to treat small Euler angles as zero during
22+
* simplification.
23+
*/
24+
inline constexpr auto DEFAULT_ATOL = 1e-12;
25+
26+
/**
27+
* Supported single-qubit Euler-style output bases.
28+
*
29+
* The listed values describe the gate alphabet that `EulerDecomposition`
30+
* targets when converting a 2x2 unitary into a `OneQubitGateSequence`.
31+
* Several entries share the angle-extraction routine and only differ in how
32+
* the final circuit is emitted (e.g. `U3` vs `U321`, or `ZSX` vs `ZSXX`).
33+
*/
34+
enum class EulerBasis : std::uint8_t {
35+
U3 = 0, ///< Single `u(theta, phi, lambda)` gate.
36+
U321 = 1, ///< `u1`/`u2`/`u3` family — picks the smallest form per angles.
37+
U = 2, ///< Same ZYZ angle extraction as `U3`, emitted as a single `u`.
38+
ZYZ = 3, ///< `rz · ry · rz`.
39+
ZXZ = 4, ///< `rz · rx · rz`.
40+
XZX = 5, ///< `rx · rz · rx`.
41+
XYX = 6, ///< `rx · ry · rx`.
42+
ZSXX = 7, ///< `rz · sx` chain, with `sx · rz(±π) · sx` collapsed to `x`.
43+
ZSX = 8, ///< Like `ZSXX` but without the `x` shortcut.
44+
};
45+
46+
/**
47+
* Return the gate types that may appear in a circuit emitted for `eulerBasis`.
48+
*
49+
* The result describes the basis alphabet, not the exact gate count. Some
50+
* decompositions emit fewer than three gates after simplification.
51+
*/
52+
[[nodiscard]] llvm::SmallVector<GateKind, 3>
53+
getGateTypesForEulerBasis(EulerBasis eulerBasis);
54+
55+
} // namespace mlir::qco::decomposition
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 "EulerBasis.h"
14+
#include "GateSequence.h"
15+
16+
#include <Eigen/Core>
17+
18+
#include <array>
19+
#include <optional>
20+
21+
namespace mlir::qco::decomposition {
22+
23+
/**
24+
* Decompose a single-qubit unitary into a selected Euler-style gate basis.
25+
*
26+
* The returned sequence tracks both the emitted gates and the scalar phase
27+
* needed to reconstruct the input matrix exactly. This is stronger than the
28+
* usual "up to global phase" contract and is relied on by downstream
29+
* canonicalization and testing code.
30+
*/
31+
class EulerDecomposition {
32+
public:
33+
/**
34+
* Decompose a 2x2 unitary into the gate alphabet described by
35+
* `targetBasis`.
36+
*
37+
* When `simplify` is true, near-zero angles are removed using `atol` (or
38+
* `DEFAULT_ATOL` if no override is provided). The returned global phase keeps
39+
* the decomposition exactly equal to `unitaryMatrix`.
40+
*/
41+
[[nodiscard]] static OneQubitGateSequence
42+
generateCircuit(EulerBasis targetBasis, const Eigen::Matrix2cd& unitaryMatrix,
43+
bool simplify, std::optional<double> atol);
44+
45+
/**
46+
* Extract canonical Euler parameters for `matrix` in the requested basis.
47+
*
48+
* Some target bases reuse the same parameter extraction routine and differ
49+
* only during circuit emission. The returned array always contains
50+
* `(theta, phi, lambda, phase)` in this order.
51+
*/
52+
[[nodiscard]] static std::array<double, 4>
53+
anglesFromUnitary(const Eigen::Matrix2cd& matrix, EulerBasis basis);
54+
55+
private:
56+
/// Extract parameters for a `RZ(phi) RY(theta) RZ(lambda)` factorization.
57+
[[nodiscard]] static std::array<double, 4>
58+
paramsZyz(const Eigen::Matrix2cd& matrix);
59+
60+
/// Extract parameters for a `RZ(phi) RX(theta) RZ(lambda)` factorization.
61+
[[nodiscard]] static std::array<double, 4>
62+
paramsZxz(const Eigen::Matrix2cd& matrix);
63+
64+
/// Extract parameters for a `RX(phi) RY(theta) RX(lambda)` factorization.
65+
[[nodiscard]] static std::array<double, 4>
66+
paramsXyx(const Eigen::Matrix2cd& matrix);
67+
68+
/// Extract parameters for a `RX(phi) RZ(theta) RX(lambda)` factorization.
69+
[[nodiscard]] static std::array<double, 4>
70+
paramsXzx(const Eigen::Matrix2cd& matrix);
71+
72+
/**
73+
* Extract parameters for a `u1`/`p` + `sx` factorization.
74+
*
75+
* The returned angles are identical to `paramsZyz` but the phase is shifted
76+
* by `-0.5 * (theta + phi + lambda)` so that the `rz`/`sx` circuits emitted
77+
* by `decomposePsxGen` match the input matrix exactly (not only up to a
78+
* global phase).
79+
*
80+
* @note Adapted from `params_u1x_inner` in the IBM Qiskit framework.
81+
* (C) Copyright IBM 2022
82+
*
83+
* This code is licensed under the Apache License, Version 2.0. You may
84+
* obtain a copy of this license in the LICENSE.txt file in the root
85+
* directory of this source tree or at
86+
* https://www.apache.org/licenses/LICENSE-2.0.
87+
*
88+
* Any modifications or derivative works of this code must retain this
89+
* copyright notice, and modified files need to carry a notice
90+
* indicating that they have been altered from the originals.
91+
*/
92+
[[nodiscard]] static std::array<double, 4>
93+
paramsU1x(const Eigen::Matrix2cd& matrix);
94+
95+
/**
96+
* Emit a K-A-K circuit from already extracted Euler parameters.
97+
*
98+
* `kGate` is used for the outer rotations and `aGate` for the middle
99+
* rotation.
100+
*
101+
* @note Adapted from circuit_kak() in the IBM Qiskit framework.
102+
* (C) Copyright IBM 2022
103+
*
104+
* This code is licensed under the Apache License, Version 2.0. You may
105+
* obtain a copy of this license in the LICENSE.txt file in the root
106+
* directory of this source tree or at
107+
* https://www.apache.org/licenses/LICENSE-2.0.
108+
*
109+
* Any modifications or derivative works of this code must retain this
110+
* copyright notice, and modified files need to carry a notice
111+
* indicating that they have been altered from the originals.
112+
*/
113+
[[nodiscard]] static OneQubitGateSequence
114+
decomposeKAK(double theta, double phi, double lambda, double phase,
115+
GateKind kGate, GateKind aGate, bool simplify,
116+
std::optional<double> atol);
117+
118+
/**
119+
* Emit an `rz`/`sx`-style circuit for the `ZSX` and `ZSXX` bases.
120+
*
121+
* The emitted sequence is structurally identical to the one produced by
122+
* Qiskit's `circuit_psx_gen`. When `simplify` is enabled the number of `sx`
123+
* gates shrinks based on `theta`: zero `sx` gates for `theta ~= 0`, one
124+
* `sx` gate for `theta ~= pi/2`, and two `sx` gates otherwise.
125+
*
126+
* When `allowXShortcut` is true (i.e. for `ZSXX`), the general-case 2-`sx`
127+
* path additionally collapses `sx . rz(+/- pi) . sx` into a single `x`
128+
* gate when the middle rotation is congruent to +/- pi modulo 2 pi.
129+
*
130+
* @note Adapted from `circuit_psx_gen` in the IBM Qiskit framework.
131+
* (C) Copyright IBM 2022
132+
*
133+
* This code is licensed under the Apache License, Version 2.0. You
134+
* may obtain a copy of this license in the LICENSE.txt file in the
135+
* root directory of this source tree or at
136+
* https://www.apache.org/licenses/LICENSE-2.0.
137+
*
138+
* Any modifications or derivative works of this code must retain
139+
* this copyright notice, and modified files need to carry a notice
140+
* indicating that they have been altered from the originals.
141+
*/
142+
[[nodiscard]] static OneQubitGateSequence
143+
decomposePsxGen(double theta, double phi, double lambda, double phase,
144+
bool allowXShortcut, bool simplify,
145+
std::optional<double> atol);
146+
};
147+
} // namespace mlir::qco::decomposition
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 "GateKind.h"
14+
15+
#include <llvm/ADT/SmallVector.h>
16+
17+
#include <cstddef>
18+
19+
namespace mlir::qco::decomposition {
20+
21+
using QubitId = std::size_t;
22+
23+
/**
24+
* Lightweight decomposition-time gate record.
25+
*
26+
* This struct is intentionally independent from MLIR operations so helper code
27+
* can build and manipulate abstract one- and two-qubit circuits before they
28+
* are materialized back into the IR.
29+
*/
30+
struct Gate {
31+
/// Operation kind represented by this gate.
32+
GateKind type{GateKind::I};
33+
34+
/// Gate parameters in operation-specific order.
35+
llvm::SmallVector<double, 3> parameter;
36+
37+
/// Logical qubit ids used by the gate, in operand order.
38+
llvm::SmallVector<QubitId, 2> qubitId = {0};
39+
};
40+
41+
} // namespace mlir::qco::decomposition
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 <cstdint>
14+
15+
namespace mlir::qco::decomposition {
16+
17+
/**
18+
* Lightweight gate identifiers used by decomposition utilities.
19+
*
20+
* These kinds intentionally stay independent from the core IR `qc::OpType`
21+
* enum so the MLIR/QCO decomposition layer does not depend on the `ir`
22+
* package.
23+
*/
24+
enum class GateKind : std::uint8_t {
25+
I = 0,
26+
H,
27+
P,
28+
U,
29+
U2,
30+
X,
31+
Y,
32+
Z,
33+
SX,
34+
RX,
35+
RY,
36+
RZ,
37+
R,
38+
RXX,
39+
RYY,
40+
RZZ,
41+
GPhase,
42+
};
43+
44+
} // namespace mlir::qco::decomposition
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 "Gate.h"
14+
15+
#include <Eigen/Core>
16+
#include <llvm/ADT/SmallVector.h>
17+
18+
#include <cstddef>
19+
20+
namespace mlir::qco::decomposition {
21+
22+
/**
23+
* Sequence of abstract decomposition gates plus a residual global phase.
24+
*
25+
* `gates` is stored in execution order: for a column state vector, the first
26+
* gate in the vector is applied first. The reconstructed 4x4 unitary
27+
* is therefore `U = e^{i * phi} * M_{n-1} * ... * M_0`, where `M_i` is the
28+
* two-qubit matrix for `gates[i]` and `phi` is `globalPhase` in radians (via
29+
* `helpers::globalPhaseFactor`).
30+
*/
31+
struct QubitGateSequence {
32+
/// Expected short decomposition length; `SmallVector` inline storage size.
33+
static constexpr unsigned GATES_INLINE_CAPACITY = 8;
34+
35+
/// Gates in execution order (see struct comment).
36+
llvm::SmallVector<Gate, GATES_INLINE_CAPACITY> gates;
37+
38+
/// Residual global phase in radians, not represented by explicit gates.
39+
double globalPhase{};
40+
41+
/// True when `std::abs(globalPhase)` exceeds `DEFAULT_ATOL` in
42+
/// `EulerBasis.h`.
43+
[[nodiscard]] bool hasGlobalPhase() const;
44+
45+
/// Heuristic complexity from `helpers::getComplexity()` for each gate, plus a
46+
/// synthetic global-phase term when `hasGlobalPhase()` is true.
47+
[[nodiscard]] std::size_t complexity() const;
48+
49+
/**
50+
* Reconstruct the overall two-qubit unitary represented by the sequence.
51+
*
52+
* Single-qubit gates are expanded to the two-qubit workspace convention used
53+
* throughout the decomposition utilities.
54+
*/
55+
[[nodiscard]] Eigen::Matrix4cd getUnitaryMatrix() const;
56+
};
57+
58+
/// Documents intent only; same type as `QubitGateSequence`.
59+
/// `QubitGateSequence::getUnitaryMatrix()` still returns an `Eigen::Matrix4cd`
60+
/// in the shared two-qubit workspace convention, even for one-qubit sequences.
61+
using OneQubitGateSequence = QubitGateSequence;
62+
63+
} // namespace mlir::qco::decomposition

0 commit comments

Comments
 (0)