|
| 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 |
0 commit comments