Skip to content

Commit b4e1422

Browse files
committed
extract decomposition into headers
1 parent 1759c40 commit b4e1422

9 files changed

Lines changed: 2158 additions & 1701 deletions

File tree

mlir/include/mlir/Dialect/MQTOpt/Transforms/Decomposition/BasisDecomposer.h

Lines changed: 547 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 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 mqt::ir::opt::decomposition {
16+
/**
17+
* Largest number that will be assumed as zero for the euler decompositions.
18+
*/
19+
static constexpr auto DEFAULT_ATOL = 1e-12;
20+
21+
/**
22+
* EulerBasis for a euler decomposition.
23+
*
24+
* @note only the following bases are supported for now: ZYZ, ZXZ and XZX
25+
*/
26+
enum class EulerBasis : std::uint8_t {
27+
U3 = 0,
28+
U321 = 1,
29+
U = 2,
30+
PSX = 3,
31+
U1X = 4,
32+
RR = 5,
33+
ZYZ = 6,
34+
ZXZ = 7,
35+
XZX = 8,
36+
XYX = 9,
37+
ZSXX = 10,
38+
ZSX = 11,
39+
};
40+
} // namespace mqt::ir::opt::decomposition
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 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+
#include "Helpers.h"
16+
#include "ir/Definitions.hpp"
17+
#include "ir/operations/OpType.hpp"
18+
19+
#include <Eigen/Core> // NOLINT(misc-include-cleaner)
20+
#include <array>
21+
#include <cassert>
22+
#include <cmath>
23+
#include <complex>
24+
#include <llvm/ADT/STLExtras.h>
25+
#include <mlir/Dialect/Arith/IR/Arith.h>
26+
#include <mlir/IR/MLIRContext.h>
27+
#include <mlir/IR/Operation.h>
28+
#include <mlir/IR/PatternMatch.h>
29+
#include <mlir/IR/Value.h>
30+
#include <mlir/IR/ValueRange.h>
31+
#include <mlir/Support/LLVM.h>
32+
#include <mlir/Support/LogicalResult.h>
33+
#include <optional>
34+
#include <unsupported/Eigen/MatrixFunctions> // TODO: unstable, NOLINT(misc-include-cleaner)
35+
36+
namespace mqt::ir::opt::decomposition {
37+
38+
/**
39+
* Decomposition of single-qubit matrices into rotation gates using a KAK
40+
* decomposition.
41+
*/
42+
class EulerDecomposition {
43+
public:
44+
/**
45+
* Perform single-qubit decomposition of a 2x2 unitary matrix based on a
46+
* given euler basis.
47+
*/
48+
[[nodiscard]] static OneQubitGateSequence
49+
generateCircuit(EulerBasis targetBasis, const matrix2x2& unitaryMatrix,
50+
bool simplify, std::optional<fp> atol) {
51+
auto [theta, phi, lambda, phase] =
52+
anglesFromUnitary(unitaryMatrix, targetBasis);
53+
54+
switch (targetBasis) {
55+
case EulerBasis::ZYZ:
56+
return decomposeKAK(theta, phi, lambda, phase, qc::RZ, qc::RY, simplify,
57+
atol);
58+
case EulerBasis::ZXZ:
59+
return decomposeKAK(theta, phi, lambda, phase, qc::RZ, qc::RX, simplify,
60+
atol);
61+
case EulerBasis::XZX:
62+
return decomposeKAK(theta, phi, lambda, phase, qc::RX, qc::RZ, simplify,
63+
atol);
64+
case EulerBasis::XYX:
65+
return decomposeKAK(theta, phi, lambda, phase, qc::RX, qc::RY, simplify,
66+
atol);
67+
default:
68+
// TODO: allow other bases
69+
throw std::invalid_argument{"Unsupported base for circuit generation!"};
70+
}
71+
}
72+
73+
/**
74+
* Calculate angles of a single-qubit matrix according to the given
75+
* EulerBasis.
76+
*
77+
* @return array containing (theta, phi, lambda, phase) in this order
78+
*/
79+
static std::array<fp, 4> anglesFromUnitary(const matrix2x2& matrix,
80+
EulerBasis basis) {
81+
if (basis == EulerBasis::XYX) {
82+
return paramsXyxInner(matrix);
83+
}
84+
if (basis == EulerBasis::XZX) {
85+
return paramsXzxInner(matrix);
86+
}
87+
if (basis == EulerBasis::ZYZ) {
88+
return paramsZyzInner(matrix);
89+
}
90+
if (basis == EulerBasis::ZXZ) {
91+
return paramsZxzInner(matrix);
92+
}
93+
throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"};
94+
}
95+
96+
private:
97+
static std::array<fp, 4> paramsZyzInner(const matrix2x2& matrix) {
98+
const auto detArg = std::arg(matrix.determinant());
99+
const auto phase = 0.5 * detArg;
100+
const auto theta =
101+
2. * std::atan2(std::abs(matrix(1, 0)), std::abs(matrix(0, 0)));
102+
const auto ang1 = std::arg(matrix(1, 1));
103+
const auto ang2 = std::arg(matrix(1, 0));
104+
const auto phi = ang1 + ang2 - detArg;
105+
const auto lam = ang1 - ang2;
106+
return {theta, phi, lam, phase};
107+
}
108+
109+
static std::array<fp, 4> paramsZxzInner(const matrix2x2& matrix) {
110+
const auto [theta, phi, lam, phase] = paramsZyzInner(matrix);
111+
return {theta, phi + (qc::PI / 2.), lam - (qc::PI / 2.), phase};
112+
}
113+
114+
static std::array<fp, 4> paramsXyxInner(const matrix2x2& matrix) {
115+
const matrix2x2 matZyz{
116+
{static_cast<fp>(0.5) *
117+
(matrix(0, 0) + matrix(0, 1) + matrix(1, 0) + matrix(1, 1)),
118+
static_cast<fp>(0.5) *
119+
(matrix(0, 0) - matrix(0, 1) + matrix(1, 0) - matrix(1, 1))},
120+
{static_cast<fp>(0.5) *
121+
(matrix(0, 0) + matrix(0, 1) - matrix(1, 0) - matrix(1, 1)),
122+
static_cast<fp>(0.5) *
123+
(matrix(0, 0) - matrix(0, 1) - matrix(1, 0) + matrix(1, 1))},
124+
};
125+
auto [theta, phi, lam, phase] = paramsZyzInner(matZyz);
126+
auto newPhi = helpers::mod2pi(phi + qc::PI, 0.);
127+
auto newLam = helpers::mod2pi(lam + qc::PI, 0.);
128+
return {
129+
theta,
130+
newPhi,
131+
newLam,
132+
phase + ((newPhi + newLam - phi - lam) / 2.),
133+
};
134+
}
135+
136+
static std::array<fp, 4> paramsXzxInner(const matrix2x2& matrix) {
137+
auto det = matrix.determinant();
138+
auto phase = std::imag(std::log(det)) / 2.0;
139+
auto sqrtDet = std::sqrt(det);
140+
const matrix2x2 matZyz{
141+
{
142+
{(matrix(0, 0) / sqrtDet).real(), (matrix(1, 0) / sqrtDet).imag()},
143+
{(matrix(1, 0) / sqrtDet).real(), (matrix(0, 0) / sqrtDet).imag()},
144+
},
145+
{
146+
{-(matrix(1, 0) / sqrtDet).real(), (matrix(0, 0) / sqrtDet).imag()},
147+
{(matrix(0, 0) / sqrtDet).real(), -(matrix(1, 0) / sqrtDet).imag()},
148+
},
149+
};
150+
auto [theta, phi, lam, phase_zxz] = paramsZxzInner(matZyz);
151+
return {theta, phi, lam, phase + phase_zxz};
152+
}
153+
154+
/**
155+
* @note Adapted from circuit_kak() in the IBM Qiskit framework.
156+
* (C) Copyright IBM 2022
157+
*
158+
* This code is licensed under the Apache License, Version 2.0. You
159+
* may obtain a copy of this license in the LICENSE.txt file in the root
160+
* directory of this source tree or at
161+
* http://www.apache.org/licenses/LICENSE-2.0.
162+
*
163+
* Any modifications or derivative works of this code must retain
164+
* this copyright notice, and modified files need to carry a notice
165+
* indicating that they have been altered from the originals.
166+
*/
167+
[[nodiscard]] static OneQubitGateSequence
168+
decomposeKAK(fp theta, fp phi, fp lambda, fp phase, qc::OpType kGate,
169+
qc::OpType aGate, bool simplify, std::optional<fp> atol) {
170+
fp angleZeroEpsilon = atol.value_or(DEFAULT_ATOL);
171+
if (!simplify) {
172+
angleZeroEpsilon = -1.0;
173+
}
174+
175+
OneQubitGateSequence sequence{
176+
.gates = {},
177+
.globalPhase = phase - ((phi + lambda) / 2.),
178+
};
179+
if (std::abs(theta) <= angleZeroEpsilon) {
180+
lambda += phi;
181+
lambda = helpers::mod2pi(lambda);
182+
if (std::abs(lambda) > angleZeroEpsilon) {
183+
sequence.gates.push_back({.type = kGate, .parameter = {lambda}});
184+
sequence.globalPhase += lambda / 2.0;
185+
}
186+
return sequence;
187+
}
188+
189+
if (std::abs(theta - qc::PI) <= angleZeroEpsilon) {
190+
sequence.globalPhase += phi;
191+
lambda -= phi;
192+
phi = 0.0;
193+
}
194+
if (std::abs(helpers::mod2pi(lambda + qc::PI)) <= angleZeroEpsilon ||
195+
std::abs(helpers::mod2pi(phi + qc::PI)) <= angleZeroEpsilon) {
196+
lambda += qc::PI;
197+
theta = -theta;
198+
phi += qc::PI;
199+
}
200+
lambda = helpers::mod2pi(lambda);
201+
if (std::abs(lambda) > angleZeroEpsilon) {
202+
sequence.globalPhase += lambda / 2.0;
203+
sequence.gates.push_back({.type = kGate, .parameter = {lambda}});
204+
}
205+
sequence.gates.push_back({.type = aGate, .parameter = {theta}});
206+
phi = helpers::mod2pi(phi);
207+
if (std::abs(phi) > angleZeroEpsilon) {
208+
sequence.globalPhase += phi / 2.0;
209+
sequence.gates.push_back({.type = kGate, .parameter = {phi}});
210+
}
211+
return sequence;
212+
}
213+
};
214+
} // namespace mqt::ir::opt::decomposition
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 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 "Helpers.h"
14+
#include "ir/operations/OpType.hpp"
15+
16+
#include <llvm/ADT/SmallVector.h>
17+
18+
namespace mqt::ir::opt::decomposition {
19+
20+
using QubitId = std::size_t;
21+
22+
/**
23+
* Gate description which should be able to represent every possible
24+
* one-qubit or two-qubit operation.
25+
*/
26+
struct Gate {
27+
qc::OpType type{qc::I};
28+
llvm::SmallVector<fp, 3> parameter;
29+
llvm::SmallVector<QubitId, 2> qubitId = {0};
30+
};
31+
32+
} // namespace mqt::ir::opt::decomposition
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 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+
#include "Helpers.h"
15+
#include "UnitaryMatrices.h"
16+
#include "ir/operations/OpType.hpp"
17+
#include "mlir/Dialect/MQTOpt/Transforms/Decomposition/Helpers.h"
18+
19+
#include <Eigen/Core> // NOLINT(misc-include-cleaner)
20+
#include <cassert>
21+
#include <cmath>
22+
#include <complex>
23+
#include <cstddef>
24+
#include <llvm/ADT/STLExtras.h>
25+
#include <mlir/Dialect/Arith/IR/Arith.h>
26+
#include <mlir/IR/MLIRContext.h>
27+
#include <mlir/IR/Operation.h>
28+
#include <mlir/IR/PatternMatch.h>
29+
#include <mlir/IR/Value.h>
30+
#include <mlir/IR/ValueRange.h>
31+
#include <mlir/Support/LLVM.h>
32+
#include <mlir/Support/LogicalResult.h>
33+
#include <unsupported/Eigen/MatrixFunctions> // TODO: unstable, NOLINT(misc-include-cleaner)
34+
35+
namespace mqt::ir::opt::decomposition {
36+
/**
37+
* Gate sequence of single-qubit and/or two-qubit gates.
38+
*/
39+
struct QubitGateSequence {
40+
/**
41+
* Container sorting the gate sequence in order.
42+
*/
43+
llvm::SmallVector<Gate, 8> gates;
44+
45+
/**
46+
* Global phase adjustment required for the sequence.
47+
*/
48+
fp globalPhase{};
49+
/**
50+
* @return true if the global phase adjustment is not zero.
51+
*/
52+
[[nodiscard]] bool hasGlobalPhase() const {
53+
return std::abs(globalPhase) > DEFAULT_ATOL;
54+
}
55+
56+
/**
57+
* Calculate complexity of sequence according to getComplexity().
58+
*/
59+
[[nodiscard]] std::size_t complexity() const {
60+
// TODO: add more sophisticated metric to determine complexity of
61+
// series/sequence
62+
// TODO: caching mechanism?
63+
std::size_t c{};
64+
for (auto&& gate : gates) {
65+
c += helpers::getComplexity(gate.type, gate.qubitId.size());
66+
}
67+
if (hasGlobalPhase()) {
68+
// need to add a global phase gate if a global phase needs to be applied
69+
c += helpers::getComplexity(qc::GPhase, 0);
70+
}
71+
return c;
72+
}
73+
74+
/**
75+
* Calculate overall unitary matrix of the sequence.
76+
*/
77+
[[nodiscard]] matrix4x4 getUnitaryMatrix() const {
78+
matrix4x4 unitaryMatrix =
79+
helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE);
80+
for (auto&& gate : gates) {
81+
auto gateMatrix = getTwoQubitMatrix(gate);
82+
unitaryMatrix = gateMatrix * unitaryMatrix;
83+
}
84+
unitaryMatrix *= std::exp(IM * globalPhase);
85+
assert(helpers::isUnitaryMatrix(unitaryMatrix));
86+
return unitaryMatrix;
87+
}
88+
};
89+
/**
90+
* Helper type to show that a gate sequence is supposed to only contain
91+
* single-qubit gates.
92+
*/
93+
using OneQubitGateSequence = QubitGateSequence;
94+
/**
95+
* Helper type to show that the gate sequence may contain two-qubit gates.
96+
*/
97+
using TwoQubitGateSequence = QubitGateSequence;
98+
99+
} // namespace mqt::ir::opt::decomposition

0 commit comments

Comments
 (0)