Skip to content

Commit ee89a21

Browse files
committed
first snapshot
1 parent 57d4153 commit ee89a21

File tree

5 files changed

+315
-0
lines changed

5 files changed

+315
-0
lines changed

mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace mqt::ir::opt {
2626
#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" // IWYU pragma: export
2727

2828
void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns);
29+
void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns);
2930
void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns);
3031
void populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns);
3132
void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ def GateElimination : Pass<"gate-elimination", "mlir::ModuleOp"> {
3535
}];
3636
}
3737

38+
def GateDecomposition : Pass<"gate-decomposition", "mlir::ModuleOp"> {
39+
let summary = "This pass will perform various gate decompositions to simplify the used gate set.";
40+
let description = [{
41+
}];
42+
}
43+
3844
def MergeRotationGates : Pass<"merge-rotation-gates", "mlir::ModuleOp"> {
3945
let summary = "This pass searches for consecutive applications of rotation gates that can be merged.";
4046
let description = [{
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
#include "mlir/Dialect/Common/Compat.h"
12+
#include "mlir/Dialect/MQTOpt/Transforms/Passes.h"
13+
14+
#include <mlir/IR/PatternMatch.h>
15+
#include <mlir/Support/LLVM.h>
16+
#include <utility>
17+
18+
namespace mqt::ir::opt {
19+
20+
#define GEN_PASS_DEF_GATEDECOMPOSITION
21+
#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc"
22+
23+
/**
24+
* @brief This pass attempts to cancel consecutive self-inverse operations.
25+
*/
26+
struct GateDecomposition final : impl::GateDecompositionBase<GateDecomposition> {
27+
28+
void runOnOperation() override {
29+
// Get the current operation being operated on.
30+
auto op = getOperation();
31+
auto* ctx = &getContext();
32+
33+
// Define the set of patterns to use.
34+
mlir::RewritePatternSet patterns(ctx);
35+
populateGateDecompositionPatterns(patterns);
36+
37+
// Apply patterns in an iterative and greedy manner.
38+
if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) {
39+
signalPassFailure();
40+
}
41+
}
42+
};
43+
44+
} // namespace mqt::ir::opt
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
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+
#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h"
12+
#include "mlir/Dialect/MQTOpt/Transforms/Passes.h"
13+
14+
#include <iterator>
15+
#include <llvm/ADT/STLExtras.h>
16+
#include <mlir/Dialect/Arith/IR/Arith.h>
17+
#include <mlir/IR/MLIRContext.h>
18+
#include <mlir/IR/Operation.h>
19+
#include <mlir/IR/PatternMatch.h>
20+
#include <mlir/IR/ValueRange.h>
21+
#include <mlir/Support/LLVM.h>
22+
#include <mlir/Support/LogicalResult.h>
23+
24+
namespace mqt::ir::opt {
25+
26+
/**
27+
* @brief This pattern replaces all negative controls by negations combined with
28+
* positively controlled operations.
29+
*/
30+
struct NegCtrlDecompositionPattern final
31+
: mlir::OpInterfaceRewritePattern<UnitaryInterface> {
32+
33+
explicit NegCtrlDecompositionPattern(mlir::MLIRContext* context)
34+
: OpInterfaceRewritePattern(context) {}
35+
36+
mlir::LogicalResult
37+
matchAndRewrite(UnitaryInterface op,
38+
mlir::PatternRewriter& rewriter) const override {
39+
return mlir::failure();
40+
}
41+
};
42+
43+
/**
44+
* @brief This pattern TODO.
45+
*/
46+
struct EulerDecompositionPattern final
47+
: mlir::OpInterfaceRewritePattern<UnitaryInterface> {
48+
49+
explicit EulerDecompositionPattern(mlir::MLIRContext* context)
50+
: OpInterfaceRewritePattern(context) {}
51+
52+
mlir::LogicalResult
53+
matchAndRewrite(UnitaryInterface op,
54+
mlir::PatternRewriter& rewriter) const override {
55+
if (!isSingleQubitOperation(op)) {
56+
return mlir::failure();
57+
}
58+
59+
auto series = getSingleQubitSeries(op);
60+
61+
return mlir::failure();
62+
}
63+
64+
[[nodiscard]] static llvm::SmallVector<UnitaryInterface>
65+
getSingleQubitSeries(UnitaryInterface op) {
66+
llvm::SmallVector<UnitaryInterface> result = {op};
67+
while (true) {
68+
auto nextOp = getNextOperation(op);
69+
if (isSingleQubitOperation(nextOp)) {
70+
result.push_back(nextOp);
71+
} else {
72+
return result;
73+
}
74+
}
75+
}
76+
77+
[[nodiscard]] static bool isSingleQubitOperation(UnitaryInterface op) {
78+
auto&& inQubits = op.getInQubits();
79+
auto&& outQubits = op.getOutQubits();
80+
return inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled();
81+
}
82+
83+
[[nodiscard]] static UnitaryInterface getNextOperation(UnitaryInterface op) {
84+
// since there is only one output qubit, there should only be one user
85+
auto&& users = op->getUsers();
86+
assert(std::distance(users.begin(), users.end()) == 1);
87+
return llvm::dyn_cast<UnitaryInterface>(*users.begin());
88+
}
89+
90+
/**
91+
* @brief Creates a new rotation gate with no controls.
92+
*
93+
* @tparam OpType The type of the operation to be created.
94+
* @param op The first instance of the rotation gate.
95+
* @param rewriter The pattern rewriter.
96+
* @return A new rotation gate.
97+
*/
98+
template <typename OpType>
99+
static OpType createRotationGate(mlir::PatternRewriter& rewriter,
100+
mlir::Value inQubit, qc::fp angle) {
101+
auto location = inQubit.getLoc();
102+
auto qubitType = inQubit.getType();
103+
104+
auto angleValue = rewriter.create<mlir::arith::ConstantOp>(
105+
location, rewriter.getF64Type(), rewriter.getF64FloatAttr(angle));
106+
107+
return rewriter.create<OpType>(
108+
location, mlir::TypeRange{qubitType}, mlir::TypeRange{},
109+
mlir::TypeRange{}, mlir::DenseF64ArrayAttr{},
110+
mlir::DenseBoolArrayAttr{}, mlir::ValueRange{angleValue},
111+
mlir::ValueRange{inQubit}, mlir::ValueRange{}, mlir::ValueRange{});
112+
}
113+
114+
[[nodiscard]] static llvm::SmallVector<UnitaryInterface, 3> createMlirGates(
115+
mlir::PatternRewriter& rewriter,
116+
const llvm::SmallVector<std::pair<qc::OpType, qc::fp>, 3>& schematic,
117+
mlir::Value inQubit) {
118+
llvm::SmallVector<UnitaryInterface, 3> result;
119+
for (auto [type, angle] : schematic) {
120+
if (type == qc::RZ) {
121+
auto newRz = createRotationGate<RZOp>(rewriter, inQubit, angle);
122+
result.push_back(newRz);
123+
} else if (type == qc::RY) {
124+
auto newRy = createRotationGate<RYOp>(rewriter, inQubit, angle);
125+
result.push_back(newRy);
126+
} else {
127+
throw std::logic_error{"Unable to create MLIR gate in Euler "
128+
"Decomposition (unsupported gate)"};
129+
}
130+
inQubit = result.back().getOutQubits().front();
131+
}
132+
return result;
133+
}
134+
135+
/**
136+
* @note Adapted from circuit_kak() in the IBM Qiskit framework.
137+
* (C) Copyright IBM 2022
138+
*
139+
* This code is licensed under the Apache License, Version 2.0. You may
140+
* obtain a copy of this license in the LICENSE.txt file in the root
141+
* directory of this source tree or at
142+
* http://www.apache.org/licenses/LICENSE-2.0.
143+
*
144+
* Any modifications or derivative works of this code must retain this
145+
* copyright notice, and modified files need to carry a notice
146+
* indicating that they have been altered from the originals.
147+
*/
148+
[[nodiscard]] static std::pair<
149+
llvm::SmallVector<std::pair<qc::OpType, qc::fp>, 3>, qc::fp>
150+
calculateRotationGates(std::array<qc::fp, 4> unitaryMatrix) {
151+
auto [lambda, theta, phi, phase] = paramsZyzInner(unitaryMatrix);
152+
qc::fp globalPhase = phase - ((phi + lambda) / 2.);
153+
constexpr qc::fp angleZeroEpsilon = 1e-12;
154+
155+
auto remEuclid = [](qc::fp a, qc::fp b) {
156+
auto r = std::fmod(a, b);
157+
return (r < 0.0) ? r + std::abs(b) : r;
158+
};
159+
auto mod2pi = [&](qc::fp angle) -> qc::fp {
160+
// remEuclid() isn't exactly the same as Python's % operator, but
161+
// because the RHS here is a constant and positive it is effectively
162+
// equivalent for this case
163+
auto wrapped = remEuclid(angle + qc::PI, 2. * qc::PI) - qc::PI;
164+
if (std::abs(wrapped - qc::PI) < angleZeroEpsilon) {
165+
return -qc::PI;
166+
}
167+
return wrapped;
168+
};
169+
170+
llvm::SmallVector<std::pair<qc::OpType, qc::fp>, 3> gates;
171+
if (std::abs(theta) < angleZeroEpsilon) {
172+
lambda += phi;
173+
lambda = mod2pi(lambda);
174+
if (std::abs(lambda) > angleZeroEpsilon) {
175+
gates.push_back({qc::RZ, lambda});
176+
globalPhase += lambda / 2.0;
177+
}
178+
return {gates, globalPhase};
179+
}
180+
181+
if (std::abs(theta - qc::PI) < angleZeroEpsilon) {
182+
globalPhase += phi;
183+
lambda -= phi;
184+
phi = 0.0;
185+
}
186+
if (std::abs(mod2pi(lambda + qc::PI)) < angleZeroEpsilon ||
187+
std::abs(mod2pi(phi + qc::PI)) < angleZeroEpsilon) {
188+
lambda += qc::PI;
189+
theta = -theta;
190+
phi += qc::PI;
191+
}
192+
lambda = mod2pi(lambda);
193+
if (std::abs(lambda) > angleZeroEpsilon) {
194+
globalPhase += lambda / 2.0;
195+
gates.push_back({qc::RZ, lambda});
196+
}
197+
gates.push_back({qc::RY, theta});
198+
phi = mod2pi(phi);
199+
if (std::abs(phi) > angleZeroEpsilon) {
200+
globalPhase += phi / 2.0;
201+
gates.push_back({qc::RZ, phi});
202+
}
203+
return {gates, globalPhase};
204+
}
205+
206+
/**
207+
* @note Adapted from circuit_kak() in the IBM Qiskit framework.
208+
* (C) Copyright IBM 2022
209+
*
210+
* This code is licensed under the Apache License, Version 2.0. You may
211+
* obtain a copy of this license in the LICENSE.txt file in the root
212+
* directory of this source tree or at
213+
* http://www.apache.org/licenses/LICENSE-2.0.
214+
*
215+
* Any modifications or derivative works of this code must retain this
216+
* copyright notice, and modified files need to carry a notice
217+
* indicating that they have been altered from the originals.
218+
*/
219+
[[nodiscard]] static std::array<qc::fp, 4>
220+
paramsZyzInner(std::array<qc::fp, 4> unitaryMatrix) {
221+
auto getIndex = [](auto x, auto y) { return (x * 2) + y; };
222+
auto determinant = [getIndex](auto&& matrix) {
223+
return (matrix.at(getIndex(0, 0)) * matrix.at(getIndex(1, 1))) -
224+
(matrix.at(getIndex(1, 0)) * matrix.at(getIndex(0, 1)));
225+
};
226+
227+
auto detArg = determinant(unitaryMatrix);
228+
auto phase = 0.5 * detArg;
229+
auto theta = 2. * std::atan2(std::abs(unitaryMatrix.at(getIndex(1, 0))),
230+
std::abs(unitaryMatrix.at(getIndex(0, 0))));
231+
auto ang1 = unitaryMatrix.at(getIndex(1, 1));
232+
auto ang2 = unitaryMatrix.at(getIndex(1, 0));
233+
auto phi = ang1 + ang2 - detArg;
234+
auto lam = ang1 - ang2;
235+
return {theta, phi, lam, phase};
236+
}
237+
};
238+
239+
/**
240+
* @brief Populates the given pattern set with patterns for gate elimination.
241+
*
242+
* @param patterns The pattern set to populate.
243+
*/
244+
void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) {
245+
patterns.add<NegCtrlDecompositionPattern>(patterns.getContext());
246+
patterns.add<EulerDecompositionPattern>(patterns.getContext());
247+
}
248+
249+
} // namespace mqt::ir::opt
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
2+
// Copyright (c) 2025 Munich Quantum Software Company GmbH
3+
// All rights reserved.
4+
//
5+
// SPDX-License-Identifier: MIT
6+
//
7+
// Licensed under the MIT License
8+
9+
// RUN: quantum-opt %s -split-input-file --gate-decomposition | FileCheck %s
10+
11+
// -----
12+
// This test checks if single-qubit consecutive self-inverses are canceled correctly.
13+
14+
module {
15+
}

0 commit comments

Comments
 (0)