From bcf595bea7d58e1660110786ebc2da2c9ab0c9d7 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 15 Sep 2025 22:47:14 +0200 Subject: [PATCH 001/100] snapshot --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 1 + .../MQTOpt/Transforms/GateDecomposition.cpp | 44 +++++ .../Transforms/GateDecompositionPattern.cpp | 118 ++++++++++++ mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 177 ++++++++++++++++++ 4 files changed, 340 insertions(+) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index 5b5593092..d5014f11b 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -35,6 +35,7 @@ enum class RoutingMethod : std::uint8_t { Naive, AStar }; #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" // IWYU pragma: export void populateGateEliminationPatterns(mlir::RewritePatternSet& patterns); +void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns); void populateMergeRotationGatesPatterns(mlir::RewritePatternSet& patterns); void populateSwapReconstructionAndElisionPatterns( mlir::RewritePatternSet& patterns); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp new file mode 100644 index 000000000..8ed6be639 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" + +#include +#include +#include +#include + +namespace mqt::ir::opt { + +#define GEN_PASS_DEF_GATEDECOMPOSITION +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" + +/** + * @brief This pass attempts to cancel consecutive self-inverse operations. + */ +struct GateDecomposition final : impl::GateDecompositionBase { + + void runOnOperation() override { + // Get the current operation being operated on. + auto op = getOperation(); + auto* ctx = &getContext(); + + // Define the set of patterns to use. + mlir::RewritePatternSet patterns(ctx); + populateGateDecompositionPatterns(patterns); + + // Apply patterns in an iterative and greedy manner. + if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp new file mode 100644 index 000000000..92e1c3c73 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#include "Helpers.h" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mqt::ir::opt { + +/** + * @brief This pattern attempts to cancel consecutive self-inverse operations. + */ +struct GateDecompositionPattern final + : mlir::OpInterfaceRewritePattern { + + explicit GateDecompositionPattern(mlir::MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + dd::TwoQubitGateMatrix twoQubitIdentity = { + {{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}}; + + mlir::LogicalResult + matchAndRewrite(UnitaryInterface op, + mlir::PatternRewriter& rewriter) const override { + auto series = getTwoQubitSeries(op); + if (series.size() <= 3) { + return mlir::failure(); + } + + dd::TwoQubitGateMatrix unitaryMatrix = dd::opToTwoQubitGateMatrix(qc::I); + for (auto&& gate : series) { + if (auto gateMatrix = helpers::getUnitaryMatrix(gate)) { + unitaryMatrix = helpers::multiply(unitaryMatrix, *gateMatrix); + } + } + + twoQubitDecompose(unitaryMatrix); + + return mlir::success(); + } + + [[nodiscard]] static llvm::SmallVector + getTwoQubitSeries(UnitaryInterface op) { + llvm::SmallVector qubits(2); + llvm::SmallVector result; + + if (helpers::isSingleQubitOperation(op)) { + qubits = {op->getResult(0), mlir::Value{}}; + } else if (helpers::isTwoQubitOperation(op)) { + qubits = op->getResults(); + } else { + return result; + } + while (true) { + for (auto&& user : op->getUsers()) { + auto userUnitary = llvm::cast(user); + if (helpers::isSingleQubitOperation(userUnitary)) { + auto&& operand = userUnitary->getOperand(0); + auto* it = llvm::find(qubits, operand); + if (it == qubits.end()) { + return result; + } + *it = userUnitary->getResult(0); + + result.push_back(userUnitary); + } else if (helpers::isTwoQubitOperation(userUnitary)) { + auto&& firstOperand = userUnitary->getOperand(0); + auto&& secondOperand = userUnitary->getOperand(1); + auto* firstQubitIt = llvm::find(qubits, firstOperand); + auto* secondQubitIt = llvm::find(qubits, secondOperand); + if (firstQubitIt == qubits.end() || secondQubitIt == qubits.end()) { + return result; + } + *firstQubitIt = userUnitary->getResult(0); + *secondQubitIt = userUnitary->getResult(1); + + result.push_back(userUnitary); + } else { + return result; + } + } + return result; + } + } + + void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { + + } +}; + +/** + * @brief Populates the given pattern set with patterns for gate + * decomposition. + */ +void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { + patterns.add(patterns.getContext()); +} + +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h new file mode 100644 index 000000000..f4976b44e --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +#pragma once + +#include "dd/GateMatrixDefinitions.hpp" +#include "dd/Package.hpp" +#include "ir/Definitions.hpp" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" + +#include +#include +#include + +namespace mqt::ir::opt::helpers { + +std::optional mlirValueToFp(mlir::Value value); + +template +std::optional performMlirFloatBinaryOp(mlir::Value value, Func&& func) { + if (auto op = value.getDefiningOp()) { + auto lhs = mlirValueToFp(op.getLhs()); + auto rhs = mlirValueToFp(op.getRhs()); + if (lhs && rhs) { + return std::invoke(std::forward(func), *lhs, *rhs); + } + } + return std::nullopt; +} + +template +std::optional performMlirFloatUnaryOp(mlir::Value value, Func&& func) { + if (auto op = value.getDefiningOp()) { + if (auto operand = mlirValueToFp(op.getOperand())) { + return std::invoke(std::forward(func), *operand); + } + } + return std::nullopt; +} + +inline std::optional mlirValueToFp(mlir::Value value) { + if (auto op = value.getDefiningOp()) { + if (auto attr = llvm::dyn_cast(op.getValue())) { + return attr.getValueAsDouble(); + } + return std::nullopt; + } + if (auto result = performMlirFloatUnaryOp( + value, [](qc::fp a) { return -a; })) { + return result; + } + if (auto result = performMlirFloatUnaryOp( + value, [](qc::fp a) { return a; })) { + return result; + } + if (auto result = performMlirFloatUnaryOp( + value, [](qc::fp a) { return a; })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return std::max(a, b); })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return std::max(a, b); })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return std::min(a, b); })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return std::min(a, b); })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return std::fmod(a, b); })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return a + b; })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return a + b; })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return a + b; })) { + return result; + } + if (auto result = performMlirFloatBinaryOp( + value, [](qc::fp a, qc::fp b) { return a + b; })) { + return result; + } + return std::nullopt; +} + +[[nodiscard]] inline std::vector getParameters(UnitaryInterface op) { + std::vector parameters; + for (auto&& param : op.getParams()) { + if (auto value = helpers::mlirValueToFp(param)) { + parameters.push_back(*value); + } + } + return parameters; +} + +[[nodiscard]] inline qc::OpType getQcType(UnitaryInterface op) { + try { + const std::string type = op->getName().stripDialect().str(); + return qc::opTypeFromString(type); + } catch (const std::invalid_argument& /*exception*/) { + return qc::OpType::None; + } +} + +[[nodiscard]] inline bool isSingleQubitOperation(UnitaryInterface op) { + auto&& inQubits = op.getInQubits(); + auto&& outQubits = op.getOutQubits(); + bool isSingleQubitOp = + inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled(); + assert(isSingleQubitOp == qc::isSingleQubitGate(getQcType(op))); + return isSingleQubitOp; +} + +[[nodiscard]] inline bool isTwoQubitOperation(UnitaryInterface op) { + auto&& inQubits = op.getInQubits(); + auto&& inPosCtrlQubits = op.getPosCtrlInQubits(); + auto&& inNegCtrlQubits = op.getNegCtrlInQubits(); + auto inQubitSize = + inQubits.size() + inPosCtrlQubits.size() + inNegCtrlQubits.size(); + auto&& outQubits = op.getOutQubits(); + auto&& outPosCtrlQubits = op.getPosCtrlInQubits(); + auto&& outNegCtrlQubits = op.getNegCtrlInQubits(); + auto outQubitSize = + outQubits.size() + outPosCtrlQubits.size() + outNegCtrlQubits.size(); + bool isTwoQubitOp = inQubitSize == 2 && outQubitSize == 2; + assert(isTwoQubitOp == qc::isTwoQubitGate(getQcType(op))); + return isTwoQubitOp; +} + +[[nodiscard]] inline std::optional +getUnitaryMatrix(UnitaryInterface op) { + auto type = getQcType(op); + auto parameters = getParameters(op); + + if (isTwoQubitOperation(op)) { + return dd::opToTwoQubitGateMatrix(type, parameters); + } + return std::nullopt; +} + +[[nodiscard]] inline dd::GateMatrix multiply(std::complex factor, + dd::GateMatrix matrix) { + return {factor * matrix.at(0), factor * matrix.at(1), factor * matrix.at(2), + factor * matrix.at(3)}; +} + +[[nodiscard]] inline dd::TwoQubitGateMatrix +kroneckerProduct(dd::GateMatrix lhs, dd::GateMatrix rhs) { + return {multiply(lhs.at(0), rhs), multiply(lhs.at(1), rhs), + multiply(lhs.at(2), rhs), multiply(lhs.at(3), rhs)}; +} + +[[nodiscard]] inline dd::TwoQubitGateMatrix +multiply(dd::TwoQubitGateMatrix lhs, dd::TwoQubitGateMatrix rhs) { + return {}; +} +} // namespace mqt::ir::opt::helpers From a06cb1b4ed067540d3bb5c6eb3ffe6675085a639 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 16 Sep 2025 13:01:25 +0200 Subject: [PATCH 002/100] copied rust code --- .../Transforms/GateDecompositionPattern.cpp | 611 ++++++++++++++++++ 1 file changed, 611 insertions(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 92e1c3c73..3c9af492e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include #include #include #include @@ -102,8 +103,618 @@ struct GateDecompositionPattern final } } + enum class Specialization { + General, + IdEquiv, + SWAPEquiv, + PartialSWAPEquiv, + PartialSWAPFlipEquiv, + ControlledEquiv, + MirrorControlledEquiv, + // These next 3 gates use the definition of fSim from eq (1) in: + // https://arxiv.org/pdf/2001.08343.pdf + fSimaabEquiv, + fSimabbEquiv, + fSimabmbEquiv, + }; + + struct TwoQubitWeylDecomposition { + qc::fp a; + qc::fp b; + qc::fp c; + qc::fp global_phase; + std::array, 4> K1l; + std::array, 4> K2l; + std::array, 4> K1r; + std::array, 4> K2r; + Specialization specialization; + //EulerBasis default_euler_basis; // TODO: simply use ZYZ? + std::optional requested_fidelity; + qc::fp calculated_fidelity; + dd::TwoQubitGateMatrix unitary_matrix; + + TwoQubitWeylDecomposition new_inner( + std::array, 4> unitary_matrix, + + std::optional fidelity, + std::optional _specialization + ) { + constexpr std::array, 4> IPZ = [[IM, C_ZERO], [C_ZERO, M_IM]]; + constexpr std::array, 4> IPY =[[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; + constexpr std::array, 4> IPX = [[C_ZERO, IM], [IM, C_ZERO]]; + + + auto u = unitary_matrix; + let det_u = u.view().into_faer().determinant(); + let det_pow = det_u.powf(-0.25); + u.mapv_inplace(|x| x * det_pow); + let mut global_phase = det_u.arg() / 4.; + let u_p = magic_basis_transform(u.view(), MagicBasisTransform::OutOf); + let m2 = u_p.t().dot(&u_p); + let default_euler_basis = EulerBasis::ZYZ; + + // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D P^T where + // P ∈ SO(4), D is diagonal with unit-magnitude elements. + // + // We can't use raw `eig` directly because it isn't guaranteed to give us real or orthogonal + // eigenvectors. Instead, since `M2` is complex-symmetric, + // M2 = A + iB + // for real-symmetric `A` and `B`, and as + // M2^+ @ M2 = A^2 + B^2 + i [A, B] = 1 + // we must have `A` and `B` commute, and consequently they are simultaneously diagonalizable. + // Mixing them together _should_ account for any degeneracy problems, but it's not + // guaranteed, so we repeat it a little bit. The fixed seed is to make failures + // deterministic; the value is not important. + let mut state = Pcg64Mcg::seed_from_u64(2023); + let mut found = false; + let mut d: Array1 = Array1::zeros(0); + let mut p: Array2 = Array2::zeros((0, 0)); + for i in 0..100 { + let rand_a: f64; + let rand_b: f64; + // For debugging the algorithm use the same RNG values from the + // previous Python implementation for the first random trial. + // In most cases this loop only executes a single iteration and + // using the same rng values rules out possible RNG differences + // as the root cause of a test failure + if i == 0 { + rand_a = 1.2602066112249388; + rand_b = 0.22317849046722027; + } else { + rand_a = state.sample(StandardNormal); + rand_b = state.sample(StandardNormal); + } + let m2_real = m2.mapv(|val| rand_a * val.re + rand_b * val.im); + let p_inner = m2_real + .view() + .into_faer() + .self_adjoint_eigen(Lower) + .map_err(|e| QiskitError::new_err(format!("{e:?}")))? + .U() + .into_ndarray() + .mapv(Complex64::from); + let d_inner = p_inner.t().dot(&m2).dot(&p_inner).diag().to_owned(); + let mut diag_d: Array2 = Array2::zeros((4, 4)); + diag_d + .diag_mut() + .iter_mut() + .enumerate() + .for_each(|(index, x)| *x = d_inner[index]); + + let compare = p_inner.dot(&diag_d).dot(&p_inner.t()); + found = abs_diff_eq!(compare.view(), m2, epsilon = 1.0e-13); + if found { + p = p_inner; + d = d_inner; + break; + } + } + if !found { + return Err(QiskitError::new_err(format!( + "TwoQubitWeylDecomposition: failed to diagonalize M2. Please report this at https://github.com/Qiskit/qiskit-terra/issues/4159. Input: {unitary_matrix:?}" + ))); + } + let mut d = -d.map(|x| x.arg() / 2.); + d[3] = -d[0] - d[1] - d[2]; + let mut cs: SmallVec<[f64; 3]> = (0..3) + .map(|i| ((d[i] + d[3]) / 2.0).rem_euclid(TWO_PI)) + .collect(); + let cstemp: SmallVec<[f64; 3]> = cs + .iter() + .map(|x| x.rem_euclid(PI2)) + .map(|x| x.min(PI2 - x)) + .collect(); + let mut order = arg_sort(&cstemp); + (order[0], order[1], order[2]) = (order[1], order[2], order[0]); + (cs[0], cs[1], cs[2]) = (cs[order[0]], cs[order[1]], cs[order[2]]); + (d[0], d[1], d[2]) = (d[order[0]], d[order[1]], d[order[2]]); + let mut p_orig = p.clone(); + for (i, item) in order.iter().enumerate().take(3) { + let slice_a = p.slice_mut(s![.., i]); + let slice_b = p_orig.slice_mut(s![.., *item]); + Zip::from(slice_a).and(slice_b).for_each(::std::mem::swap); + } + if p.view().into_faer().determinant().re < 0. { + p.slice_mut(s![.., -1]).mapv_inplace(|x| -x); + } + let mut temp: Array2 = Array2::zeros((4, 4)); + temp.diag_mut() + .iter_mut() + .enumerate() + .for_each(|(index, x)| *x = (IM * d[index]).exp()); + let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), MagicBasisTransform::Into); + let k2 = magic_basis_transform(p.t(), MagicBasisTransform::Into); + + #[allow(non_snake_case)] + let (mut K1l, mut K1r, phase_l) = decompose_two_qubit_product_gate(k1.view())?; + #[allow(non_snake_case)] + let (K2l, mut K2r, phase_r) = decompose_two_qubit_product_gate(k2.view())?; + global_phase += phase_l + phase_r; + + // Flip into Weyl chamber + if cs[0] > PI2 { + cs[0] -= PI32; + K1l = K1l.dot(&ipy); + K1r = K1r.dot(&ipy); + global_phase += PI2; + } + if cs[1] > PI2 { + cs[1] -= PI32; + K1l = K1l.dot(&ipx); + K1r = K1r.dot(&ipx); + global_phase += PI2; + } + let mut conjs = 0; + if cs[0] > PI4 { + cs[0] = PI2 - cs[0]; + K1l = K1l.dot(&ipy); + K2r = ipy.dot(&K2r); + conjs += 1; + global_phase -= PI2; + } + if cs[1] > PI4 { + cs[1] = PI2 - cs[1]; + K1l = K1l.dot(&ipx); + K2r = ipx.dot(&K2r); + conjs += 1; + global_phase += PI2; + if conjs == 1 { + global_phase -= PI; + } + } + if cs[2] > PI2 { + cs[2] -= PI32; + K1l = K1l.dot(&ipz); + K1r = K1r.dot(&ipz); + global_phase += PI2; + if conjs == 1 { + global_phase -= PI; + } + } + if conjs == 1 { + cs[2] = PI2 - cs[2]; + K1l = K1l.dot(&ipz); + K2r = ipz.dot(&K2r); + global_phase += PI2; + } + if cs[2] > PI4 { + cs[2] -= PI2; + K1l = K1l.dot(&ipz); + K1r = K1r.dot(&ipz); + global_phase -= PI2; + } + let [a, b, c] = [cs[1], cs[0], cs[2]]; + let is_close = |ap: f64, bp: f64, cp: f64| -> bool { + let [da, db, dc] = [a - ap, b - bp, c - cp]; + let tr = 4. + * c64( + da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), + ); + match fidelity { + Some(fid) => tr.trace_to_fid() >= fid, + // Set to false here to default to general specialization in the absence of a + // fidelity and provided specialization. + None => false, + } + }; + + let closest_abc = closest_partial_swap(a, b, c); + let closest_ab_minus_c = closest_partial_swap(a, b, -c); + let mut flipped_from_original = false; + let specialization = match _specialization { + Some(specialization) => specialization, + None => { + if is_close(0., 0., 0.) { + Specialization::IdEquiv + } else if is_close(PI4, PI4, PI4) || is_close(PI4, PI4, -PI4) { + Specialization::SWAPEquiv + } else if is_close(closest_abc, closest_abc, closest_abc) { + Specialization::PartialSWAPEquiv + } else if is_close(closest_ab_minus_c, closest_ab_minus_c, -closest_ab_minus_c) { + Specialization::PartialSWAPFlipEquiv + } else if is_close(a, 0., 0.) { + Specialization::ControlledEquiv + } else if is_close(PI4, PI4, c) { + Specialization::MirrorControlledEquiv + } else if is_close((a + b) / 2., (a + b) / 2., c) { + Specialization::fSimaabEquiv + } else if is_close(a, (b + c) / 2., (b + c) / 2.) { + Specialization::fSimabbEquiv + } else if is_close(a, (b - c) / 2., (c - b) / 2.) { + Specialization::fSimabmbEquiv + } else { + Specialization::General + } + } + }; + let general = TwoQubitWeylDecomposition { + a, + b, + c, + global_phase, + K1l, + K1r, + K2l, + K2r, + specialization: Specialization::General, + default_euler_basis, + requested_fidelity: fidelity, + calculated_fidelity: -1.0, + unitary_matrix, + }; + let mut specialized: TwoQubitWeylDecomposition = match specialization { + // :math:`U \sim U_d(0,0,0) \sim Id` + // + // This gate binds 0 parameters, we make it canonical by setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::IdEquiv => TwoQubitWeylDecomposition { + specialization, + a: 0., + b: 0., + c: 0., + K1l: general.K1l.dot(&general.K2l), + K1r: general.K1r.dot(&general.K2r), + K2l: Array2::eye(2), + K2r: Array2::eye(2), + ..general + }, + // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, -\pi/4) \sim \text{SWAP}` + // + // This gate binds 0 parameters, we make it canonical by setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::SWAPEquiv => { + if c > 0. { + TwoQubitWeylDecomposition { + specialization, + a: PI4, + b: PI4, + c: PI4, + K1l: general.K1l.dot(&general.K2r), + K1r: general.K1r.dot(&general.K2l), + K2l: Array2::eye(2), + K2r: Array2::eye(2), + ..general + } + } else { + flipped_from_original = true; + TwoQubitWeylDecomposition { + specialization, + a: PI4, + b: PI4, + c: PI4, + global_phase: global_phase + PI2, + K1l: general.K1l.dot(&ipz).dot(&general.K2r), + K1r: general.K1r.dot(&ipz).dot(&general.K2l), + K2l: Array2::eye(2), + K2r: Array2::eye(2), + ..general + } + } + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim \text{SWAP}^\alpha` + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id`. + Specialization::PartialSWAPEquiv => { + let closest = closest_partial_swap(a, b, c); + let mut k2l_dag = general.K2l.t().to_owned(); + k2l_dag.view_mut().mapv_inplace(|x| x.conj()); + TwoQubitWeylDecomposition { + specialization, + a: closest, + b: closest, + c: closest, + K1l: general.K1l.dot(&general.K2l), + K1r: general.K1r.dot(&general.K2l), + K2r: k2l_dag.dot(&general.K2r), + K2l: Array2::eye(2), + ..general + } + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim \text{SWAP}^\alpha` + // + // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv + // similar to how :math:`x = (\pm \sqrt(x))^2`) + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id` + Specialization::PartialSWAPFlipEquiv => { + let closest = closest_partial_swap(a, b, -c); + let mut k2l_dag = general.K2l.t().to_owned(); + k2l_dag.mapv_inplace(|x| x.conj()); + TwoQubitWeylDecomposition { + specialization, + a: closest, + b: closest, + c: -closest, + K1l: general.K1l.dot(&general.K2l), + K1r: general.K1r.dot(&ipz).dot(&general.K2l).dot(&ipz), + K2r: ipz.dot(&k2l_dag).dot(&ipz).dot(&general.K2r), + K2l: Array2::eye(2), + ..general + } + } + // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , + // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . + Specialization::ControlledEquiv => { + let euler_basis = EulerBasis::XYX; + let [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + let [k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, + a, + b: 0., + c: 0., + global_phase: global_phase + k2lphase + k2rphase, + K1l: general.K1l.dot(&rx_matrix(k2lphi)), + K1r: general.K1r.dot(&rx_matrix(k2rphi)), + K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r: ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), + default_euler_basis: euler_basis, + ..general + } + } + // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = Ry(\theta_r)\cdot Rz(\lambda_r)` + Specialization::MirrorControlledEquiv => { + let [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + let [k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, + a: PI4, + b: PI4, + c, + global_phase: global_phase + k2lphase + k2rphase, + K1l: general.K1l.dot(&rz_matrix(k2rphi)), + K1r: general.K1r.dot(&rz_matrix(k2lphi)), + K2l: ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r: ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), + ..general + } + } + // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. + Specialization::fSimaabEquiv => { + let [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, + a: (a + b) / 2., + b: (a + b) / 2., + c, + global_phase: global_phase + k2lphase, + K1r: general.K1r.dot(&rz_matrix(k2lphi)), + K1l: general.K1l.dot(&rz_matrix(k2lphi)), + K2l: ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r: rz_matrix(-k2lphi).dot(&general.K2r), + ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabbEquiv => { + let euler_basis = EulerBasis::XYX; + let [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, + a, + b: (b + c) / 2., + c: (b + c) / 2., + global_phase: global_phase + k2lphase, + K1r: general.K1r.dot(&rx_matrix(k2lphi)), + K1l: general.K1l.dot(&rx_matrix(k2lphi)), + K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r: rx_matrix(-k2lphi).dot(&general.K2r), + default_euler_basis: euler_basis, + ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabmbEquiv => { + let euler_basis = EulerBasis::XYX; + let [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, + a, + b: (b - c) / 2., + c: -((b - c) / 2.), + global_phase: global_phase + k2lphase, + K1l: general.K1l.dot(&rx_matrix(k2lphi)), + K1r: general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), + K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r: ipz.dot(&rx_matrix(-k2lphi)).dot(&ipz).dot(&general.K2r), + default_euler_basis: euler_basis, + ..general + } + } + // U has no special symmetry. + // + // This gate binds all 6 possible parameters, so there is no need to make the single-qubit + // pre-/post-gates canonical. + Specialization::General => general, + }; + + let tr = if flipped_from_original { + let [da, db, dc] = [ + PI2 - a - specialized.a, + b - specialized.b, + -c - specialized.c, + ]; + 4. * c64( + da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), + ) + } else { + let [da, db, dc] = [a - specialized.a, b - specialized.b, c - specialized.c]; + 4. * c64( + da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), + ) + }; + specialized.calculated_fidelity = tr.trace_to_fid(); + if let Some(fid) = specialized.requested_fidelity { + if specialized.calculated_fidelity + 1.0e-13 < fid { + return Err(QiskitError::new_err(format!( + "Specialization: {:?} calculated fidelity: {} is worse than requested fidelity: {}", + specialized.specialization, + specialized.calculated_fidelity, + fid + ))); + } + } + specialized.global_phase += tr.arg(); + Ok(specialized) + }; + void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { + qc::fp basis_fidelity = 1.0; + let target_decomposed = TwoQubitWeylDecomposition::new_inner( + unitary, Some(DEFAULT_FIDELITY), None) + ? ; + let traces = self.traces(&target_decomposed); + let best_nbasis = _num_basis_uses.unwrap_or_else( + || + {traces.into_iter() + .enumerate() + .map(| (idx, trace) | + (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) + .min_by(| (_idx1, fid1), + (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) + .unwrap() .0 as u8}); + let decomposition = match best_nbasis{ + 0 = > decomp0_inner(&target_decomposed), + 1 = > self.decomp1_inner(&target_decomposed), + 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), + 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), + _ = > unreachable !("Invalid basis to use"), + }; + let pulse_optimize = self.pulse_optimize.unwrap_or(true); + let sequence = if pulse_optimize { + self.pulse_optimal_chooser(best_nbasis, &decomposition, + &target_decomposed) + ? + } + else {None}; + if let + Some(seq) = sequence { return Ok(seq); } + let mut target_1q_basis_list = EulerBasisSet::new (); + target_1q_basis_list.add_basis(self.euler_basis); + let euler_decompositions + : SmallVec<[Option; 8]> = + decomposition.iter() + .map(| decomp | + {unitary_to_gate_sequence_inner(decomp.view(), + &target_1q_basis_list, 0, + None, true, None, )}) + .collect(); + let mut gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + let mut global_phase = target_decomposed.global_phase; + global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; + if best_nbasis + == 2 { global_phase += PI; } + for + i in 0..best_nbasis as usize { + if let + Some(euler_decomp) = &euler_decompositions[2 * i] { + for + gate in& euler_decomp.gates { + gates.push((gate.0.into(), gate.1.clone(), smallvec ![0])); + } + global_phase += euler_decomp.global_phase + } + if let + Some(euler_decomp) = &euler_decompositions[2 * i + 1] { + for + gate in& euler_decomp.gates { + gates.push((gate.0.into(), gate.1.clone(), smallvec ![1])); + } + global_phase += euler_decomp.global_phase + } + gates.push( + (self.gate.clone(), self.gate_params.clone(), smallvec ![ 0, 1 ])); + } + if let + Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] { + for + gate in& euler_decomp.gates { + gates.push((gate.0.into(), gate.1.clone(), smallvec ![0])); + } + global_phase += euler_decomp.global_phase + } + if let + Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] { + for + gate in& euler_decomp.gates { + gates.push((gate.0.into(), gate.1.clone(), smallvec ![1])); + } + global_phase += euler_decomp.global_phase + } + Ok(TwoQubitGateSequence{ + gates, + global_phase, + }) + } + std::array, 4> traces(TwoQubitWeylDecomposition target) { + return { + 4. * std::complex( + target.a.cos() * target.b.cos() * target.c.cos(), + target.a.sin() * target.b.sin() * target.c.sin(), ), + 4. * + c64((PI4 - target.a).cos() * + (self.basis_decomposer.b - target.b).cos() * target.c.cos(), + (PI4 - target.a).sin() * + (self.basis_decomposer.b - target.b).sin() * + target.c.sin(), ), + c64(4. * target.c.cos(), 0.), + c64(4., 0.), + }; } }; From 83a75c457018633c5c39cecf500f37360ab6c492 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:02:50 +0000 Subject: [PATCH 003/100] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MQTOpt/Transforms/GateDecomposition.cpp | 3 +- .../Transforms/GateDecompositionPattern.cpp | 945 +++++++++--------- 2 files changed, 474 insertions(+), 474 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index 8ed6be639..a612aca95 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -23,7 +23,8 @@ namespace mqt::ir::opt { /** * @brief This pass attempts to cancel consecutive self-inverse operations. */ -struct GateDecomposition final : impl::GateDecompositionBase { +struct GateDecomposition final + : impl::GateDecompositionBase { void runOnOperation() override { // Get the current operation being operated on. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 3c9af492e..216edcf7b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -128,536 +128,535 @@ struct GateDecompositionPattern final std::array, 4> K1r; std::array, 4> K2r; Specialization specialization; - //EulerBasis default_euler_basis; // TODO: simply use ZYZ? + // EulerBasis default_euler_basis; // TODO: simply use ZYZ? std::optional requested_fidelity; qc::fp calculated_fidelity; dd::TwoQubitGateMatrix unitary_matrix; - TwoQubitWeylDecomposition new_inner( - std::array, 4> unitary_matrix, + TwoQubitWeylDecomposition + new_inner(std::array, 4> unitary_matrix, - std::optional fidelity, - std::optional _specialization - ) { - constexpr std::array, 4> IPZ = [[IM, C_ZERO], [C_ZERO, M_IM]]; - constexpr std::array, 4> IPY =[[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; - constexpr std::array, 4> IPX = [[C_ZERO, IM], [IM, C_ZERO]]; + std::optional fidelity, + std::optional _specialization) { + constexpr std::array, 4> IPZ = + [[IM, C_ZERO], [C_ZERO, M_IM]]; + constexpr std::array, 4> IPY = + [[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; + constexpr std::array, 4> IPX = + [[C_ZERO, IM], [IM, C_ZERO]]; + auto u = unitary_matrix; + let det_u = u.view().into_faer().determinant(); + let det_pow = det_u.powf(-0.25); + u.mapv_inplace(| x | x * det_pow); + let mut global_phase = det_u.arg() / 4.; + let u_p = magic_basis_transform(u.view(), MagicBasisTransform::OutOf); + let m2 = u_p.t().dot(&u_p); + let default_euler_basis = EulerBasis::ZYZ; - auto u = unitary_matrix; - let det_u = u.view().into_faer().determinant(); - let det_pow = det_u.powf(-0.25); - u.mapv_inplace(|x| x * det_pow); - let mut global_phase = det_u.arg() / 4.; - let u_p = magic_basis_transform(u.view(), MagicBasisTransform::OutOf); - let m2 = u_p.t().dot(&u_p); - let default_euler_basis = EulerBasis::ZYZ; - - // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D P^T where - // P ∈ SO(4), D is diagonal with unit-magnitude elements. - // - // We can't use raw `eig` directly because it isn't guaranteed to give us real or orthogonal - // eigenvectors. Instead, since `M2` is complex-symmetric, - // M2 = A + iB - // for real-symmetric `A` and `B`, and as - // M2^+ @ M2 = A^2 + B^2 + i [A, B] = 1 - // we must have `A` and `B` commute, and consequently they are simultaneously diagonalizable. - // Mixing them together _should_ account for any degeneracy problems, but it's not - // guaranteed, so we repeat it a little bit. The fixed seed is to make failures - // deterministic; the value is not important. - let mut state = Pcg64Mcg::seed_from_u64(2023); - let mut found = false; - let mut d: Array1 = Array1::zeros(0); - let mut p: Array2 = Array2::zeros((0, 0)); - for i in 0..100 { - let rand_a: f64; - let rand_b: f64; + // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D + // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. + // + // We can't use raw `eig` directly because it isn't guaranteed to give us + // real or orthogonal eigenvectors. Instead, since `M2` is + // complex-symmetric, + // M2 = A + iB + // for real-symmetric `A` and `B`, and as + // M2^+ @ M2 = A^2 + B^2 + i [A, B] = 1 + // we must have `A` and `B` commute, and consequently they are + // simultaneously diagonalizable. Mixing them together _should_ account + // for any degeneracy problems, but it's not guaranteed, so we repeat it a + // little bit. The fixed seed is to make failures deterministic; the + // value is not important. + let mut state = Pcg64Mcg::seed_from_u64(2023); + let mut found = false; + let mut d : Array1 = Array1::zeros(0); + let mut p : Array2 = Array2::zeros((0, 0)); + for + i in 0..100 { + let rand_a : f64; + let rand_b : f64; // For debugging the algorithm use the same RNG values from the // previous Python implementation for the first random trial. // In most cases this loop only executes a single iteration and // using the same rng values rules out possible RNG differences // as the root cause of a test failure - if i == 0 { + if i + == 0 { rand_a = 1.2602066112249388; rand_b = 0.22317849046722027; - } else { - rand_a = state.sample(StandardNormal); - rand_b = state.sample(StandardNormal); + } + else { + rand_a = state.sample(StandardNormal); + rand_b = state.sample(StandardNormal); } - let m2_real = m2.mapv(|val| rand_a * val.re + rand_b * val.im); - let p_inner = m2_real - .view() - .into_faer() - .self_adjoint_eigen(Lower) - .map_err(|e| QiskitError::new_err(format!("{e:?}")))? - .U() - .into_ndarray() - .mapv(Complex64::from); + let m2_real = m2.mapv(| val | rand_a * val.re + rand_b * val.im); + let p_inner = + m2_real.view().into_faer().self_adjoint_eigen(Lower).map_err( + | e | QiskitError::new_err(format !("{e:?}"))) + ?.U().into_ndarray().mapv(Complex64::from); let d_inner = p_inner.t().dot(&m2).dot(&p_inner).diag().to_owned(); - let mut diag_d: Array2 = Array2::zeros((4, 4)); - diag_d - .diag_mut() - .iter_mut() - .enumerate() - .for_each(|(index, x)| *x = d_inner[index]); + let mut diag_d : Array2 = Array2::zeros((4, 4)); + diag_d.diag_mut().iter_mut().enumerate().for_each( + | (index, x) | * x = d_inner[index]); let compare = p_inner.dot(&diag_d).dot(&p_inner.t()); - found = abs_diff_eq!(compare.view(), m2, epsilon = 1.0e-13); + found = abs_diff_eq !(compare.view(), m2, epsilon = 1.0e-13); if found { - p = p_inner; - d = d_inner; - break; + p = p_inner; + d = d_inner; + break; } - } + } if !found { - return Err(QiskitError::new_err(format!( - "TwoQubitWeylDecomposition: failed to diagonalize M2. Please report this at https://github.com/Qiskit/qiskit-terra/issues/4159. Input: {unitary_matrix:?}" - ))); + return Err(QiskitError::new_err( + format !("TwoQubitWeylDecomposition: failed to diagonalize M2. " + "Please report this at " + "https://github.com/Qiskit/qiskit-terra/issues/4159. " + "Input: {unitary_matrix:?}"))); } - let mut d = -d.map(|x| x.arg() / 2.); + let mut d = -d.map(| x | x.arg() / 2.); d[3] = -d[0] - d[1] - d[2]; - let mut cs: SmallVec<[f64; 3]> = (0..3) - .map(|i| ((d[i] + d[3]) / 2.0).rem_euclid(TWO_PI)) - .collect(); - let cstemp: SmallVec<[f64; 3]> = cs - .iter() - .map(|x| x.rem_euclid(PI2)) - .map(|x| x.min(PI2 - x)) - .collect(); + let mut cs + : SmallVec<[f64; 3]> = + (0..3) + .map(| i | ((d[i] + d[3]) / 2.0).rem_euclid(TWO_PI)) + .collect(); + let cstemp : SmallVec<[f64; 3]> = cs.iter() + .map(| x | x.rem_euclid(PI2)) + .map(| x | x.min(PI2 - x)) + .collect(); let mut order = arg_sort(&cstemp); (order[0], order[1], order[2]) = (order[1], order[2], order[0]); (cs[0], cs[1], cs[2]) = (cs[order[0]], cs[order[1]], cs[order[2]]); (d[0], d[1], d[2]) = (d[order[0]], d[order[1]], d[order[2]]); let mut p_orig = p.clone(); - for (i, item) in order.iter().enumerate().take(3) { - let slice_a = p.slice_mut(s![.., i]); - let slice_b = p_orig.slice_mut(s![.., *item]); - Zip::from(slice_a).and(slice_b).for_each(::std::mem::swap); - } - if p.view().into_faer().determinant().re < 0. { - p.slice_mut(s![.., -1]).mapv_inplace(|x| -x); - } - let mut temp: Array2 = Array2::zeros((4, 4)); - temp.diag_mut() - .iter_mut() - .enumerate() - .for_each(|(index, x)| *x = (IM * d[index]).exp()); - let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), MagicBasisTransform::Into); + for (i, item) + in order.iter().enumerate().take(3) { + let slice_a = p.slice_mut(s ![.., i ]); + let slice_b = p_orig.slice_mut(s ![.., *item ]); + Zip::from(slice_a).and (slice_b).for_each(::std::mem::swap); + } + if p + .view().into_faer().determinant().re < 0. { + p.slice_mut(s ![.., -1 ]).mapv_inplace(| x | -x); + } + let mut temp : Array2 = Array2::zeros((4, 4)); + temp.diag_mut().iter_mut().enumerate().for_each( + | (index, x) | * x = (IM * d[index]).exp()); + let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), + MagicBasisTransform::Into); let k2 = magic_basis_transform(p.t(), MagicBasisTransform::Into); - #[allow(non_snake_case)] - let (mut K1l, mut K1r, phase_l) = decompose_two_qubit_product_gate(k1.view())?; - #[allow(non_snake_case)] - let (K2l, mut K2r, phase_r) = decompose_two_qubit_product_gate(k2.view())?; +#[allow(non_snake_case)] + let(mut K1l, mut K1r, phase_l) = + decompose_two_qubit_product_gate(k1.view()) ? ; +#[allow(non_snake_case)] + let(K2l, mut K2r, phase_r) = + decompose_two_qubit_product_gate(k2.view()) ? ; global_phase += phase_l + phase_r; // Flip into Weyl chamber - if cs[0] > PI2 { + if cs + [0] > PI2 { cs[0] -= PI32; K1l = K1l.dot(&ipy); K1r = K1r.dot(&ipy); global_phase += PI2; - } - if cs[1] > PI2 { + } + if cs + [1] > PI2 { cs[1] -= PI32; K1l = K1l.dot(&ipx); K1r = K1r.dot(&ipx); global_phase += PI2; - } + } let mut conjs = 0; - if cs[0] > PI4 { + if cs + [0] > PI4 { cs[0] = PI2 - cs[0]; K1l = K1l.dot(&ipy); K2r = ipy.dot(&K2r); conjs += 1; global_phase -= PI2; - } - if cs[1] > PI4 { + } + if cs + [1] > PI4 { cs[1] = PI2 - cs[1]; K1l = K1l.dot(&ipx); K2r = ipx.dot(&K2r); conjs += 1; global_phase += PI2; - if conjs == 1 { - global_phase -= PI; - } - } - if cs[2] > PI2 { + if conjs + == 1 { global_phase -= PI; } + } + if cs + [2] > PI2 { cs[2] -= PI32; K1l = K1l.dot(&ipz); K1r = K1r.dot(&ipz); global_phase += PI2; - if conjs == 1 { - global_phase -= PI; - } - } - if conjs == 1 { + if conjs + == 1 { global_phase -= PI; } + } + if conjs + == 1 { cs[2] = PI2 - cs[2]; K1l = K1l.dot(&ipz); K2r = ipz.dot(&K2r); global_phase += PI2; - } - if cs[2] > PI4 { + } + if cs + [2] > PI4 { cs[2] -= PI2; K1l = K1l.dot(&ipz); K1r = K1r.dot(&ipz); global_phase -= PI2; - } - let [a, b, c] = [cs[1], cs[0], cs[2]]; - let is_close = |ap: f64, bp: f64, cp: f64| -> bool { - let [da, db, dc] = [a - ap, b - bp, c - cp]; - let tr = 4. - * c64( - da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), - ); - match fidelity { - Some(fid) => tr.trace_to_fid() >= fid, - // Set to false here to default to general specialization in the absence of a - // fidelity and provided specialization. - None => false, - } + } + let[a, b, c] = [ cs[1], cs[0], cs[2] ]; + let is_close = | ap : f64, bp : f64, cp : f64 |->bool { + let[da, db, dc] = [ a - ap, b - bp, c - cp ]; + let tr = 4. * c64(da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), ); + match fidelity { + Some(fid) = > tr.trace_to_fid() >= fid, + // Set to false here to default to general specialization in the + // absence of a fidelity and provided specialization. + None = > false, + } }; let closest_abc = closest_partial_swap(a, b, c); let closest_ab_minus_c = closest_partial_swap(a, b, -c); let mut flipped_from_original = false; - let specialization = match _specialization { - Some(specialization) => specialization, - None => { - if is_close(0., 0., 0.) { - Specialization::IdEquiv - } else if is_close(PI4, PI4, PI4) || is_close(PI4, PI4, -PI4) { - Specialization::SWAPEquiv - } else if is_close(closest_abc, closest_abc, closest_abc) { - Specialization::PartialSWAPEquiv - } else if is_close(closest_ab_minus_c, closest_ab_minus_c, -closest_ab_minus_c) { - Specialization::PartialSWAPFlipEquiv - } else if is_close(a, 0., 0.) { - Specialization::ControlledEquiv - } else if is_close(PI4, PI4, c) { - Specialization::MirrorControlledEquiv - } else if is_close((a + b) / 2., (a + b) / 2., c) { - Specialization::fSimaabEquiv - } else if is_close(a, (b + c) / 2., (b + c) / 2.) { - Specialization::fSimabbEquiv - } else if is_close(a, (b - c) / 2., (c - b) / 2.) { - Specialization::fSimabmbEquiv - } else { - Specialization::General - } - } - }; - let general = TwoQubitWeylDecomposition { - a, - b, - c, - global_phase, - K1l, - K1r, - K2l, - K2r, - specialization: Specialization::General, - default_euler_basis, - requested_fidelity: fidelity, - calculated_fidelity: -1.0, - unitary_matrix, + let specialization = match _specialization{ + Some(specialization) = > specialization, + None =>{ + if is_close (0., 0., 0.){ + Specialization::IdEquiv} else if is_close (PI4, PI4, PI4) || + is_close(PI4, PI4, -PI4){ + Specialization::SWAPEquiv} else if is_close (closest_abc, + closest_abc, + closest_abc){ + Specialization:: + PartialSWAPEquiv} else if is_close (closest_ab_minus_c, + closest_ab_minus_c, + -closest_ab_minus_c){ + Specialization::PartialSWAPFlipEquiv} else if is_close (a, + 0., + 0.){ + Specialization::ControlledEquiv} else if is_close (PI4, PI4, + c){ + Specialization:: + MirrorControlledEquiv} else if is_close ((a + b) / 2., + (a + b) / 2., + c){ + Specialization::fSimaabEquiv} else if is_close (a, + (b + c) / + 2., + (b + c) / + 2.){ + Specialization::fSimabbEquiv} else if is_close (a, + (b - c) / + 2., + (c - b) / + 2.){ + Specialization::fSimabmbEquiv} else { + Specialization::General}}}; + let general = TwoQubitWeylDecomposition{ + a, + b, + c, + global_phase, + K1l, + K1r, + K2l, + K2r, + specialization : Specialization::General, + default_euler_basis, + requested_fidelity : fidelity, + calculated_fidelity : -1.0, + unitary_matrix, }; - let mut specialized: TwoQubitWeylDecomposition = match specialization { - // :math:`U \sim U_d(0,0,0) \sim Id` - // - // This gate binds 0 parameters, we make it canonical by setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::IdEquiv => TwoQubitWeylDecomposition { - specialization, - a: 0., - b: 0., - c: 0., - K1l: general.K1l.dot(&general.K2l), - K1r: general.K1r.dot(&general.K2r), - K2l: Array2::eye(2), - K2r: Array2::eye(2), - ..general - }, - // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, -\pi/4) \sim \text{SWAP}` - // - // This gate binds 0 parameters, we make it canonical by setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::SWAPEquiv => { - if c > 0. { - TwoQubitWeylDecomposition { - specialization, - a: PI4, - b: PI4, - c: PI4, - K1l: general.K1l.dot(&general.K2r), - K1r: general.K1r.dot(&general.K2l), - K2l: Array2::eye(2), - K2r: Array2::eye(2), - ..general - } - } else { - flipped_from_original = true; - TwoQubitWeylDecomposition { - specialization, - a: PI4, - b: PI4, - c: PI4, - global_phase: global_phase + PI2, - K1l: general.K1l.dot(&ipz).dot(&general.K2r), - K1r: general.K1r.dot(&ipz).dot(&general.K2l), - K2l: Array2::eye(2), - K2r: Array2::eye(2), - ..general - } - } - } - // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim \text{SWAP}^\alpha` - // - // This gate binds 3 parameters, we make it canonical by setting: - // - // :math:`K2_l = Id`. - Specialization::PartialSWAPEquiv => { - let closest = closest_partial_swap(a, b, c); - let mut k2l_dag = general.K2l.t().to_owned(); - k2l_dag.view_mut().mapv_inplace(|x| x.conj()); - TwoQubitWeylDecomposition { - specialization, - a: closest, - b: closest, - c: closest, - K1l: general.K1l.dot(&general.K2l), - K1r: general.K1r.dot(&general.K2l), - K2r: k2l_dag.dot(&general.K2r), - K2l: Array2::eye(2), - ..general - } - } - // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim \text{SWAP}^\alpha` - // - // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv - // similar to how :math:`x = (\pm \sqrt(x))^2`) - // - // This gate binds 3 parameters, we make it canonical by setting: - // - // :math:`K2_l = Id` - Specialization::PartialSWAPFlipEquiv => { - let closest = closest_partial_swap(a, b, -c); - let mut k2l_dag = general.K2l.t().to_owned(); - k2l_dag.mapv_inplace(|x| x.conj()); - TwoQubitWeylDecomposition { - specialization, - a: closest, - b: closest, - c: -closest, - K1l: general.K1l.dot(&general.K2l), - K1r: general.K1r.dot(&ipz).dot(&general.K2l).dot(&ipz), - K2r: ipz.dot(&k2l_dag).dot(&ipz).dot(&general.K2r), - K2l: Array2::eye(2), - ..general - } - } - // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` - // - // This gate binds 4 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , - // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . - Specialization::ControlledEquiv => { - let euler_basis = EulerBasis::XYX; - let [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - let [k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, - a, - b: 0., - c: 0., - global_phase: global_phase + k2lphase + k2rphase, - K1l: general.K1l.dot(&rx_matrix(k2lphi)), - K1r: general.K1r.dot(&rx_matrix(k2rphi)), - K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r: ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), - default_euler_basis: euler_basis, - ..general - } - } - // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot \text{Ctrl-U}` - // - // This gate binds 4 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = Ry(\theta_r)\cdot Rz(\lambda_r)` - Specialization::MirrorControlledEquiv => { - let [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - let [k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { - specialization, - a: PI4, - b: PI4, - c, - global_phase: global_phase + k2lphase + k2rphase, - K1l: general.K1l.dot(&rz_matrix(k2rphi)), - K1r: general.K1r.dot(&rz_matrix(k2lphi)), - K2l: ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r: ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), - ..general - } - } - // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. - Specialization::fSimaabEquiv => { - let [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { + let mut specialized + : TwoQubitWeylDecomposition = match specialization{ + // :math:`U \sim U_d(0,0,0) \sim Id` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::IdEquiv = > TwoQubitWeylDecomposition{ specialization, - a: (a + b) / 2., - b: (a + b) / 2., - c, - global_phase: global_phase + k2lphase, - K1r: general.K1r.dot(&rz_matrix(k2lphi)), - K1l: general.K1l.dot(&rz_matrix(k2lphi)), - K2l: ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r: rz_matrix(-k2lphi).dot(&general.K2r), + a : 0., + b : 0., + c : 0., + K1l : general.K1l.dot(&general.K2l), + K1r : general.K1r.dot(&general.K2r), + K2l : Array2::eye(2), + K2r : Array2::eye(2), ..general - } - } - // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabbEquiv => { - let euler_basis = EulerBasis::XYX; - let [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, - a, - b: (b + c) / 2., - c: (b + c) / 2., - global_phase: global_phase + k2lphase, - K1r: general.K1r.dot(&rx_matrix(k2lphi)), - K1l: general.K1l.dot(&rx_matrix(k2lphi)), - K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r: rx_matrix(-k2lphi).dot(&general.K2r), - default_euler_basis: euler_basis, - ..general - } - } - // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabmbEquiv => { - let euler_basis = EulerBasis::XYX; - let [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, - a, - b: (b - c) / 2., - c: -((b - c) / 2.), - global_phase: global_phase + k2lphase, - K1l: general.K1l.dot(&rx_matrix(k2lphi)), - K1r: general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), - K2l: ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r: ipz.dot(&rx_matrix(-k2lphi)).dot(&ipz).dot(&general.K2r), - default_euler_basis: euler_basis, - ..general - } - } - // U has no special symmetry. - // - // This gate binds all 6 possible parameters, so there is no need to make the single-qubit - // pre-/post-gates canonical. - Specialization::General => general, - }; - - let tr = if flipped_from_original { - let [da, db, dc] = [ - PI2 - a - specialized.a, - b - specialized.b, - -c - specialized.c, - ]; - 4. * c64( - da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), - ) - } else { - let [da, db, dc] = [a - specialized.a, b - specialized.b, c - specialized.c]; - 4. * c64( - da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), - ) - }; - specialized.calculated_fidelity = tr.trace_to_fid(); - if let Some(fid) = specialized.requested_fidelity { - if specialized.calculated_fidelity + 1.0e-13 < fid { - return Err(QiskitError::new_err(format!( - "Specialization: {:?} calculated fidelity: {} is worse than requested fidelity: {}", - specialized.specialization, - specialized.calculated_fidelity, - fid - ))); - } + }, + // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, + // -\pi/4) \sim \text{SWAP}` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::SWAPEquiv =>{ + if c > 0. {TwoQubitWeylDecomposition{ + specialization, + a : PI4, + b : PI4, + c : PI4, + K1l : general.K1l.dot(&general.K2r), + K1r : general.K1r.dot(&general.K2l), + K2l : Array2::eye(2), + K2r : Array2::eye(2), + ..general + }} else {flipped_from_original = true; + TwoQubitWeylDecomposition { + specialization, + a : PI4, + b : PI4, + c : PI4, + global_phase : global_phase + PI2, + K1l : general.K1l.dot(&ipz).dot(&general.K2r), + K1r : general.K1r.dot(&ipz).dot(&general.K2l), + K2l : Array2::eye(2), + K2r : Array2::eye(2), + ..general } - specialized.global_phase += tr.arg(); - Ok(specialized) - }; - - void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { - qc::fp basis_fidelity = 1.0; - let target_decomposed = TwoQubitWeylDecomposition::new_inner( - unitary, Some(DEFAULT_FIDELITY), None) - ? ; - let traces = self.traces(&target_decomposed); - let best_nbasis = _num_basis_uses.unwrap_or_else( - || - {traces.into_iter() - .enumerate() - .map(| (idx, trace) | - (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) - .min_by(| (_idx1, fid1), - (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) - .unwrap() .0 as u8}); - let decomposition = match best_nbasis{ - 0 = > decomp0_inner(&target_decomposed), - 1 = > self.decomp1_inner(&target_decomposed), - 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), - 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), - _ = > unreachable !("Invalid basis to use"), - }; - let pulse_optimize = self.pulse_optimize.unwrap_or(true); - let sequence = if pulse_optimize { - self.pulse_optimal_chooser(best_nbasis, &decomposition, - &target_decomposed) - ? } - else {None}; - if let - Some(seq) = sequence { return Ok(seq); } - let mut target_1q_basis_list = EulerBasisSet::new (); - target_1q_basis_list.add_basis(self.euler_basis); - let euler_decompositions - : SmallVec<[Option; 8]> = - decomposition.iter() - .map(| decomp | - {unitary_to_gate_sequence_inner(decomp.view(), - &target_1q_basis_list, 0, - None, true, None, )}) - .collect(); - let mut gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - let mut global_phase = target_decomposed.global_phase; - global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; - if best_nbasis - == 2 { global_phase += PI; } + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim + // \text{SWAP}^\alpha` + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id`. + Specialization::PartialSWAPEquiv => { + let closest = closest_partial_swap(a, b, c); + let mut k2l_dag = general.K2l.t().to_owned(); + k2l_dag.view_mut().mapv_inplace(| x | x.conj()); + TwoQubitWeylDecomposition { + specialization, a : closest, + b : closest, + c : closest, + K1l : general.K1l.dot(&general.K2l), + K1r : general.K1r.dot(&general.K2l), + K2r : k2l_dag.dot(&general.K2r), + K2l : Array2::eye(2), + ..general + } + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim + // \text{SWAP}^\alpha` + // + // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv + // similar to how :math:`x = (\pm \sqrt(x))^2`) + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id` + Specialization::PartialSWAPFlipEquiv => { + let closest = closest_partial_swap(a, b, -c); + let mut k2l_dag = general.K2l.t().to_owned(); + k2l_dag.mapv_inplace(| x | x.conj()); + TwoQubitWeylDecomposition { + specialization, + a : closest, + b : closest, + c : -closest, + K1l : general.K1l.dot(&general.K2l), + K1r : general.K1r.dot(&ipz).dot(&general.K2l).dot(&ipz), + K2r : ipz.dot(&k2l_dag).dot(&ipz).dot(&general.K2r), + K2l : Array2::eye(2), + ..general + } + } + // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , + // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . + Specialization::ControlledEquiv => { + let euler_basis = EulerBasis::XYX; + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + let[k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, a, b : 0., c : 0., + global_phase : global_phase + k2lphase + k2rphase, + K1l : general.K1l.dot(&rx_matrix(k2lphi)), + K1r : general.K1r.dot(&rx_matrix(k2rphi)), + K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r : ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), + default_euler_basis : euler_basis, ..general + } + } + // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot + // \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = + // Ry(\theta_r)\cdot Rz(\lambda_r)` + Specialization::MirrorControlledEquiv => { + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + let[k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, + a : PI4, + b : PI4, + c, + global_phase : global_phase + k2lphase + k2rphase, + K1l : general.K1l.dot(&rz_matrix(k2rphi)), + K1r : general.K1r.dot(&rz_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r : ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), + ..general + } + } + // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. + Specialization::fSimaabEquiv => { + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, a : (a + b) / 2., b : (a + b) / 2., c, + global_phase : global_phase + k2lphase, + K1r : general.K1r.dot(&rz_matrix(k2lphi)), + K1l : general.K1l.dot(&rz_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r : rz_matrix(-k2lphi).dot(&general.K2r), ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabbEquiv => { + let euler_basis = EulerBasis::XYX; + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, a, b : (b + c) / 2., c : (b + c) / 2., + global_phase : global_phase + k2lphase, + K1r : general.K1r.dot(&rx_matrix(k2lphi)), + K1l : general.K1l.dot(&rx_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r : rx_matrix(-k2lphi).dot(&general.K2r), + default_euler_basis : euler_basis, ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabmbEquiv => { + let euler_basis = EulerBasis::XYX; + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, a, b : (b - c) / 2., + c : -((b - c) / 2.), + global_phase : global_phase + k2lphase, + K1l : general.K1l.dot(&rx_matrix(k2lphi)), + K1r : general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), + K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r : ipz.dot(&rx_matrix(-k2lphi)).dot(&ipz).dot(&general.K2r), + default_euler_basis : euler_basis, + ..general + } + } + // U has no special symmetry. + // + // This gate binds all 6 possible parameters, so there is no need to make the + // single-qubit pre-/post-gates canonical. + Specialization::General = > general, +}; + +let tr = if flipped_from_original { + let[da, db, dc] = [ + PI2 - a - specialized.a, + b - specialized.b, + -c - specialized.c, + ]; + 4. * c64(da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) +} +else { + let[da, db, dc] = [ a - specialized.a, b - specialized.b, c - specialized.c ]; + 4. * c64(da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) +}; +specialized.calculated_fidelity = tr.trace_to_fid(); +if let + Some(fid) = specialized.requested_fidelity { + if specialized + .calculated_fidelity + 1.0e-13 < fid { + return Err(QiskitError::new_err(format !( + "Specialization: {:?} calculated fidelity: {} is worse than " + "requested fidelity: {}", + specialized.specialization, specialized.calculated_fidelity, fid))); + } + } +specialized.global_phase += tr.arg(); +Ok(specialized) +}; + +void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { + qc::fp basis_fidelity = 1.0; + let target_decomposed = TwoQubitWeylDecomposition::new_inner( + unitary, Some(DEFAULT_FIDELITY), None) + ? ; + let traces = self.traces(&target_decomposed); + let best_nbasis = _num_basis_uses.unwrap_or_else( + || + {traces.into_iter() + .enumerate() + .map(| (idx, trace) | + (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) + .min_by(| (_idx1, fid1), + (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) + .unwrap() .0 as u8}); + let decomposition = match best_nbasis{ + 0 = > decomp0_inner(&target_decomposed), + 1 = > self.decomp1_inner(&target_decomposed), + 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), + 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), + _ = > unreachable !("Invalid basis to use"), + }; + let pulse_optimize = self.pulse_optimize.unwrap_or(true); + let sequence = if pulse_optimize { + self.pulse_optimal_chooser(best_nbasis, &decomposition, &target_decomposed) + ? + } + else {None}; + if let + Some(seq) = sequence { return Ok(seq); } + let mut target_1q_basis_list = EulerBasisSet::new (); + target_1q_basis_list.add_basis(self.euler_basis); + let euler_decompositions + : SmallVec<[Option; 8]> = + decomposition.iter() + .map(| decomp | + {unitary_to_gate_sequence_inner(decomp.view(), + &target_1q_basis_list, 0, + None, true, None, )}) + .collect(); + let mut gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + let mut global_phase = target_decomposed.global_phase; + global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; + if best_nbasis + == 2 { global_phase += PI; } for i in 0..best_nbasis as usize { if let @@ -699,24 +698,24 @@ struct GateDecompositionPattern final gates, global_phase, }) - } +} - std::array, 4> traces(TwoQubitWeylDecomposition target) { - return { - 4. * std::complex( - target.a.cos() * target.b.cos() * target.c.cos(), - target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * - c64((PI4 - target.a).cos() * - (self.basis_decomposer.b - target.b).cos() * target.c.cos(), - (PI4 - target.a).sin() * - (self.basis_decomposer.b - target.b).sin() * - target.c.sin(), ), - c64(4. * target.c.cos(), 0.), - c64(4., 0.), - }; - } -}; +std::array, 4> traces(TwoQubitWeylDecomposition target) { + return { + 4. * std::complex( + target.a.cos() * target.b.cos() * target.c.cos(), + target.a.sin() * target.b.sin() * target.c.sin(), ), + 4. * c64((PI4 - target.a).cos() * + (self.basis_decomposer.b - target.b).cos() * target.c.cos(), + (PI4 - target.a).sin() * + (self.basis_decomposer.b - target.b).sin() * + target.c.sin(), ), + c64(4. * target.c.cos(), 0.), + c64(4., 0.), + }; +} +} +; /** * @brief Populates the given pattern set with patterns for gate From 1dc459bac791f747b42a9fd4d1e33e31c9796684 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 6 Oct 2025 22:23:59 +0200 Subject: [PATCH 004/100] snapshot --- .../Transforms/GateDecompositionPattern.cpp | 730 +++++++++++------- 1 file changed, 444 insertions(+), 286 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 216edcf7b..57d7c52b7 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include #include @@ -118,6 +118,160 @@ struct GateDecompositionPattern final fSimabmbEquiv, }; + enum class MagicBasisTransform { + Into, + OutOf, + }; + + enum class EulerBasis { + U3 = 0, + U321 = 1, + U = 2, + PSX = 3, + U1X = 4, + RR = 5, + ZYZ = 6, + ZXZ = 7, + XZX = 8, + XYX = 9, + ZSXX = 10, + ZSX = 11, + }; + + using qfp = std::complex; + using diagonal4x4 = std::array; + using vector2d = std::vector; + using matrix2x2 = std::array; + using matrix4x4 = std::array; + + static constexpr matrix2x2 identityGate = {1, 0, 0, 1}; + + static qc::fp remEuclid(qc::fp a, qc::fp b) { + auto r = std::fmod(a, b); + return (r < 0.0) ? r + std::abs(b) : r; + } + + static matrix2x2 dot(const matrix2x2& lhs, const matrix2x2& rhs) { + return lhs; + } + static matrix4x4 dot(const matrix4x4& lhs, const matrix4x4& rhs) { + return lhs; + } + + static matrix2x2 transpose(const matrix2x2& x) { return x; } + static matrix4x4 transpose(const matrix4x4& x) { return x; } + + static qfp determinant(const matrix2x2& x) { return 0.0; }; + static qfp determinant(const matrix4x4& x) { return 0.0; }; + + static matrix2x2 multiply(qfp factor, matrix2x2 matrix) { + llvm::transform(matrix, matrix.begin(), + [&](auto&& x) { return factor * x; }); + return matrix; + } + + static matrix4x4 kroneckerProduct(const matrix2x2& lhs, + const matrix2x2& rhs) { + return from(multiply(lhs[0 * 2 + 0], rhs), multiply(lhs[0 * 2 + 1], rhs), + multiply(lhs[1 * 2 + 0], rhs), multiply(lhs[1 * 2 + 1], rhs)); + } + + static matrix4x4 from(const matrix2x2& first_quadrant, + const matrix2x2& second_quadrant, + const matrix2x2& third_quadrant, + const matrix2x2& fourth_quadrant) { + return { + first_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], + second_quadrant[0 * 2 + 0], second_quadrant[0 * 2 + 1], + first_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], + second_quadrant[1 * 2 + 0], second_quadrant[1 * 2 + 1], + third_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], + fourth_quadrant[0 * 2 + 0], fourth_quadrant[0 * 2 + 1], + third_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], + fourth_quadrant[1 * 2 + 0], fourth_quadrant[1 * 2 + 1], + }; + } + + // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen + static matrix4x4 self_adjoint_eigen_lower(const matrix4x4& x) { return x; } + + static std::tuple + decompose_two_qubit_product_gate(matrix4x4 special_unitary) { + // first quadrant + matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], + special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; + auto det_r = determinant(r); + if (std::abs(det_r) < 0.1) { + // third quadrant + r = {special_unitary[2 * 4 + 0], special_unitary[2 * 4 + 1], + special_unitary[3 * 4 + 0], special_unitary[3 * 4 + 1]}; + det_r = determinant(r); + } + if (std::abs(det_r) < 0.1) { + throw std::runtime_error{ + "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; + } + llvm::transform(r, r.begin(), + [&](auto&& x) { return x / std::sqrt(det_r); }); + // transpose with complex conjugate of each element + matrix2x2 r_t_conj; + llvm::transform(transpose(r), r_t_conj.begin(), + [](auto&& x) { return std::conj(x); }); + + auto temp = kroneckerProduct(identityGate, r_t_conj); + temp = dot(special_unitary, temp); + + // [[a, b, c, d], + // [e, f, g, h], => [[a, c], + // [i, j, k, l], [i, k]] + // [m, n, o, p]] + matrix2x2 l = {temp[0 * 4 + 0], temp[0 * 4 + 2], temp[2 * 4 + 0], + temp[2 * 4 + 2]}; + auto det_l = determinant(l); + if (std::abs(det_l) < 0.9) { + throw std::runtime_error{ + "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9"}; + } + llvm::transform(l, l.begin(), + [&](auto&& x) { return x / std::sqrt(det_l); }); + auto phase = std::arg(det_l) / 2.; + + return {l, r, phase}; + } + + static diagonal4x4 diagonal(const matrix4x4& matrix) { + return {matrix[0 * 4 + 0], matrix[1 * 4 + 1], matrix[2 * 4 + 2], + matrix[3 * 4 + 3]}; + } + + static matrix4x4 magic_basis_transform(const matrix4x4& unitary, + MagicBasisTransform direction) { + constexpr matrix4x4 B_NON_NORMALIZED = { + C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, + C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, + }; + + constexpr matrix4x4 B_NON_NORMALIZED_DAGGER = { + qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.), + qfp(0., -0.5), C_ZERO, C_ZERO, qfp(0., 0.5), + C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO, + C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO, + }; + if (direction == MagicBasisTransform::OutOf) { + return dot(dot(B_NON_NORMALIZED_DAGGER, unitary), B_NON_NORMALIZED); + } + if (direction == MagicBasisTransform::Into) { + return dot(dot(B_NON_NORMALIZED, unitary), B_NON_NORMALIZED_DAGGER); + } + throw std::logic_error{"Unknown MagicBasisTransform direction!"}; + } + + static constexpr std::complex C_ZERO{0., 0.}; + static constexpr std::complex C_ONE{1., 0.}; + static constexpr std::complex C_M_ONE{-1., 0.}; + static constexpr std::complex IM{0., 1.}; + static constexpr std::complex M_IM{0., -1.}; + struct TwoQubitWeylDecomposition { qc::fp a; qc::fp b; @@ -133,26 +287,26 @@ struct GateDecompositionPattern final qc::fp calculated_fidelity; dd::TwoQubitGateMatrix unitary_matrix; - TwoQubitWeylDecomposition - new_inner(std::array, 4> unitary_matrix, + static TwoQubitWeylDecomposition + new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { - constexpr std::array, 4> IPZ = - [[IM, C_ZERO], [C_ZERO, M_IM]]; - constexpr std::array, 4> IPY = - [[C_ZERO, C_ONE], [C_M_ONE, C_ZERO]]; - constexpr std::array, 4> IPX = - [[C_ZERO, IM], [IM, C_ZERO]]; - - auto u = unitary_matrix; - let det_u = u.view().into_faer().determinant(); - let det_pow = det_u.powf(-0.25); - u.mapv_inplace(| x | x * det_pow); - let mut global_phase = det_u.arg() / 4.; - let u_p = magic_basis_transform(u.view(), MagicBasisTransform::OutOf); - let m2 = u_p.t().dot(&u_p); - let default_euler_basis = EulerBasis::ZYZ; + constexpr std::array, 4> IPZ = {IM, C_ZERO, C_ZERO, + M_IM}; + constexpr std::array, 4> IPY = {C_ZERO, C_ONE, + C_M_ONE, C_ZERO}; + constexpr std::array, 4> IPX = {C_ZERO, IM, IM, + C_ZERO}; + + auto& u = unitary_matrix; + auto det_u = determinant(u); + auto det_pow = std::pow(det_u, static_cast(-0.25)); + llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); + auto global_phase = std::arg(det_u) / 4.; + auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); + auto m2 = dot(transpose(u_p), u_p); + auto default_euler_basis = EulerBasis::ZYZ; // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -168,264 +322,266 @@ struct GateDecompositionPattern final // for any degeneracy problems, but it's not guaranteed, so we repeat it a // little bit. The fixed seed is to make failures deterministic; the // value is not important. - let mut state = Pcg64Mcg::seed_from_u64(2023); - let mut found = false; - let mut d : Array1 = Array1::zeros(0); - let mut p : Array2 = Array2::zeros((0, 0)); - for - i in 0..100 { - let rand_a : f64; - let rand_b : f64; - // For debugging the algorithm use the same RNG values from the - // previous Python implementation for the first random trial. - // In most cases this loop only executes a single iteration and - // using the same rng values rules out possible RNG differences - // as the root cause of a test failure - if i - == 0 { - rand_a = 1.2602066112249388; - rand_b = 0.22317849046722027; - } - else { - rand_a = state.sample(StandardNormal); - rand_b = state.sample(StandardNormal); - } - let m2_real = m2.mapv(| val | rand_a * val.re + rand_b * val.im); - let p_inner = - m2_real.view().into_faer().self_adjoint_eigen(Lower).map_err( - | e | QiskitError::new_err(format !("{e:?}"))) - ?.U().into_ndarray().mapv(Complex64::from); - let d_inner = p_inner.t().dot(&m2).dot(&p_inner).diag().to_owned(); - let mut diag_d : Array2 = Array2::zeros((4, 4)); - diag_d.diag_mut().iter_mut().enumerate().for_each( - | (index, x) | * x = d_inner[index]); - - let compare = p_inner.dot(&diag_d).dot(&p_inner.t()); - found = abs_diff_eq !(compare.view(), m2, epsilon = 1.0e-13); - if found { - p = p_inner; - d = d_inner; - break; - } - } - if !found { - return Err(QiskitError::new_err( - format !("TwoQubitWeylDecomposition: failed to diagonalize M2. " - "Please report this at " - "https://github.com/Qiskit/qiskit-terra/issues/4159. " - "Input: {unitary_matrix:?}"))); + auto state = std::mt19937{2023}; + std::normal_distribution dist; + auto found = false; + diagonal4x4 d; + matrix4x4 p; + for (int i = 0; i < 100; ++i) { + qc::fp rand_a; + qc::fp rand_b; + // For debugging the algorithm use the same RNG values from the + // previous Python implementation for the first random trial. + // In most cases this loop only executes a single iteration and + // using the same rng values rules out possible RNG differences + // as the root cause of a test failure + if (i == 0) { + rand_a = 1.2602066112249388; + rand_b = 0.22317849046722027; + } else { + rand_a = dist(state); + rand_b = dist(state); } - let mut d = -d.map(| x | x.arg() / 2.); - d[3] = -d[0] - d[1] - d[2]; - let mut cs - : SmallVec<[f64; 3]> = - (0..3) - .map(| i | ((d[i] + d[3]) / 2.0).rem_euclid(TWO_PI)) - .collect(); - let cstemp : SmallVec<[f64; 3]> = cs.iter() - .map(| x | x.rem_euclid(PI2)) - .map(| x | x.min(PI2 - x)) - .collect(); - let mut order = arg_sort(&cstemp); - (order[0], order[1], order[2]) = (order[1], order[2], order[0]); - (cs[0], cs[1], cs[2]) = (cs[order[0]], cs[order[1]], cs[order[2]]); - (d[0], d[1], d[2]) = (d[order[0]], d[order[1]], d[order[2]]); - let mut p_orig = p.clone(); - for (i, item) - in order.iter().enumerate().take(3) { - let slice_a = p.slice_mut(s ![.., i ]); - let slice_b = p_orig.slice_mut(s ![.., *item ]); - Zip::from(slice_a).and (slice_b).for_each(::std::mem::swap); - } - if p - .view().into_faer().determinant().re < 0. { - p.slice_mut(s ![.., -1 ]).mapv_inplace(| x | -x); - } - let mut temp : Array2 = Array2::zeros((4, 4)); - temp.diag_mut().iter_mut().enumerate().for_each( - | (index, x) | * x = (IM * d[index]).exp()); - let k1 = magic_basis_transform(u_p.dot(&p).dot(&temp).view(), - MagicBasisTransform::Into); - let k2 = magic_basis_transform(p.t(), MagicBasisTransform::Into); - -#[allow(non_snake_case)] - let(mut K1l, mut K1r, phase_l) = - decompose_two_qubit_product_gate(k1.view()) ? ; -#[allow(non_snake_case)] - let(K2l, mut K2r, phase_r) = - decompose_two_qubit_product_gate(k2.view()) ? ; - global_phase += phase_l + phase_r; - - // Flip into Weyl chamber - if cs - [0] > PI2 { - cs[0] -= PI32; - K1l = K1l.dot(&ipy); - K1r = K1r.dot(&ipy); - global_phase += PI2; - } - if cs - [1] > PI2 { - cs[1] -= PI32; - K1l = K1l.dot(&ipx); - K1r = K1r.dot(&ipx); - global_phase += PI2; - } - let mut conjs = 0; - if cs - [0] > PI4 { - cs[0] = PI2 - cs[0]; - K1l = K1l.dot(&ipy); - K2r = ipy.dot(&K2r); - conjs += 1; - global_phase -= PI2; - } - if cs - [1] > PI4 { - cs[1] = PI2 - cs[1]; - K1l = K1l.dot(&ipx); - K2r = ipx.dot(&K2r); - conjs += 1; - global_phase += PI2; - if conjs - == 1 { global_phase -= PI; } - } - if cs - [2] > PI2 { - cs[2] -= PI32; - K1l = K1l.dot(&ipz); - K1r = K1r.dot(&ipz); - global_phase += PI2; - if conjs - == 1 { global_phase -= PI; } - } - if conjs - == 1 { - cs[2] = PI2 - cs[2]; - K1l = K1l.dot(&ipz); - K2r = ipz.dot(&K2r); - global_phase += PI2; - } - if cs - [2] > PI4 { - cs[2] -= PI2; - K1l = K1l.dot(&ipz); - K1r = K1r.dot(&ipz); - global_phase -= PI2; - } - let[a, b, c] = [ cs[1], cs[0], cs[2] ]; - let is_close = | ap : f64, bp : f64, cp : f64 |->bool { - let[da, db, dc] = [ a - ap, b - bp, c - cp ]; - let tr = 4. * c64(da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), ); - match fidelity { - Some(fid) = > tr.trace_to_fid() >= fid, - // Set to false here to default to general specialization in the - // absence of a fidelity and provided specialization. - None = > false, - } - }; - - let closest_abc = closest_partial_swap(a, b, c); - let closest_ab_minus_c = closest_partial_swap(a, b, -c); - let mut flipped_from_original = false; - let specialization = match _specialization{ - Some(specialization) = > specialization, - None =>{ - if is_close (0., 0., 0.){ - Specialization::IdEquiv} else if is_close (PI4, PI4, PI4) || - is_close(PI4, PI4, -PI4){ - Specialization::SWAPEquiv} else if is_close (closest_abc, - closest_abc, - closest_abc){ - Specialization:: - PartialSWAPEquiv} else if is_close (closest_ab_minus_c, - closest_ab_minus_c, - -closest_ab_minus_c){ - Specialization::PartialSWAPFlipEquiv} else if is_close (a, - 0., - 0.){ - Specialization::ControlledEquiv} else if is_close (PI4, PI4, - c){ - Specialization:: - MirrorControlledEquiv} else if is_close ((a + b) / 2., - (a + b) / 2., - c){ - Specialization::fSimaabEquiv} else if is_close (a, - (b + c) / - 2., - (b + c) / - 2.){ - Specialization::fSimabbEquiv} else if is_close (a, - (b - c) / - 2., - (c - b) / - 2.){ - Specialization::fSimabmbEquiv} else { - Specialization::General}}}; - let general = TwoQubitWeylDecomposition{ - a, - b, - c, - global_phase, - K1l, - K1r, - K2l, - K2r, - specialization : Specialization::General, - default_euler_basis, - requested_fidelity : fidelity, - calculated_fidelity : -1.0, - unitary_matrix, - }; - let mut specialized - : TwoQubitWeylDecomposition = match specialization{ - // :math:`U \sim U_d(0,0,0) \sim Id` - // - // This gate binds 0 parameters, we make it canonical by - // setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::IdEquiv = > TwoQubitWeylDecomposition{ - specialization, - a : 0., - b : 0., - c : 0., - K1l : general.K1l.dot(&general.K2l), - K1r : general.K1r.dot(&general.K2r), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general - }, - // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, - // -\pi/4) \sim \text{SWAP}` - // - // This gate binds 0 parameters, we make it canonical by - // setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::SWAPEquiv =>{ - if c > 0. {TwoQubitWeylDecomposition{ - specialization, - a : PI4, - b : PI4, - c : PI4, - K1l : general.K1l.dot(&general.K2r), - K1r : general.K1r.dot(&general.K2l), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general - }} else {flipped_from_original = true; - TwoQubitWeylDecomposition { - specialization, - a : PI4, - b : PI4, - c : PI4, - global_phase : global_phase + PI2, - K1l : general.K1l.dot(&ipz).dot(&general.K2r), - K1r : general.K1r.dot(&ipz).dot(&general.K2l), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general + matrix4x4 m2_real; + llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { + return rand_a * val.real() + rand_b * val.imag(); + }); + matrix4x4 p_inner = self_adjoint_eigen_lower(m2_real); + auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); + matrix4x4 diag_d{}; // zero initialization + diag_d[0 * 4 + 0] = d_inner[0]; + diag_d[1 * 4 + 1] = d_inner[1]; + diag_d[2 * 4 + 2] = d_inner[2]; + diag_d[3 * 4 + 3] = d_inner[3]; + + auto compare = dot(dot(p_inner, diag_d), transpose(p_inner)); + found = llvm::all_of_zip(compare, m2, [](auto&& a, auto&& b) { + return std::abs(a - b) < 1.0e-13; + }); + if (found) { + p = p_inner; + d = d_inner; + break; + } + } + if (!found) { + throw std::runtime_error{ + "TwoQubitWeylDecomposition: failed to diagonalize M2."}; + } + std::array d_real; + llvm::transform(d, d_real.begin(), + [](auto&& x) { return -std::arg(x) / 2.0; }); + d_real[3] = -d_real[0] - d_real[1] - d_real[2]; + std::array cs; + for (int i = 0; i < cs.size(); ++i) { + assert(i < d_real.size()); + cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::PI_2); + } + decltype(cs) cstemp; + llvm::transform(cs, cstemp.begin(), [](auto&& x) { + auto tmp = remEuclid(x, qc::PI_2); + return std::min(tmp, qc::PI_2 - tmp); + }); + std::array order{0, 1, 2}; + llvm::stable_sort(order, + [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); + std::tie(order[0], order[1], order[2]) = {order[1], order[2], order[0]}; + std::tie(cs[0], cs[1], cs[2]) = {cs[order[0]], cs[order[1]], + cs[order[2]]}; + std::tie(d_real[0], d_real[1], d_real[2]) = { + d_real[order[0]], d_real[order[1]], d_real[order[2]]}; + + // swap columns of p according to order + constexpr auto P_ROW_LENGTH = 4; + auto p_orig = p; + for (int i = 0; i < order.size(); ++i) { + for (std::size_t row = 0; row < P_ROW_LENGTH; ++row) { + std::swap(p[row * 3 + i], p_orig[row * 3 + order[i]]); + } + } + + if (determinant(p).real() < 0.0) { + // negate last column + for (int i = 0; i < P_ROW_LENGTH; ++i) { + auto& x = p[i * P_ROW_LENGTH + P_ROW_LENGTH - 1]; + x = -x; + } + } + + matrix4x4 temp{}; + temp[0 * 4 + 0] = std::exp(IM * d_real[0]); + temp[1 * 4 + 1] = std::exp(IM * d_real[1]); + temp[2 * 4 + 2] = std::exp(IM * d_real[2]); + temp[3 * 4 + 3] = std::exp(IM * d_real[3]); + auto k1 = magic_basis_transform(dot(dot(u_p, p), temp), + MagicBasisTransform::Into); + auto k2 = magic_basis_transform(transpose(p), MagicBasisTransform::Into); + + auto [K1l, K1r, phase_l] = decompose_two_qubit_product_gate(k1); + auto [K2l, K2r, phase_r] = decompose_two_qubit_product_gate(k2); + global_phase += phase_l + phase_r; + + // Flip into Weyl chamber + if (cs[0] > qc::PI_2) { + cs[0] -= 3.0 * qc::PI_2; + K1l = dot(K1l, IPY); + K1r = dot(K1r, IPY); + global_phase += qc::PI_2; + } + if (cs[1] > qc::PI_2) { + cs[1] -= 3.0 * qc::PI_2; + K1l = dot(K1l, IPX); + K1r = dot(K1r, IPX); + global_phase += qc::PI_2; + } + auto conjs = 0; + if (cs[0] > qc::PI_4) { + cs[0] = qc::PI_2 - cs[0]; + K1l = dot(K1l, IPY); + K2r = dot(IPY, K2r); + conjs += 1; + global_phase -= qc::PI_2; + } + if (cs[1] > qc::PI_4) { + cs[1] = qc::PI_2 - cs[1]; + K1l = dot(K1l, IPX); + K2r = dot(IPX, K2r); + conjs += 1; + global_phase += qc::PI_2; + if (conjs == 1) { + global_phase -= qc::PI; } + } + if (cs[2] > qc::PI_2) { + cs[2] -= 3.0 * qc::PI_2; + K1l = dot(K1l, IPZ); + K1r = dot(K1r, IPZ); + global_phase += qc::PI_2; + if (conjs == 1) { + global_phase -= qc::PI; + } + } + if (conjs == 1) { + cs[2] = qc::PI_2 - cs[2]; + K1l = dot(K1l, IPZ); + K2r = dot(IPZ, K2r); + global_phase += qc::PI_2; + } + if (cs[2] > qc::PI_4) { + cs[2] -= qc::PI_2; + K1l = dot(K1l, IPZ); + K1r = dot(K1r, IPZ); + global_phase -= qc::PI_2; + } + auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); + let is_close = | ap : f64, bp : f64, cp : f64 |->bool { + let[da, db, dc] = [ a - ap, b - bp, c - cp ]; + let tr = 4. * c64(da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), ); + match fidelity { + Some(fid) = > tr.trace_to_fid() >= fid, + // Set to false here to default to general specialization in the + // absence of a fidelity and provided specialization. + None = > false, + } + }; + + let closest_abc = closest_partial_swap(a, b, c); + let closest_ab_minus_c = closest_partial_swap(a, b, -c); + auto flipped_from_original = false; + let specialization = match _specialization{ + Some(specialization) = > specialization, + None =>{ + if is_close (0., 0., 0.){ + Specialization::IdEquiv} else if is_close (qc::PI_4, qc::PI_4, + qc::PI_4) || + is_close(qc::PI_4, qc::PI_4, -qc::PI_4){ + Specialization::SWAPEquiv} else if is_close (closest_abc, + closest_abc, + closest_abc){ + Specialization:: + PartialSWAPEquiv} else if is_close (closest_ab_minus_c, + closest_ab_minus_c, + -closest_ab_minus_c){ + Specialization::PartialSWAPFlipEquiv} else if is_close (a, 0., + 0.){ + Specialization::ControlledEquiv} else if is_close (qc::PI_4, + qc::PI_4, + c){ + Specialization:: + MirrorControlledEquiv} else if is_close ((a + b) / 2., + (a + b) / 2., c){ + Specialization::fSimaabEquiv} else if is_close (a, + (b + c) / 2., + (b + c) / 2.){ + Specialization::fSimabbEquiv} else if is_close (a, + (b - c) / 2., + (c - b) / 2.){ + Specialization::fSimabmbEquiv} else { + Specialization::General}}}; + let general = TwoQubitWeylDecomposition{ + a, + b, + c, + global_phase, + K1l, + K1r, + K2l, + K2r, + specialization : Specialization::General, + default_euler_basis, + requested_fidelity : fidelity, + calculated_fidelity : -1.0, + unitary_matrix, + }; + auto specialized + : TwoQubitWeylDecomposition = match specialization{ + // :math:`U \sim U_d(0,0,0) \sim Id` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::IdEquiv = > TwoQubitWeylDecomposition{ + specialization, + a : 0., + b : 0., + c : 0., + K1l : general.K1l.dot(&general.K2l), + K1r : general.K1r.dot(&general.K2r), + K2l : Array2::eye(2), + K2r : Array2::eye(2), + ..general + }, + // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, + // -\pi/4) \sim \text{SWAP}` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + Specialization::SWAPEquiv =>{ + if c > 0. {TwoQubitWeylDecomposition{ + specialization, + a : qc::PI_4, + b : qc::PI_4, + c : qc::PI_4, + K1l : general.K1l.dot(&general.K2r), + K1r : general.K1r.dot(&general.K2l), + K2l : Array2::eye(2), + K2r : Array2::eye(2), + ..general + }} else {flipped_from_original = true; + TwoQubitWeylDecomposition { + specialization, + a : qc::PI_4, + b : qc::PI_4, + c : qc::PI_4, + global_phase : global_phase + qc::PI_2, + K1l : general.K1l.dot(&ipz).dot(&general.K2r), + K1r : general.K1r.dot(&ipz).dot(&general.K2l), + K2l : Array2::eye(2), + K2r : Array2::eye(2), + ..general + } } } // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim @@ -436,7 +592,7 @@ struct GateDecompositionPattern final // :math:`K2_l = Id`. Specialization::PartialSWAPEquiv => { let closest = closest_partial_swap(a, b, c); - let mut k2l_dag = general.K2l.t().to_owned(); + auto k2l_dag = general.K2l.t().to_owned(); k2l_dag.view_mut().mapv_inplace(| x | x.conj()); TwoQubitWeylDecomposition { specialization, a : closest, @@ -460,7 +616,7 @@ struct GateDecompositionPattern final // :math:`K2_l = Id` Specialization::PartialSWAPFlipEquiv => { let closest = closest_partial_swap(a, b, -c); - let mut k2l_dag = general.K2l.t().to_owned(); + auto k2l_dag = general.K2l.t().to_owned(); k2l_dag.mapv_inplace(| x | x.conj()); TwoQubitWeylDecomposition { specialization, @@ -510,8 +666,8 @@ struct GateDecompositionPattern final angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); TwoQubitWeylDecomposition { specialization, - a : PI4, - b : PI4, + a : qc::PI_4, + b : qc::PI_4, c, global_phase : global_phase + k2lphase + k2rphase, K1l : general.K1l.dot(&rz_matrix(k2rphi)), @@ -583,11 +739,11 @@ struct GateDecompositionPattern final // This gate binds all 6 possible parameters, so there is no need to make the // single-qubit pre-/post-gates canonical. Specialization::General = > general, -}; +} let tr = if flipped_from_original { let[da, db, dc] = [ - PI2 - a - specialized.a, + qc::PI_2 - a - specialized.a, b - specialized.b, -c - specialized.c, ]; @@ -610,7 +766,9 @@ if let } specialized.global_phase += tr.arg(); Ok(specialized) -}; +} // namespace mqt::ir::opt +} +; void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { qc::fp basis_fidelity = 1.0; @@ -642,7 +800,7 @@ void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { else {None}; if let Some(seq) = sequence { return Ok(seq); } - let mut target_1q_basis_list = EulerBasisSet::new (); + auto target_1q_basis_list = EulerBasisSet::new (); target_1q_basis_list.add_basis(self.euler_basis); let euler_decompositions : SmallVec<[Option; 8]> = @@ -652,8 +810,8 @@ void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { &target_1q_basis_list, 0, None, true, None, )}) .collect(); - let mut gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - let mut global_phase = target_decomposed.global_phase; + auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + auto global_phase = target_decomposed.global_phase; global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; if best_nbasis == 2 { global_phase += PI; } @@ -705,9 +863,9 @@ std::array, 4> traces(TwoQubitWeylDecomposition target) { 4. * std::complex( target.a.cos() * target.b.cos() * target.c.cos(), target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * c64((PI4 - target.a).cos() * + 4. * c64((qc::PI_4 - target.a).cos() * (self.basis_decomposer.b - target.b).cos() * target.c.cos(), - (PI4 - target.a).sin() * + (qc::PI_4 - target.a).sin() * (self.basis_decomposer.b - target.b).sin() * target.c.sin(), ), c64(4. * target.c.cos(), 0.), From 95d7cae5eaf2c0be3312ab8eccf067270c0b2d9d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 7 Oct 2025 17:07:31 +0200 Subject: [PATCH 005/100] snapshot --- .../Transforms/GateDecompositionPattern.cpp | 760 ++++++++++-------- 1 file changed, 409 insertions(+), 351 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 57d7c52b7..19996b59a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -266,6 +266,29 @@ struct GateDecompositionPattern final throw std::logic_error{"Unknown MagicBasisTransform direction!"}; } + static qc::fp trace_to_fid(const qfp& x) { + auto x_abs = std::abs(x); + return (4.0 + x_abs * x_abs) / 20.0; + } + + static qc::fp closest_partial_swap(qc::fp a, qc::fp b, qc::fp c) { + auto m = (a + b + c) / 3.; + auto [am, bm, cm] = std::array{a - m, b - m, c - m}; + auto [ab, bc, ca] = std::array{a - b, b - c, c - a}; + return m + am * bm * cm * (6. + ab * ab + bc * bc + ca * ca) / 18.; + } + + static matrix2x2 rx_matrix(qc::fp theta) { + auto half_theta = theta / 2.; + auto cos = qfp(std::cos(half_theta), 0.); + auto isin = qfp(0., -std::sin(half_theta)); + return {cos, isin, isin, cos}; + } + + static std::array params_xyx_inner(const matrix2x2& matrix) { + + } + static constexpr std::complex C_ZERO{0., 0.}; static constexpr std::complex C_ONE{1., 0.}; static constexpr std::complex C_M_ONE{-1., 0.}; @@ -285,7 +308,7 @@ struct GateDecompositionPattern final // EulerBasis default_euler_basis; // TODO: simply use ZYZ? std::optional requested_fidelity; qc::fp calculated_fidelity; - dd::TwoQubitGateMatrix unitary_matrix; + matrix4x4 unitary_matrix; static TwoQubitWeylDecomposition new_inner(matrix4x4 unitary_matrix, @@ -474,347 +497,382 @@ struct GateDecompositionPattern final global_phase -= qc::PI_2; } auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); - let is_close = | ap : f64, bp : f64, cp : f64 |->bool { - let[da, db, dc] = [ a - ap, b - bp, c - cp ]; - let tr = 4. * c64(da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), ); - match fidelity { - Some(fid) = > tr.trace_to_fid() >= fid, - // Set to false here to default to general specialization in the - // absence of a fidelity and provided specialization. - None = > false, + auto is_close = [&](qc::fp ap, qc::fp bp, qc::fp cp) -> bool { + auto da = a - ap; + auto db = b - bp; + auto dc = c - cp; + auto tr = 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + if (fidelity) { + return trace_to_fid(tr) >= *fidelity; } + return false; }; - let closest_abc = closest_partial_swap(a, b, c); - let closest_ab_minus_c = closest_partial_swap(a, b, -c); + auto closest_abc = closest_partial_swap(a, b, c); + auto closest_ab_minus_c = closest_partial_swap(a, b, -c); auto flipped_from_original = false; - let specialization = match _specialization{ - Some(specialization) = > specialization, - None =>{ - if is_close (0., 0., 0.){ - Specialization::IdEquiv} else if is_close (qc::PI_4, qc::PI_4, - qc::PI_4) || - is_close(qc::PI_4, qc::PI_4, -qc::PI_4){ - Specialization::SWAPEquiv} else if is_close (closest_abc, - closest_abc, - closest_abc){ - Specialization:: - PartialSWAPEquiv} else if is_close (closest_ab_minus_c, - closest_ab_minus_c, - -closest_ab_minus_c){ - Specialization::PartialSWAPFlipEquiv} else if is_close (a, 0., - 0.){ - Specialization::ControlledEquiv} else if is_close (qc::PI_4, - qc::PI_4, - c){ - Specialization:: - MirrorControlledEquiv} else if is_close ((a + b) / 2., - (a + b) / 2., c){ - Specialization::fSimaabEquiv} else if is_close (a, - (b + c) / 2., - (b + c) / 2.){ - Specialization::fSimabbEquiv} else if is_close (a, - (b - c) / 2., - (c - b) / 2.){ - Specialization::fSimabmbEquiv} else { - Specialization::General}}}; - let general = TwoQubitWeylDecomposition{ - a, - b, - c, - global_phase, - K1l, - K1r, - K2l, - K2r, - specialization : Specialization::General, - default_euler_basis, - requested_fidelity : fidelity, - calculated_fidelity : -1.0, - unitary_matrix, + + auto get_default_specialzation = [&]() { + if (is_close(0., 0., 0.)) { + return Specialization::IdEquiv; + } else if (is_close(qc::PI_4, qc::PI_4, qc::PI_4) || + is_close(qc::PI_4, qc::PI_4, -qc::PI_4)) { + return Specialization::SWAPEquiv; + } else if (is_close(closest_abc, closest_abc, closest_abc)) { + return Specialization::PartialSWAPEquiv; + } else if (is_close(closest_ab_minus_c, closest_ab_minus_c, + -closest_ab_minus_c)) { + return Specialization::PartialSWAPFlipEquiv; + } else if (is_close(a, 0., 0.)) { + return Specialization::ControlledEquiv; + } else if (is_close(qc::PI_4, qc::PI_4, c)) { + return Specialization::MirrorControlledEquiv; + } else if (is_close((a + b) / 2., (a + b) / 2., c)) { + return Specialization::fSimaabEquiv; + } else if (is_close(a, (b + c) / 2., (b + c) / 2.)) { + return Specialization::fSimabbEquiv; + } else if (is_close(a, (b - c) / 2., (c - b) / 2.)) { + return Specialization::fSimabmbEquiv; + } else { + return Specialization::General; + } }; - auto specialized - : TwoQubitWeylDecomposition = match specialization{ - // :math:`U \sim U_d(0,0,0) \sim Id` - // - // This gate binds 0 parameters, we make it canonical by - // setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::IdEquiv = > TwoQubitWeylDecomposition{ - specialization, - a : 0., - b : 0., - c : 0., - K1l : general.K1l.dot(&general.K2l), - K1r : general.K1r.dot(&general.K2r), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general - }, - // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, - // -\pi/4) \sim \text{SWAP}` - // - // This gate binds 0 parameters, we make it canonical by - // setting - // :math:`K2_l = Id` , :math:`K2_r = Id`. - Specialization::SWAPEquiv =>{ - if c > 0. {TwoQubitWeylDecomposition{ - specialization, - a : qc::PI_4, - b : qc::PI_4, - c : qc::PI_4, - K1l : general.K1l.dot(&general.K2r), - K1r : general.K1r.dot(&general.K2l), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general - }} else {flipped_from_original = true; - TwoQubitWeylDecomposition { - specialization, - a : qc::PI_4, - b : qc::PI_4, - c : qc::PI_4, - global_phase : global_phase + qc::PI_2, - K1l : general.K1l.dot(&ipz).dot(&general.K2r), - K1r : general.K1r.dot(&ipz).dot(&general.K2l), - K2l : Array2::eye(2), - K2r : Array2::eye(2), - ..general - } - } - } - // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim - // \text{SWAP}^\alpha` - // - // This gate binds 3 parameters, we make it canonical by setting: - // - // :math:`K2_l = Id`. - Specialization::PartialSWAPEquiv => { - let closest = closest_partial_swap(a, b, c); - auto k2l_dag = general.K2l.t().to_owned(); - k2l_dag.view_mut().mapv_inplace(| x | x.conj()); - TwoQubitWeylDecomposition { - specialization, a : closest, - b : closest, - c : closest, - K1l : general.K1l.dot(&general.K2l), - K1r : general.K1r.dot(&general.K2l), - K2r : k2l_dag.dot(&general.K2r), - K2l : Array2::eye(2), + auto specialization = + _specialization.value_or(get_default_specialzation()); + TwoQubitWeylDecomposition general{ + a, + b, + c, + global_phase, + K1l, + K1r, + K2l, + K2r, + Specialization::General, + // default_euler_basis, + fidelity, + -1.0, + unitary_matrix, + }; + auto get_specialized_decomposition = [&]() { + // :math:`U \sim U_d(0,0,0) \sim Id` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + if (specialization == Specialization::IdEquiv) { + return TwoQubitWeylDecomposition{ + 0., + 0., + 0., + general.global_phase, + dot(general.K1l, general.K2l), + identityGate, + dot(general.K1r, general.K2r), + identityGate, + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } + // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, + // -\pi/4) \sim \text{SWAP}` + // + // This gate binds 0 parameters, we make it canonical by + // setting + // :math:`K2_l = Id` , :math:`K2_r = Id`. + if (specialization == Specialization::SWAPEquiv) { + if (c > 0.) { + return TwoQubitWeylDecomposition{ + qc::PI_4, + qc::PI_4, + qc::PI_4, + general.global_phase, + dot(general.K1l, general.K2r), + identityGate, + dot(general.K1r, general.K2l), + identityGate, + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } else { + flipped_from_original = true; + return TwoQubitWeylDecomposition{ + qc::PI_4, + qc::PI_4, + qc::PI_4, + global_phase + qc::PI_2, + dot(dot(general.K1l, IPZ), general.K2r), + identityGate, + dot(dot(general.K1r, IPZ), general.K2l), + identityGate, + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim + // \text{SWAP}^\alpha` + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id`. + if (specialization == Specialization::PartialSWAPEquiv) { + auto closest = closest_partial_swap(a, b, c); + auto k2l_dag = transpose(general.K2l); + llvm::transform(k2l_dag, k2l_dag.begin(), + [](auto&& x) { return std::conj(x); }); + + return TwoQubitWeylDecomposition{ + closest, + closest, + closest, + general.global_phase, + dot(general.K1l, general.K2l), + identityGate, + dot(general.K1r, general.K2l), + dot(k2l_dag, general.K2r), + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } + // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim + // \text{SWAP}^\alpha` + // + // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv + // similar to how :math:`x = (\pm \sqrt(x))^2`) + // + // This gate binds 3 parameters, we make it canonical by setting: + // + // :math:`K2_l = Id` + if (specialization == Specialization::PartialSWAPFlipEquiv) { + auto closest = closest_partial_swap(a, b, -c); + auto k2l_dag = transpose(general.K2l); + llvm::transform(k2l_dag, k2l_dag.begin(), + [](auto&& x) { return std::conj(x); }); + + return TwoQubitWeylDecomposition{ + closest, + closest, + -closest, + general.global_phase, + dot(general.K1l, general.K2l), + identityGate, + dot(dot(dot(general.K1r, IPZ), general.K2l), IPZ), + dot(dot(dot(IPZ, k2l_dag), IPZ), general.K2r), + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } + // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , + // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . + if (specialization == Specialization::ControlledEquiv) { + auto euler_basis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l, euler_basis); + auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r, euler_basis); + return TwoQubitWeylDecomposition{ + a, + 0., + 0., + global_phase + k2lphase + k2rphase, + dot(general.K1l, rx_matrix(k2lphi)), + ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + dot(general.K1r, rx_matrix(k2rphi)), + ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), + // default_euler_basis : euler_basis, + specialization, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; + } + // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot + // \text{Ctrl-U}` + // + // This gate binds 4 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = + // Ry(\theta_r)\cdot Rz(\lambda_r)` + Specialization::MirrorControlledEquiv => { + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + let[k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, + a : qc::PI_4, + b : qc::PI_4, + c, + global_phase : global_phase + k2lphase + k2rphase, + K1l : general.K1l.dot(&rz_matrix(k2rphi)), + K1r : general.K1r.dot(&rz_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r : ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), + ..general + } + } + // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. + Specialization::fSimaabEquiv => { + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); + TwoQubitWeylDecomposition { + specialization, a : (a + b) / 2., b : (a + b) / 2., c, + global_phase : global_phase + k2lphase, + K1r : general.K1r.dot(&rz_matrix(k2lphi)), + K1l : general.K1l.dot(&rz_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), + K2r : rz_matrix(-k2lphi).dot(&general.K2r), ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabbEquiv => { + let euler_basis = EulerBasis::XYX; + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, a, b : (b + c) / 2., c : (b + c) / 2., + global_phase : global_phase + k2lphase, + K1r : general.K1r.dot(&rx_matrix(k2lphi)), + K1l : general.K1l.dot(&rx_matrix(k2lphi)), + K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r : rx_matrix(-k2lphi).dot(&general.K2r), + default_euler_basis : euler_basis, ..general + } + } + // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` + // + // This gate binds 5 parameters, we make it canonical by setting: + // + // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` + Specialization::fSimabmbEquiv => { + let euler_basis = EulerBasis::XYX; + let[k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l.view(), euler_basis); + TwoQubitWeylDecomposition { + specialization, a, b : (b - c) / 2., + c + : -((b - c) / 2.), + global_phase : global_phase + k2lphase, + K1l : general.K1l.dot(&rx_matrix(k2lphi)), + K1r : general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), + K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + K2r : ipz.dot(&rx_matrix(-k2lphi)) + .dot(&ipz) + .dot(&general.K2r), + default_euler_basis : euler_basis, ..general - } - } - // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim - // \text{SWAP}^\alpha` - // - // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv - // similar to how :math:`x = (\pm \sqrt(x))^2`) - // - // This gate binds 3 parameters, we make it canonical by setting: - // - // :math:`K2_l = Id` - Specialization::PartialSWAPFlipEquiv => { - let closest = closest_partial_swap(a, b, -c); - auto k2l_dag = general.K2l.t().to_owned(); - k2l_dag.mapv_inplace(| x | x.conj()); - TwoQubitWeylDecomposition { - specialization, - a : closest, - b : closest, - c : -closest, - K1l : general.K1l.dot(&general.K2l), - K1r : general.K1r.dot(&ipz).dot(&general.K2l).dot(&ipz), - K2r : ipz.dot(&k2l_dag).dot(&ipz).dot(&general.K2r), - K2l : Array2::eye(2), - ..general - } - } - // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` - // - // This gate binds 4 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , - // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . - Specialization::ControlledEquiv => { - let euler_basis = EulerBasis::XYX; - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - let[k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, a, b : 0., c : 0., - global_phase : global_phase + k2lphase + k2rphase, - K1l : general.K1l.dot(&rx_matrix(k2lphi)), - K1r : general.K1r.dot(&rx_matrix(k2rphi)), - K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r : ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), - default_euler_basis : euler_basis, ..general - } - } - // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot - // \text{Ctrl-U}` - // - // This gate binds 4 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = - // Ry(\theta_r)\cdot Rz(\lambda_r)` - Specialization::MirrorControlledEquiv => { - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - let[k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { - specialization, - a : qc::PI_4, - b : qc::PI_4, - c, - global_phase : global_phase + k2lphase + k2rphase, - K1l : general.K1l.dot(&rz_matrix(k2rphi)), - K1r : general.K1r.dot(&rz_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r : ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), - ..general - } - } - // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. - Specialization::fSimaabEquiv => { - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { - specialization, a : (a + b) / 2., b : (a + b) / 2., c, - global_phase : global_phase + k2lphase, - K1r : general.K1r.dot(&rz_matrix(k2lphi)), - K1l : general.K1l.dot(&rz_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r : rz_matrix(-k2lphi).dot(&general.K2r), ..general - } - } - // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabbEquiv => { - let euler_basis = EulerBasis::XYX; - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, a, b : (b + c) / 2., c : (b + c) / 2., - global_phase : global_phase + k2lphase, - K1r : general.K1r.dot(&rx_matrix(k2lphi)), - K1l : general.K1l.dot(&rx_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r : rx_matrix(-k2lphi).dot(&general.K2r), - default_euler_basis : euler_basis, ..general - } - } - // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` - // - // This gate binds 5 parameters, we make it canonical by setting: - // - // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabmbEquiv => { - let euler_basis = EulerBasis::XYX; - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, a, b : (b - c) / 2., - c : -((b - c) / 2.), - global_phase : global_phase + k2lphase, - K1l : general.K1l.dot(&rx_matrix(k2lphi)), - K1r : general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), - K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r : ipz.dot(&rx_matrix(-k2lphi)).dot(&ipz).dot(&general.K2r), - default_euler_basis : euler_basis, - ..general - } - } - // U has no special symmetry. - // - // This gate binds all 6 possible parameters, so there is no need to make the - // single-qubit pre-/post-gates canonical. - Specialization::General = > general, -} - -let tr = if flipped_from_original { - let[da, db, dc] = [ - qc::PI_2 - a - specialized.a, - b - specialized.b, - -c - specialized.c, - ]; - 4. * c64(da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) -} -else { - let[da, db, dc] = [ a - specialized.a, b - specialized.b, c - specialized.c ]; - 4. * c64(da.cos() * db.cos() * dc.cos(), da.sin() * db.sin() * dc.sin(), ) -}; -specialized.calculated_fidelity = tr.trace_to_fid(); -if let - Some(fid) = specialized.requested_fidelity { - if specialized - .calculated_fidelity + 1.0e-13 < fid { - return Err(QiskitError::new_err(format !( - "Specialization: {:?} calculated fidelity: {} is worse than " - "requested fidelity: {}", - specialized.specialization, specialized.calculated_fidelity, fid))); + } + } + // U has no special symmetry. + // + // This gate binds all 6 possible parameters, so there is no need to + // make the single-qubit pre-/post-gates canonical. + Specialization::General = > general, + }; + TwoQubitWeylDecomposition specialized = match specialization + + let tr = if flipped_from_original { + let[da, db, dc] = [ + qc::PI_2 - a - specialized.a, + b - specialized.b, + -c - specialized.c, + ]; + 4. * c64(da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), ) } - } -specialized.global_phase += tr.arg(); -Ok(specialized) -} // namespace mqt::ir::opt -} -; - -void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { - qc::fp basis_fidelity = 1.0; - let target_decomposed = TwoQubitWeylDecomposition::new_inner( - unitary, Some(DEFAULT_FIDELITY), None) - ? ; - let traces = self.traces(&target_decomposed); - let best_nbasis = _num_basis_uses.unwrap_or_else( - || - {traces.into_iter() - .enumerate() - .map(| (idx, trace) | - (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) - .min_by(| (_idx1, fid1), - (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) - .unwrap() .0 as u8}); - let decomposition = match best_nbasis{ - 0 = > decomp0_inner(&target_decomposed), - 1 = > self.decomp1_inner(&target_decomposed), - 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), - 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), - _ = > unreachable !("Invalid basis to use"), + else { + let[da, db, dc] = + [ a - specialized.a, b - specialized.b, c - specialized.c ]; + 4. * c64(da.cos() * db.cos() * dc.cos(), + da.sin() * db.sin() * dc.sin(), ) + }; + specialized.calculated_fidelity = tr.trace_to_fid(); + if let + Some(fid) = specialized.requested_fidelity { + if specialized + .calculated_fidelity + 1.0e-13 < fid { + return Err(QiskitError::new_err(format !( + "Specialization: {:?} calculated fidelity: {} is worse than " + "requested fidelity: {}", + specialized.specialization, specialized.calculated_fidelity, + fid))); + } + } + specialized.global_phase += tr.arg(); + Ok(specialized) + } // namespace mqt::ir::opt }; - let pulse_optimize = self.pulse_optimize.unwrap_or(true); - let sequence = if pulse_optimize { - self.pulse_optimal_chooser(best_nbasis, &decomposition, &target_decomposed) - ? - } - else {None}; - if let - Some(seq) = sequence { return Ok(seq); } - auto target_1q_basis_list = EulerBasisSet::new (); - target_1q_basis_list.add_basis(self.euler_basis); - let euler_decompositions - : SmallVec<[Option; 8]> = - decomposition.iter() - .map(| decomp | - {unitary_to_gate_sequence_inner(decomp.view(), - &target_1q_basis_list, 0, - None, true, None, )}) - .collect(); - auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - auto global_phase = target_decomposed.global_phase; - global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; - if best_nbasis - == 2 { global_phase += PI; } + + void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { + qc::fp basis_fidelity = 1.0; + let target_decomposed = TwoQubitWeylDecomposition::new_inner( + unitary, Some(DEFAULT_FIDELITY), None) + ? ; + let traces = self.traces(&target_decomposed); + let best_nbasis = _num_basis_uses.unwrap_or_else( + || + {traces.into_iter() + .enumerate() + .map(| (idx, trace) | + (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) + .min_by(| (_idx1, fid1), + (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) + .unwrap() .0 as u8}); + let decomposition = match best_nbasis{ + 0 = > decomp0_inner(&target_decomposed), + 1 = > self.decomp1_inner(&target_decomposed), + 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), + 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), + _ = > unreachable !("Invalid basis to use"), + }; + let pulse_optimize = self.pulse_optimize.unwrap_or(true); + let sequence = if pulse_optimize { + self.pulse_optimal_chooser(best_nbasis, &decomposition, + &target_decomposed) + ? + } + else {None}; + if let + Some(seq) = sequence { return Ok(seq); } + auto target_1q_basis_list = EulerBasisSet::new (); + target_1q_basis_list.add_basis(self.euler_basis); + let euler_decompositions + : SmallVec<[Option; 8]> = + decomposition.iter() + .map(| decomp | + {unitary_to_gate_sequence_inner(decomp.view(), + &target_1q_basis_list, 0, + None, true, None, )}) + .collect(); + auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + auto global_phase = target_decomposed.global_phase; + global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; + if best_nbasis + == 2 { global_phase += PI; } for i in 0..best_nbasis as usize { if let @@ -856,24 +914,24 @@ void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { gates, global_phase, }) -} + } -std::array, 4> traces(TwoQubitWeylDecomposition target) { - return { - 4. * std::complex( - target.a.cos() * target.b.cos() * target.c.cos(), - target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * c64((qc::PI_4 - target.a).cos() * - (self.basis_decomposer.b - target.b).cos() * target.c.cos(), - (qc::PI_4 - target.a).sin() * - (self.basis_decomposer.b - target.b).sin() * - target.c.sin(), ), - c64(4. * target.c.cos(), 0.), - c64(4., 0.), - }; -} -} -; + std::array, 4> traces(TwoQubitWeylDecomposition target) { + return { + 4. * std::complex( + target.a.cos() * target.b.cos() * target.c.cos(), + target.a.sin() * target.b.sin() * target.c.sin(), ), + 4. * + c64((qc::PI_4 - target.a).cos() * + (self.basis_decomposer.b - target.b).cos() * target.c.cos(), + (qc::PI_4 - target.a).sin() * + (self.basis_decomposer.b - target.b).sin() * + target.c.sin(), ), + c64(4. * target.c.cos(), 0.), + c64(4., 0.), + }; + } +}; /** * @brief Populates the given pattern set with patterns for gate From 512fbe8f59e9c75171932ac981ee984b397bf28a Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 7 Oct 2025 21:34:56 +0200 Subject: [PATCH 006/100] snapshot --- .../Transforms/GateDecompositionPattern.cpp | 411 +++++++++++------- 1 file changed, 252 insertions(+), 159 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 19996b59a..8f4b20412 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -285,10 +285,28 @@ struct GateDecompositionPattern final return {cos, isin, isin, cos}; } - static std::array params_xyx_inner(const matrix2x2& matrix) { + static matrix2x2 ry_matrix(qc::fp theta) { + auto half_theta = theta / 2.; + auto cos = qfp(std::cos(half_theta), 0.); + auto sin = qfp(std::sin(half_theta), 0.); + return {cos, -sin, sin, cos}; + } + static matrix2x2 rz_matrix(qc::fp theta) { + auto ilam2 = qfp(0., 0.5 * theta); + return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; + } + + static std::array angles_from_unitary(const matrix2x2& matrix, + EulerBasis basis) { + if (basis == EulerBasis::XYX) { + return params_xyx_inner(matrix); + } + throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"}; } + static std::array params_xyx_inner(const matrix2x2& matrix) {} + static constexpr std::complex C_ZERO{0., 0.}; static constexpr std::complex C_ONE{1., 0.}; static constexpr std::complex C_M_ONE{-1., 0.}; @@ -305,7 +323,7 @@ struct GateDecompositionPattern final std::array, 4> K1r; std::array, 4> K2r; Specialization specialization; - // EulerBasis default_euler_basis; // TODO: simply use ZYZ? + EulerBasis default_euler_basis; std::optional requested_fidelity; qc::fp calculated_fidelity; matrix4x4 unitary_matrix; @@ -396,7 +414,7 @@ struct GateDecompositionPattern final [](auto&& x) { return -std::arg(x) / 2.0; }); d_real[3] = -d_real[0] - d_real[1] - d_real[2]; std::array cs; - for (int i = 0; i < cs.size(); ++i) { + for (std::size_t i = 0; i < cs.size(); ++i) { assert(i < d_real.size()); cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::PI_2); } @@ -417,7 +435,7 @@ struct GateDecompositionPattern final // swap columns of p according to order constexpr auto P_ROW_LENGTH = 4; auto p_orig = p; - for (int i = 0; i < order.size(); ++i) { + for (std::size_t i = 0; i < order.size(); ++i) { for (std::size_t row = 0; row < P_ROW_LENGTH; ++row) { std::swap(p[row * 3 + i], p_orig[row * 3 + order[i]]); } @@ -550,7 +568,7 @@ struct GateDecompositionPattern final K2l, K2r, Specialization::General, - // default_euler_basis, + default_euler_basis, fidelity, -1.0, unitary_matrix, @@ -572,6 +590,7 @@ struct GateDecompositionPattern final dot(general.K1r, general.K2r), identityGate, specialization, + general.default_euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -595,6 +614,7 @@ struct GateDecompositionPattern final dot(general.K1r, general.K2l), identityGate, specialization, + general.default_euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -611,6 +631,7 @@ struct GateDecompositionPattern final dot(dot(general.K1r, IPZ), general.K2l), identityGate, specialization, + general.default_euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -639,6 +660,7 @@ struct GateDecompositionPattern final dot(general.K1r, general.K2l), dot(k2l_dag, general.K2r), specialization, + general.default_euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -669,6 +691,7 @@ struct GateDecompositionPattern final dot(dot(dot(general.K1r, IPZ), general.K2l), IPZ), dot(dot(dot(IPZ, k2l_dag), IPZ), general.K2r), specialization, + general.default_euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -692,11 +715,11 @@ struct GateDecompositionPattern final 0., global_phase + k2lphase + k2rphase, dot(general.K1l, rx_matrix(k2lphi)), - ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), + dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), dot(general.K1r, rx_matrix(k2rphi)), - ry_matrix(k2rtheta).dot(&rx_matrix(k2rlambda)), - // default_euler_basis : euler_basis, + dot(ry_matrix(k2rtheta), rx_matrix(k2rlambda)), specialization, + euler_basis, general.requested_fidelity, general.calculated_fidelity, general.unitary_matrix, @@ -709,170 +732,239 @@ struct GateDecompositionPattern final // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = // Ry(\theta_r)\cdot Rz(\lambda_r)` - Specialization::MirrorControlledEquiv => { - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - let[k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { - specialization, - a : qc::PI_4, - b : qc::PI_4, - c, - global_phase : global_phase + k2lphase + k2rphase, - K1l : general.K1l.dot(&rz_matrix(k2rphi)), - K1r : general.K1r.dot(&rz_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r : ry_matrix(k2rtheta).dot(&rz_matrix(k2rlambda)), - ..general - } + if (specialization == Specialization::MirrorControlledEquiv) { + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l, EulerBasis::ZYZ); + auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = + angles_from_unitary(general.K2r, EulerBasis::ZYZ); + TwoQubitWeylDecomposition{ + qc::PI_4, + qc::PI_4, + c, + global_phase + k2lphase + k2rphase, + dot(general.K1l, rz_matrix(k2rphi)), + dot(ry_matrix(k2ltheta), rz_matrix(k2llambda)), + dot(general.K1r, rz_matrix(k2lphi)), + dot(ry_matrix(k2rtheta), rz_matrix(k2rlambda)), + specialization, + general.default_euler_basis, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; } // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. - Specialization::fSimaabEquiv => { - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), EulerBasis::ZYZ); - TwoQubitWeylDecomposition { - specialization, a : (a + b) / 2., b : (a + b) / 2., c, - global_phase : global_phase + k2lphase, - K1r : general.K1r.dot(&rz_matrix(k2lphi)), - K1l : general.K1l.dot(&rz_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rz_matrix(k2llambda)), - K2r : rz_matrix(-k2lphi).dot(&general.K2r), ..general - } + if (specialization == Specialization::fSimaabEquiv) { + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l, EulerBasis::ZYZ); + return TwoQubitWeylDecomposition{ + (a + b) / 2., + (a + b) / 2., + c, + global_phase + k2lphase, + dot(general.K1l, rz_matrix(k2lphi)), + dot(ry_matrix(k2ltheta), rz_matrix(k2llambda)), + dot(general.K1r, rz_matrix(k2lphi)), + dot(rz_matrix(-k2lphi), general.K2r), + specialization, + general.default_euler_basis, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabbEquiv => { - let euler_basis = EulerBasis::XYX; - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, a, b : (b + c) / 2., c : (b + c) / 2., - global_phase : global_phase + k2lphase, - K1r : general.K1r.dot(&rx_matrix(k2lphi)), - K1l : general.K1l.dot(&rx_matrix(k2lphi)), - K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r : rx_matrix(-k2lphi).dot(&general.K2r), - default_euler_basis : euler_basis, ..general - } + if (specialization == Specialization::fSimabbEquiv) { + auto euler_basis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l, euler_basis); + return TwoQubitWeylDecomposition{ + a, + (b + c) / 2., + (b + c) / 2., + global_phase + k2lphase, + dot(general.K1l, rx_matrix(k2lphi)), + dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), + dot(general.K1r, rx_matrix(k2lphi)), + dot(rx_matrix(-k2lphi), general.K2r), + specialization, + euler_basis, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - Specialization::fSimabmbEquiv => { - let euler_basis = EulerBasis::XYX; - let[k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l.view(), euler_basis); - TwoQubitWeylDecomposition { - specialization, a, b : (b - c) / 2., - c - : -((b - c) / 2.), - global_phase : global_phase + k2lphase, - K1l : general.K1l.dot(&rx_matrix(k2lphi)), - K1r : general.K1r.dot(&ipz).dot(&rx_matrix(k2lphi)).dot(&ipz), - K2l : ry_matrix(k2ltheta).dot(&rx_matrix(k2llambda)), - K2r : ipz.dot(&rx_matrix(-k2lphi)) - .dot(&ipz) - .dot(&general.K2r), - default_euler_basis : euler_basis, - ..general - } + if (specialization == Specialization::fSimabmbEquiv) { + auto euler_basis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + angles_from_unitary(general.K2l, euler_basis); + return TwoQubitWeylDecomposition{ + a, + (b - c) / 2., + -((b - c) / 2.), + global_phase + k2lphase, + dot(general.K1l, rx_matrix(k2lphi)), + dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), + dot(dot(dot(general.K1r, IPZ), rx_matrix(k2lphi)), IPZ), + dot(dot(dot(IPZ, rx_matrix(-k2lphi)), IPZ), general.K2r), + specialization, + euler_basis, + general.requested_fidelity, + general.calculated_fidelity, + general.unitary_matrix, + }; } // U has no special symmetry. // // This gate binds all 6 possible parameters, so there is no need to // make the single-qubit pre-/post-gates canonical. - Specialization::General = > general, + if (specialization == Specialization::General) { + return general; + } }; - TwoQubitWeylDecomposition specialized = match specialization - - let tr = if flipped_from_original { - let[da, db, dc] = [ - qc::PI_2 - a - specialized.a, - b - specialized.b, - -c - specialized.c, - ]; - 4. * c64(da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), ) - } - else { - let[da, db, dc] = - [ a - specialized.a, b - specialized.b, c - specialized.c ]; - 4. * c64(da.cos() * db.cos() * dc.cos(), - da.sin() * db.sin() * dc.sin(), ) + + TwoQubitWeylDecomposition specialized = get_specialized_decomposition(); + + auto get_tr = [&]() { + if (flipped_from_original) { + auto [da, db, dc] = std::array{ + qc::PI_2 - a - specialized.a, + b - specialized.b, + -c - specialized.c, + }; + return 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + } else { + auto [da, db, dc] = std::array{a - specialized.a, b - specialized.b, + c - specialized.c}; + return 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + } }; - specialized.calculated_fidelity = tr.trace_to_fid(); - if let - Some(fid) = specialized.requested_fidelity { - if specialized - .calculated_fidelity + 1.0e-13 < fid { - return Err(QiskitError::new_err(format !( - "Specialization: {:?} calculated fidelity: {} is worse than " - "requested fidelity: {}", - specialized.specialization, specialized.calculated_fidelity, - fid))); - } + auto tr = get_tr(); + specialized.calculated_fidelity = trace_to_fid(tr); + if (specialized.requested_fidelity) { + if (specialized.calculated_fidelity + 1.0e-13 < + *specialized.requested_fidelity) { + throw std::runtime_error{ + "Specialization: {:?} calculated fidelity: {} is worse than " + "requested fidelity: {}", + }; } - specialized.global_phase += tr.arg(); - Ok(specialized) + } + specialized.global_phase += std::arg(tr); + return specialized; } // namespace mqt::ir::opt }; - void twoQubitDecompose(dd::TwoQubitGateMatrix unitaryMatrix) { - qc::fp basis_fidelity = 1.0; - let target_decomposed = TwoQubitWeylDecomposition::new_inner( - unitary, Some(DEFAULT_FIDELITY), None) - ? ; - let traces = self.traces(&target_decomposed); - let best_nbasis = _num_basis_uses.unwrap_or_else( - || - {traces.into_iter() - .enumerate() - .map(| (idx, trace) | - (idx, trace.trace_to_fid() * basis_fidelity.powi(idx as i32))) - .min_by(| (_idx1, fid1), - (_idx2, fid2) | fid2.partial_cmp(fid1).unwrap()) - .unwrap() .0 as u8}); - let decomposition = match best_nbasis{ - 0 = > decomp0_inner(&target_decomposed), - 1 = > self.decomp1_inner(&target_decomposed), - 2 = > self.decomp2_supercontrolled_inner(&target_decomposed), - 3 = > self.decomp3_supercontrolled_inner(&target_decomposed), - _ = > unreachable !("Invalid basis to use"), - }; - let pulse_optimize = self.pulse_optimize.unwrap_or(true); - let sequence = if pulse_optimize { - self.pulse_optimal_chooser(best_nbasis, &decomposition, - &target_decomposed) - ? - } - else {None}; - if let - Some(seq) = sequence { return Ok(seq); } - auto target_1q_basis_list = EulerBasisSet::new (); - target_1q_basis_list.add_basis(self.euler_basis); - let euler_decompositions - : SmallVec<[Option; 8]> = - decomposition.iter() - .map(| decomp | - {unitary_to_gate_sequence_inner(decomp.view(), - &target_1q_basis_list, 0, - None, true, None, )}) - .collect(); - auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - auto global_phase = target_decomposed.global_phase; - global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; - if best_nbasis - == 2 { global_phase += PI; } + static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; + + struct TwoQubitBasisDecomposer { + std::string gate; + qc::fp basis_fidelity; + EulerBasis euler_basis; + std::optional pulse_optimize; + TwoQubitWeylDecomposition basis_decomposer; + bool super_controlled; + matrix2x2 u0l; + matrix2x2 u0r; + matrix2x2 u1l; + matrix2x2 u1ra; + matrix2x2 u1rb; + matrix2x2 u2la; + matrix2x2 u2lb; + matrix2x2 u2ra; + matrix2x2 u2rb; + matrix2x2 u3l; + matrix2x2 u3r; + matrix2x2 q0l; + matrix2x2 q0r; + matrix2x2 q1la; + matrix2x2 q1lb; + matrix2x2 q1ra; + matrix2x2 q1rb; + matrix2x2 q2l; + matrix2x2 q2r; + + void twoQubitDecompose(const matrix4x4& unitaryMatrix, + std::optional _basis_fidelity, + bool approximate, + std::optional _num_basis_uses) { + auto get_basis_fidelity = [&]() { + if (approximate) { + return _basis_fidelity.value_or(this->basis_fidelity); + } + return 1.0; + }; + qc::fp basis_fidelity = get_basis_fidelity(); + auto target_decomposed = TwoQubitWeylDecomposition::new_inner( + unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); + auto traces = this->traces(target_decomposed); + auto get_default_nbasis = [&]() { + auto minValue = std::numeric_limits::max(); + auto minIndex = -1; + for(std::size_t i = 0; i < traces.size(); ++i) { + auto value = trace_to_fid(traces[i]) * std::pow(basis_fidelity, i); + if (value < minValue) { + minIndex = i; + minValue = value; + } + } + return minIndex; + }; + auto best_nbasis = _num_basis_uses.value_or(get_default_nbasis()); + auto choose_decomposition = [&]() { + if (best_nbasis == 0) { + return decomp0_inner(&target_decomposed); + } + if (best_nbasis == 1) { + return decomp1_inner(&target_decomposed); + } + if (best_nbasis == 2) { + return decomp2_supercontrolled_inner(&target_decomposed); + } + if (best_nbasis == 3) { + return decomp3_supercontrolled_inner(&target_decomposed); + } + throw std::logic_error{"Invalid basis to use"}; + }; + auto pulse_optimize = this->pulse_optimize.unwrap_or(true); + auto sequence = if (pulse_optimize) { + self.pulse_optimal_chooser(best_nbasis, &decomposition, + &target_decomposed) + ? + } + else {None}; + if let + Some(seq) = sequence { return Ok(seq); } + auto target_1q_basis_list = EulerBasisSet::new (); + target_1q_basis_list.add_basis(self.euler_basis); + let euler_decompositions + : SmallVec<[Option; 8]> = + decomposition.iter() + .map(| decomp | + {unitary_to_gate_sequence_inner( + decomp.view(), &target_1q_basis_list, 0, None, + true, None, )}) + .collect(); + auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + auto global_phase = target_decomposed.global_phase; + global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; + if best_nbasis + == 2 { global_phase += PI; } for i in 0..best_nbasis as usize { if let @@ -914,23 +1006,24 @@ struct GateDecompositionPattern final gates, global_phase, }) - } + } - std::array, 4> traces(TwoQubitWeylDecomposition target) { - return { - 4. * std::complex( - target.a.cos() * target.b.cos() * target.c.cos(), - target.a.sin() * target.b.sin() * target.c.sin(), ), - 4. * - c64((qc::PI_4 - target.a).cos() * - (self.basis_decomposer.b - target.b).cos() * target.c.cos(), - (qc::PI_4 - target.a).sin() * - (self.basis_decomposer.b - target.b).sin() * - target.c.sin(), ), - c64(4. * target.c.cos(), 0.), - c64(4., 0.), - }; - } + std::array traces(TwoQubitWeylDecomposition target) { + return { + 4. * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), + std::sin(target.a) * std::sin(target.b) * + std::sin(target.c), ), + 4. * qfp(std::cos(qc::PI_4 - target.a) * + std::cos(self.basis_decomposer.b - target.b) * + std::cos(target.c), + std::sin(qc::PI_4 - target.a) * + std::sin(self.basis_decomposer.b - target.b) * + std::sin(target.c)), + qfp(4. * std::cos(target.c), 0.), + qfp(4., 0.), + }; + } + }; }; /** From ea302ddc804c8574da2dbcd506b34883e6dc5ecc Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 9 Oct 2025 12:31:54 +0200 Subject: [PATCH 007/100] minor additions --- .../Transforms/GateDecompositionPattern.cpp | 401 +++++++++++++++++- 1 file changed, 387 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 8f4b20412..fb05f6586 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -564,8 +564,8 @@ struct GateDecompositionPattern final c, global_phase, K1l, - K1r, K2l, + K1r, K2r, Specialization::General, default_euler_basis, @@ -867,7 +867,7 @@ struct GateDecompositionPattern final } specialized.global_phase += std::arg(tr); return specialized; - } // namespace mqt::ir::opt + } }; static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; @@ -916,7 +916,7 @@ struct GateDecompositionPattern final auto get_default_nbasis = [&]() { auto minValue = std::numeric_limits::max(); auto minIndex = -1; - for(std::size_t i = 0; i < traces.size(); ++i) { + for (std::size_t i = 0; i < traces.size(); ++i) { auto value = trace_to_fid(traces[i]) * std::pow(basis_fidelity, i); if (value < minValue) { minIndex = i; @@ -928,20 +928,20 @@ struct GateDecompositionPattern final auto best_nbasis = _num_basis_uses.value_or(get_default_nbasis()); auto choose_decomposition = [&]() { if (best_nbasis == 0) { - return decomp0_inner(&target_decomposed); + return decomp0_inner(target_decomposed); } if (best_nbasis == 1) { - return decomp1_inner(&target_decomposed); + return decomp1_inner(target_decomposed); } if (best_nbasis == 2) { - return decomp2_supercontrolled_inner(&target_decomposed); + return decomp2_supercontrolled_inner(target_decomposed); } if (best_nbasis == 3) { - return decomp3_supercontrolled_inner(&target_decomposed); + return decomp3_supercontrolled_inner(target_decomposed); } throw std::logic_error{"Invalid basis to use"}; }; - auto pulse_optimize = this->pulse_optimize.unwrap_or(true); + auto pulse_optimize = this->pulse_optimize.value_or(true); auto sequence = if (pulse_optimize) { self.pulse_optimal_chooser(best_nbasis, &decomposition, &target_decomposed) @@ -1008,21 +1008,394 @@ struct GateDecompositionPattern final }) } - std::array traces(TwoQubitWeylDecomposition target) { + private: + [[nodiscard]] std::vector + decomp0_inner(const TwoQubitWeylDecomposition& target) const { + return { + dot(target.K1r, target.K2r), + dot(target.K1l, target.K2l), + }; + } + + [[nodiscard]] std::vector + decomp1_inner(const TwoQubitWeylDecomposition& target) const { + auto transpose_conjugate = [](auto&& matrix) { + auto result = transpose(matrix); + llvm::transform(result, result.begin(), + [](auto&& x) { return std::conj(x); }); + return result; + }; + + // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in + // the Weyl chamber) + return { + dot(transpose_conjugate(basis_decomposer.K2r), target.K2r), + dot(transpose_conjugate(basis_decomposer.K2l), target.K2l), + dot(target.K1r, transpose_conjugate(basis_decomposer.K1r)), + dot(target.K1l, transpose_conjugate(basis_decomposer.K1l)), + }; + } + + [[nodiscard]] std::vector decomp2_supercontrolled_inner( + const TwoQubitWeylDecomposition& target) const { + return { + dot(q2r, target.K2r), + dot(q2l, target.K2l), + dot(dot(q1ra, rz_matrix(2. * target.b)), q1rb), + dot(dot(q1la, rz_matrix(-2. * target.a)), q1lb), + dot(target.K1r, q0r), + dot(target.K1l, q0l), + }; + } + + [[nodiscard]] std::vector decomp3_supercontrolled_inner( + const TwoQubitWeylDecomposition& target) const { return { - 4. * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), - std::sin(target.a) * std::sin(target.b) * - std::sin(target.c), ), + dot(u3r, target.K2r), + dot(u3l, target.K2l), + dot(dot(u2ra, rz_matrix(2. * target.b)), u2rb), + dot(dot(u2la, rz_matrix(-2. * target.a)), u2lb), + dot(dot(u1ra, rz_matrix(-2. * target.c)), u1rb), + u1l, + dot(target.K1r, u0r), + dot(target.K1l, u0l), + }; + } + + std::optional> + pulse_optimal_chooser(std::uint8_t best_nbasis, + std::vector decomposition, + const TwoQubitWeylDecomposition& target_decomposed) { + if (pulse_optimize.has_value() && + (best_nbasis == 0 || best_nbasis == 1 || best_nbasis > 3)) { + return std::nullopt; + } + if (euler_basis != EulerBasis::ZSX && euler_basis != EulerBasis::ZSXX) { + if (pulse_optimize.has_value()) { + throw std::runtime_error{ + "'pulse_optimize' currently only works with ZSX basis"}; + } + return std::nullopt; + } + + if (gate != "cx") { + if (pulse_optimize.has_value()) { + throw std::runtime_error{ + "pulse_optimizer currently only works with CNOT entangling gate"}; + } + return std::nullopt; + } + auto result = if (best_nbasis == 3) { + self.get_sx_vz_3cx_efficient_euler(decomposition, target_decomposed) + } + else if (best_nbasis == 2) { + self.get_sx_vz_2cx_efficient_euler(decomposition, target_decomposed) + } + else {None}; + if (pulse_optimize.has_value() && result.is_none()) { + throw std::runtime_error{ + "Failed to compute requested pulse optimal decomposition"}; + } + return result; + } + /// + /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT + /// gates assuming two CNOT gates are needed. + /// + /// This first decomposes each unitary from the KAK decomposition into ZXZ + /// on the source qubit of the CNOTs and XZX on the targets in order to + /// commute operators to beginning and end of decomposition. The beginning + /// and ending single qubit gates are then collapsed and re-decomposed with + /// the single qubit decomposer. This last step could be avoided if + /// performance is a concern. + std::optional> get_sx_vz_2cx_efficient_euler( + const std::vector& decomposition, + const TwoQubitWeylDecomposition& target_decomposed) { + std::vector gates; + auto global_phase = target_decomposed.global_phase; + global_phase -= 2. * basis_decomposer.global_phase; + + auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { + std::vector> result; + for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { + auto euler_angles = angles_from_unitary(decomposition[i], basis); + global_phase += euler_angles[3]; + result.push_back({euler_angles[2], euler_angles[0], euler_angles[1]}); + } + return result; + }; + + auto euler_q0 = get_euler_angles(0, EulerBasis::ZXZ); + auto euler_q1 = get_euler_angles(1, EulerBasis::XZX); + + auto euler_matrix_q0 = + dot(rx_matrix(euler_q0[0][1]), rz_matrix(euler_q0[0][0])); + euler_matrix_q0 = + dot(rz_matrix(euler_q0[0][2] + euler_q0[1][0] + qc::PI_2), + euler_matrix_q0); + append_1q_sequence(gates, global_phase, euler_matrix_q0, 0); + let mut euler_matrix_q1 = + rz_matrix(euler_q1[0][1]).dot(&rx_matrix(euler_q1[0][0])); + euler_matrix_q1 = + rx_matrix(euler_q1[0][2] + euler_q1[1][0]).dot(&euler_matrix_q1); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix_q1.view(), 1); + gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 0, 1 ])); + gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); + gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[1][1] - PI], + smallvec ![0], )); + gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); + gates.push((Some(StandardGate::RZ), smallvec ![euler_q1[1][1]], + smallvec ![1], )); + global_phase += PI2; + gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 0, 1 ])); + let mut euler_matrix_q0 = + rx_matrix(euler_q0[2][1]) + .dot(&rz_matrix(euler_q0[1][2] + euler_q0[2][0] + PI2)); + euler_matrix_q0 = rz_matrix(euler_q0[2][2]).dot(&euler_matrix_q0); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix_q0.view(), 0); + let mut euler_matrix_q1 = + rz_matrix(euler_q1[2][1]) + .dot(&rx_matrix(euler_q1[1][2] + euler_q1[2][0])); + euler_matrix_q1 = rx_matrix(euler_q1[2][2]).dot(&euler_matrix_q1); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix_q1.view(), 1); + Some(TwoQubitGateSequence{ + gates, + global_phase, + }) + } + + /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT + /// gates assuming three CNOT gates are needed. + /// + /// This first decomposes each unitary from the KAK decomposition into ZXZ + /// on the source qubit of the CNOTs and XZX on the targets in order commute + /// operators to beginning and end of decomposition. Inserting Hadamards + /// reverses the direction of the CNOTs and transforms a variable Rx -> + /// variable virtual Rz. The beginning and ending single qubit gates are + /// then collapsed and re-decomposed with the single qubit decomposer. This + /// last step could be avoided if performance is a concern. + fn get_sx_vz_3cx_efficient_euler( + &self, decomposition : &SmallVec<[Array2; 8]>, + target_decomposed : &TwoQubitWeylDecomposition, ) + -> Option { + let mut gates = Vec::new (); + let mut global_phase = target_decomposed.global_phase; + global_phase -= 3. * self.basis_decomposer.global_phase; + global_phase = global_phase.rem_euclid(TWO_PI); + let atol = 1e-10; // absolute tolerance for floats + // Decompose source unitaries to zxz + let euler_q0 + : Vec<[f64; 3]> = + decomposition.iter() + .step_by(2) + .map(| decomp | + { + let euler_angles = angles_from_unitary( + decomp.view(), EulerBasis::ZXZ); + global_phase += euler_angles[3]; + [ euler_angles[2], euler_angles[0], euler_angles[1] ] + }) + .collect(); + // Decompose target unitaries to xzx + let euler_q1 + : Vec<[f64; 3]> = + decomposition.iter() + .skip(1) + .step_by(2) + .map(| decomp | + { + let euler_angles = angles_from_unitary( + decomp.view(), EulerBasis::XZX); + global_phase += euler_angles[3]; + [ euler_angles[2], euler_angles[0], euler_angles[1] ] + }) + .collect(); + + let x12 = euler_q0[1][2] + euler_q0[2][0]; + let x12_is_non_zero = !abs_diff_eq !(x12, 0., epsilon = atol); + let mut x12_is_old_mult = None; + let mut x12_phase = 0.; + let x12_is_pi_mult = abs_diff_eq !(x12.sin(), 0., epsilon = atol); + if x12_is_pi_mult { + x12_is_old_mult = Some(abs_diff_eq !(x12.cos(), -1., epsilon = atol)); + x12_phase = PI * x12.cos(); + } + let x02_add = x12 - euler_q0[1][0]; + let x12_is_half_pi = abs_diff_eq !(x12, PI2, epsilon = atol); + + let mut euler_matrix_q0 = + rx_matrix(euler_q0[0][1]).dot(&rz_matrix(euler_q0[0][0])); + if x12_is_non_zero + &&x12_is_pi_mult { + euler_matrix_q0 = + rz_matrix(euler_q0[0][2] - x02_add).dot(&euler_matrix_q0); + } + else { + euler_matrix_q0 = + rz_matrix(euler_q0[0][2] + euler_q0[1][0]).dot(&euler_matrix_q0); + } + euler_matrix_q0 = aview2(&H_GATE).dot(&euler_matrix_q0); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix_q0.view(), 0); + + let rx_0 = rx_matrix(euler_q1[0][0]); + let rz = rz_matrix(euler_q1[0][1]); + let rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]); + let mut euler_matrix_q1 = rz.dot(&rx_0); + euler_matrix_q1 = rx_1.dot(&euler_matrix_q1); + euler_matrix_q1 = aview2(&H_GATE).dot(&euler_matrix_q1); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix_q1.view(), 1); + + gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); + + if x12_is_pi_mult { + // even or odd multiple + if x12_is_non_zero { + global_phase += x12_phase; + } + if x12_is_non_zero + &&x12_is_old_mult.unwrap() { + gates.push((Some(StandardGate::RZ), smallvec ![-euler_q0[1][1]], + smallvec ![0], )); + } + else { + gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[1][1]], + smallvec ![0], )); + global_phase += PI; + } + } + if x12_is_half_pi { + gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); + global_phase -= PI4; + } else if x12_is_non_zero + &&!x12_is_pi_mult { + if self + .pulse_optimize.is_none() { + self.append_1q_sequence(&mut gates, &mut global_phase, + rx_matrix(x12).view(), 0); + } + else { + return None; + } + } + if abs_diff_eq + !(euler_q1[1][1], PI2, epsilon = atol) { + gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![1])); + global_phase -= PI4 + } + else if self + .pulse_optimize.is_none() { + self.append_1q_sequence(&mut gates, &mut global_phase, + rx_matrix(euler_q1[1][1]).view(), 1, ); + } + else { + return None; + } + gates.push((Some(StandardGate::RZ), + smallvec ![euler_q1[1][2] + euler_q1[2][0]], + smallvec ![1], )); + gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); + gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[2][1]], + smallvec ![0], )); + if abs_diff_eq + !(euler_q1[2][1], PI2, epsilon = atol) { + gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![1])); + global_phase -= PI4; + } + else if self + .pulse_optimize.is_none() { + self.append_1q_sequence(&mut gates, &mut global_phase, + rx_matrix(euler_q1[2][1]).view(), 1, ); + } + else { + return None; + } + gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); + let mut euler_matrix = + rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&H_GATE)); + euler_matrix = rx_matrix(euler_q0[3][1]).dot(&euler_matrix); + euler_matrix = rz_matrix(euler_q0[3][2]).dot(&euler_matrix); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix.view(), 0); + + let mut euler_matrix = + rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&H_GATE)); + euler_matrix = rz_matrix(euler_q1[3][1]).dot(&euler_matrix); + euler_matrix = rx_matrix(euler_q1[3][2]).dot(&euler_matrix); + self.append_1q_sequence(&mut gates, &mut global_phase, + euler_matrix.view(), 1); + + let out_unitary = compute_unitary(&gates, global_phase); + // TODO: fix the sign problem to avoid correction here + if abs_diff_eq + !(target_decomposed.unitary_matrix[[ 0, 0 ]], -out_unitary[[ 0, 0 ]], + epsilon = atol) { + global_phase += PI; + } + Some(TwoQubitGateSequence{ + gates, + global_phase, + }) + } + + void append_1q_sequence(std::vector& gates, qc::fp& global_phase, + matrix4x4 unitary, std::uint8_t qubit) { + std::vector target_1q_basis_list; + target_1q_basis_list.push_back(euler_basis); + auto sequence = unitary_to_gate_sequence_inner( + unitary, &target_1q_basis_list, qubit as usize, None, true, None, ); + if let + Some(sequence) = sequence { + *global_phase += sequence.global_phase; + for + gate in sequence.gates { + gates.push((Some(gate.0), gate.1, smallvec ![qubit])); + } + } + } + + [[nodiscard]] std::array + traces(TwoQubitWeylDecomposition target) const { + return { + 4. * + qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), + std::sin(target.a) * std::sin(target.b) * std::sin(target.c)), 4. * qfp(std::cos(qc::PI_4 - target.a) * - std::cos(self.basis_decomposer.b - target.b) * + std::cos(basis_decomposer.b - target.b) * std::cos(target.c), std::sin(qc::PI_4 - target.a) * - std::sin(self.basis_decomposer.b - target.b) * + std::sin(basis_decomposer.b - target.b) * std::sin(target.c)), qfp(4. * std::cos(target.c), 0.), qfp(4., 0.), }; } + + std::optional> unitary_to_gate_sequence_inner( + matrix4x4 unitary_mat, const std::vector& target_basis_list, + std::size_t qubit, + std::vector> error_map, + bool simplify, std::optional atol) { + target_basis_list.get_bases() + .map(| target_basis | + { + let[theta, phi, lam, phase] = + angles_from_unitary(unitary_mat, target_basis); + generate_circuit(&target_basis, theta, phi, lam, phase, + simplify, atol) + .unwrap() + }) + .min_by( + | a, b | { + let error_a = compare_error_fn(a, &error_map, qubit); + let error_b = compare_error_fn(b, &error_map, qubit); + error_a.partial_cmp(&error_b).unwrap_or(Ordering::Equal) + }) + } }; }; From 91fd9c09064c2cc20765aac0631c8095c6f38ed1 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 9 Oct 2025 23:13:51 +0200 Subject: [PATCH 008/100] add pass to Passes.td --- mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index bdcc5138e..2540b796c 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -43,6 +43,12 @@ def GateElimination : Pass<"gate-elimination", "mlir::ModuleOp"> { }]; } +def GateDecomposition : Pass<"gate-decomposition", "mlir::ModuleOp"> { + let summary = "This pass performs various gate decompositions to translate quantum gates being used."; + let description = [{ + }]; +} + def MergeRotationGates : Pass<"merge-rotation-gates", "mlir::ModuleOp"> { let summary = "This pass searches for consecutive applications of rotation gates that can be merged."; let description = [{ From 8cf929f67ce909a3f10a0d5b42ff87960328f8f0 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 9 Oct 2025 23:14:07 +0200 Subject: [PATCH 009/100] snapshot --- .../Transforms/GateDecompositionPattern.cpp | 703 ++++++++++-------- 1 file changed, 413 insertions(+), 290 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index fb05f6586..090ca3353 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -144,7 +144,10 @@ struct GateDecompositionPattern final using matrix2x2 = std::array; using matrix4x4 = std::array; + static constexpr auto sqrt2 = static_cast(1.4142135623730950488L); static constexpr matrix2x2 identityGate = {1, 0, 0, 1}; + static constexpr matrix2x2 hGate = {1.0 / sqrt2, 1.0 / sqrt2, 1.0 / sqrt2, + -1.0 / sqrt2}; static qc::fp remEuclid(qc::fp a, qc::fp b) { auto r = std::fmod(a, b); @@ -873,7 +876,8 @@ struct GateDecompositionPattern final static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; struct TwoQubitBasisDecomposer { - std::string gate; + qc::OpType gate; + std::optional gate_params; qc::fp basis_fidelity; EulerBasis euler_basis; std::optional pulse_optimize; @@ -899,10 +903,22 @@ struct GateDecompositionPattern final matrix2x2 q2l; matrix2x2 q2r; - void twoQubitDecompose(const matrix4x4& unitaryMatrix, - std::optional _basis_fidelity, - bool approximate, - std::optional _num_basis_uses) { + struct QubitGateSequence { + struct Gate { + qc::OpType type{qc::I}; + std::optional parameter; + std::vector qubit_id = {0}; + }; + std::vector gates; + qc::fp globalPhase; + }; + using OneQubitGateSequence = QubitGateSequence; + using TwoQubitGateSequence = QubitGateSequence; + + std::optional + twoQubitDecompose(const matrix4x4& unitaryMatrix, + std::optional _basis_fidelity, bool approximate, + std::optional _num_basis_uses) { auto get_basis_fidelity = [&]() { if (approximate) { return _basis_fidelity.value_or(this->basis_fidelity); @@ -941,71 +957,58 @@ struct GateDecompositionPattern final } throw std::logic_error{"Invalid basis to use"}; }; - auto pulse_optimize = this->pulse_optimize.value_or(true); - auto sequence = if (pulse_optimize) { - self.pulse_optimal_chooser(best_nbasis, &decomposition, - &target_decomposed) - ? + auto decomposition = choose_decomposition(); + if (pulse_optimize.value_or(true)) { + return pulse_optimal_chooser(best_nbasis, decomposition, + target_decomposed); } - else {None}; - if let - Some(seq) = sequence { return Ok(seq); } - auto target_1q_basis_list = EulerBasisSet::new (); - target_1q_basis_list.add_basis(self.euler_basis); - let euler_decompositions - : SmallVec<[Option; 8]> = - decomposition.iter() - .map(| decomp | - {unitary_to_gate_sequence_inner( - decomp.view(), &target_1q_basis_list, 0, None, - true, None, )}) - .collect(); - auto gates = Vec::with_capacity(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - auto global_phase = target_decomposed.global_phase; - global_phase -= best_nbasis as f64 * self.basis_decomposer.global_phase; - if best_nbasis - == 2 { global_phase += PI; } - for - i in 0..best_nbasis as usize { - if let - Some(euler_decomp) = &euler_decompositions[2 * i] { - for - gate in& euler_decomp.gates { - gates.push((gate.0.into(), gate.1.clone(), smallvec ![0])); - } - global_phase += euler_decomp.global_phase - } - if let - Some(euler_decomp) = &euler_decompositions[2 * i + 1] { - for - gate in& euler_decomp.gates { - gates.push((gate.0.into(), gate.1.clone(), smallvec ![1])); - } - global_phase += euler_decomp.global_phase - } - gates.push( - (self.gate.clone(), self.gate_params.clone(), smallvec ![ 0, 1 ])); + std::vector target_1q_basis_list; + target_1q_basis_list.push_back(euler_basis); + llvm::SmallVector, 8> + euler_decompositions; + for (auto&& decomp : decomposition) { + auto euler_decomp = unitary_to_gate_sequence_inner( + decomp, target_1q_basis_list, 0, {}, true, std::nullopt); + euler_decompositions.push_back(euler_decomp); } - if let - Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize] { - for - gate in& euler_decomp.gates { - gates.push((gate.0.into(), gate.1.clone(), smallvec ![0])); - } - global_phase += euler_decomp.global_phase + // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q gate + // We might overallocate a bit if the euler basis is different but + // the worst case is just 16 extra elements with just a String and 2 + // smallvecs each. This is only transient though as the circuit sequences + // aren't long lived and are just used to create a QuantumCircuit or + // DAGCircuit when we return to Python space. + constexpr auto TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY = 21; + TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; + gates.gates.reserve(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); + gates.globalPhase -= best_nbasis * basis_decomposer.global_phase; + if (best_nbasis == 2) { + gates.globalPhase += qc::PI; } - if let - Some(euler_decomp) = &euler_decompositions[2 * best_nbasis as usize + 1] { - for - gate in& euler_decomp.gates { - gates.push((gate.0.into(), gate.1.clone(), smallvec ![1])); + + auto add_euler_decomposition = [&](std::size_t index, + std::size_t qubit_id) { + if (auto&& euler_decomp = euler_decompositions[index]) { + for (auto&& gate : euler_decomp->gates) { + gates.gates.push_back({.type = gate.type, + .parameter = gate.parameter, + .qubit_id = {qubit_id}}); + gates.globalPhase += euler_decomp->globalPhase; } - global_phase += euler_decomp.global_phase + } + }; + + for (std::size_t i = 0; i < best_nbasis; ++i) { + add_euler_decomposition(2 * i, 0); + add_euler_decomposition(2 * i + 1, 1); + + gates.gates.push_back( + {.type = gate, .parameter = gate_params, .qubit_id = {0, 1}}); } - Ok(TwoQubitGateSequence{ - gates, - global_phase, - }) + + add_euler_decomposition(2 * best_nbasis, 0); + add_euler_decomposition(2 * best_nbasis + 1, 1); + + return gates; } private: @@ -1062,7 +1065,7 @@ struct GateDecompositionPattern final }; } - std::optional> + std::optional pulse_optimal_chooser(std::uint8_t best_nbasis, std::vector decomposition, const TwoQubitWeylDecomposition& target_decomposed) { @@ -1085,19 +1088,64 @@ struct GateDecompositionPattern final } return std::nullopt; } - auto result = if (best_nbasis == 3) { - self.get_sx_vz_3cx_efficient_euler(decomposition, target_decomposed) - } - else if (best_nbasis == 2) { - self.get_sx_vz_2cx_efficient_euler(decomposition, target_decomposed) + std::optional result; + if (best_nbasis == 3) { + result = + get_sx_vz_3cx_efficient_euler(decomposition, target_decomposed); + } else if (best_nbasis == 2) { + result = + get_sx_vz_2cx_efficient_euler(decomposition, target_decomposed); } - else {None}; - if (pulse_optimize.has_value() && result.is_none()) { + if (pulse_optimize.has_value() && !result.has_value()) { throw std::runtime_error{ "Failed to compute requested pulse optimal decomposition"}; } return result; } + + matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { + if (gate.type == qc::SX) { + return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; + } + if (gate.type == qc::RZ) { + return rz_matrix(gate.parameter.value()); + } + if (gate.type == qc::X) { + return {0, 1, 1, 0}; + } + throw std::invalid_argument{ + "unsupported gate type for single qubit matrix"}; + } + + matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { + if (gate.qubit_id.empty()) { + return kroneckerProduct(identityGate, identityGate); + } else if (gate.qubit_id.size() == 1) { + if (gate.qubit_id[0] == 0) { + return kroneckerProduct(identityGate, getSingleQubitMatrix(gate)); + } else if (gate.qubit_id[0] == 1) { + return kroneckerProduct(getSingleQubitMatrix(gate), identityGate); + } else { + throw std::logic_error{"Invalid qubit ID in compute_unitary"}; + } + } else if (gate.qubit_id.size() == 2) { + if (gate.type == qc::X) { + // controlled X (CX) + if (gate.qubit_id == std::vector{0, 1}) { + return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; + } + if (gate.qubit_id == std::vector{1, 0}) { + return {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; + } + } + throw std::invalid_argument{ + "unsupported gate type for two qubit matrix"}; + } else { + throw std::logic_error{ + "Invalid number of qubit IDs in compute_unitary"}; + } + } + /// /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT /// gates assuming two CNOT gates are needed. @@ -1108,18 +1156,17 @@ struct GateDecompositionPattern final /// and ending single qubit gates are then collapsed and re-decomposed with /// the single qubit decomposer. This last step could be avoided if /// performance is a concern. - std::optional> get_sx_vz_2cx_efficient_euler( + TwoQubitGateSequence get_sx_vz_2cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { - std::vector gates; - auto global_phase = target_decomposed.global_phase; - global_phase -= 2. * basis_decomposer.global_phase; + TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; + gates.globalPhase -= 2. * basis_decomposer.global_phase; auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { std::vector> result; for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { auto euler_angles = angles_from_unitary(decomposition[i], basis); - global_phase += euler_angles[3]; + gates.globalPhase += euler_angles[3]; result.push_back({euler_angles[2], euler_angles[0], euler_angles[1]}); } return result; @@ -1133,38 +1180,34 @@ struct GateDecompositionPattern final euler_matrix_q0 = dot(rz_matrix(euler_q0[0][2] + euler_q0[1][0] + qc::PI_2), euler_matrix_q0); - append_1q_sequence(gates, global_phase, euler_matrix_q0, 0); - let mut euler_matrix_q1 = - rz_matrix(euler_q1[0][1]).dot(&rx_matrix(euler_q1[0][0])); + append_1q_sequence(gates, euler_matrix_q0, 0); + auto euler_matrix_q1 = + dot(rz_matrix(euler_q1[0][1]), rx_matrix(euler_q1[0][0])); euler_matrix_q1 = - rx_matrix(euler_q1[0][2] + euler_q1[1][0]).dot(&euler_matrix_q1); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix_q1.view(), 1); - gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 0, 1 ])); - gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); - gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[1][1] - PI], - smallvec ![0], )); - gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); - gates.push((Some(StandardGate::RZ), smallvec ![euler_q1[1][1]], - smallvec ![1], )); - global_phase += PI2; - gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 0, 1 ])); - let mut euler_matrix_q0 = - rx_matrix(euler_q0[2][1]) - .dot(&rz_matrix(euler_q0[1][2] + euler_q0[2][0] + PI2)); - euler_matrix_q0 = rz_matrix(euler_q0[2][2]).dot(&euler_matrix_q0); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix_q0.view(), 0); - let mut euler_matrix_q1 = - rz_matrix(euler_q1[2][1]) - .dot(&rx_matrix(euler_q1[1][2] + euler_q1[2][0])); - euler_matrix_q1 = rx_matrix(euler_q1[2][2]).dot(&euler_matrix_q1); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix_q1.view(), 1); - Some(TwoQubitGateSequence{ - gates, - global_phase, - }) + dot(rx_matrix(euler_q1[0][2] + euler_q1[1][0]), euler_matrix_q1); + append_1q_sequence(gates, euler_matrix_q1, 1); + gates.gates.push_back( + {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? + gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); + gates.gates.push_back({.type = qc::RZ, + .parameter = euler_q0[1][1] - qc::PI, + .qubit_id = {0}}); + gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); + gates.gates.push_back( + {.type = qc::RZ, .parameter = euler_q1[1][1], .qubit_id = {1}}); + gates.globalPhase += qc::PI_2; + gates.gates.push_back( + {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? + euler_matrix_q0 = + dot(rx_matrix(euler_q0[2][1]), + rz_matrix(euler_q0[1][2] + euler_q0[2][0] + qc::PI_2)); + euler_matrix_q0 = dot(rz_matrix(euler_q0[2][2]), euler_matrix_q0); + append_1q_sequence(gates, euler_matrix_q0, 0); + euler_matrix_q1 = dot(rz_matrix(euler_q1[2][1]), + rx_matrix(euler_q1[1][2] + euler_q1[2][0])); + euler_matrix_q1 = dot(rx_matrix(euler_q1[2][2]), euler_matrix_q1); + append_1q_sequence(gates, euler_matrix_q1, 1); + return gates; } /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT @@ -1177,185 +1220,158 @@ struct GateDecompositionPattern final /// variable virtual Rz. The beginning and ending single qubit gates are /// then collapsed and re-decomposed with the single qubit decomposer. This /// last step could be avoided if performance is a concern. - fn get_sx_vz_3cx_efficient_euler( - &self, decomposition : &SmallVec<[Array2; 8]>, - target_decomposed : &TwoQubitWeylDecomposition, ) - -> Option { - let mut gates = Vec::new (); - let mut global_phase = target_decomposed.global_phase; - global_phase -= 3. * self.basis_decomposer.global_phase; - global_phase = global_phase.rem_euclid(TWO_PI); - let atol = 1e-10; // absolute tolerance for floats - // Decompose source unitaries to zxz - let euler_q0 - : Vec<[f64; 3]> = - decomposition.iter() - .step_by(2) - .map(| decomp | - { - let euler_angles = angles_from_unitary( - decomp.view(), EulerBasis::ZXZ); - global_phase += euler_angles[3]; - [ euler_angles[2], euler_angles[0], euler_angles[1] ] - }) - .collect(); + std::optional get_sx_vz_3cx_efficient_euler( + const std::vector& decomposition, + const TwoQubitWeylDecomposition& target_decomposed) { + TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; + gates.globalPhase -= 3. * basis_decomposer.global_phase; + gates.globalPhase = remEuclid(gates.globalPhase, qc::TAU); + auto atol = 1e-10; // absolute tolerance for floats + // Decompose source unitaries to zxz + + auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { + std::vector> result; + for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { + auto euler_angles = angles_from_unitary(decomposition[i], basis); + gates.globalPhase += euler_angles[3]; + result.push_back({euler_angles[2], euler_angles[0], euler_angles[1]}); + } + return result; + }; + + auto euler_q0 = get_euler_angles(0, EulerBasis::ZXZ); // Decompose target unitaries to xzx - let euler_q1 - : Vec<[f64; 3]> = - decomposition.iter() - .skip(1) - .step_by(2) - .map(| decomp | - { - let euler_angles = angles_from_unitary( - decomp.view(), EulerBasis::XZX); - global_phase += euler_angles[3]; - [ euler_angles[2], euler_angles[0], euler_angles[1] ] - }) - .collect(); - - let x12 = euler_q0[1][2] + euler_q0[2][0]; - let x12_is_non_zero = !abs_diff_eq !(x12, 0., epsilon = atol); - let mut x12_is_old_mult = None; - let mut x12_phase = 0.; - let x12_is_pi_mult = abs_diff_eq !(x12.sin(), 0., epsilon = atol); - if x12_is_pi_mult { - x12_is_old_mult = Some(abs_diff_eq !(x12.cos(), -1., epsilon = atol)); - x12_phase = PI * x12.cos(); + auto euler_q1 = get_euler_angles(1, EulerBasis::XZX); + + auto x12 = euler_q0[1][2] + euler_q0[2][0]; + auto x12_is_non_zero = std::abs(x12) < atol; + auto x12_is_old_mult = std::abs(std::cos(x12) - 1.0) < atol; + auto x12_phase = 0.; + auto x12_is_pi_mult = std::abs(std::sin(x12)) < atol; + if (x12_is_pi_mult) { + x12_phase = qc::PI * std::cos(x12); } - let x02_add = x12 - euler_q0[1][0]; - let x12_is_half_pi = abs_diff_eq !(x12, PI2, epsilon = atol); - - let mut euler_matrix_q0 = - rx_matrix(euler_q0[0][1]).dot(&rz_matrix(euler_q0[0][0])); - if x12_is_non_zero - &&x12_is_pi_mult { - euler_matrix_q0 = - rz_matrix(euler_q0[0][2] - x02_add).dot(&euler_matrix_q0); - } - else { + auto x02_add = x12 - euler_q0[1][0]; + auto x12_is_half_pi = std::abs(x12 - qc::PI_2) < atol; + + auto euler_matrix_q0 = + dot(rx_matrix(euler_q0[0][1]), rz_matrix(euler_q0[0][0])); + if (x12_is_non_zero && x12_is_pi_mult) { + euler_matrix_q0 = + dot(rz_matrix(euler_q0[0][2] - x02_add), euler_matrix_q0); + } else { euler_matrix_q0 = - rz_matrix(euler_q0[0][2] + euler_q0[1][0]).dot(&euler_matrix_q0); + dot(rz_matrix(euler_q0[0][2] + euler_q0[1][0]), euler_matrix_q0); } - euler_matrix_q0 = aview2(&H_GATE).dot(&euler_matrix_q0); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix_q0.view(), 0); - - let rx_0 = rx_matrix(euler_q1[0][0]); - let rz = rz_matrix(euler_q1[0][1]); - let rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]); - let mut euler_matrix_q1 = rz.dot(&rx_0); - euler_matrix_q1 = rx_1.dot(&euler_matrix_q1); - euler_matrix_q1 = aview2(&H_GATE).dot(&euler_matrix_q1); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix_q1.view(), 1); - - gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); - - if x12_is_pi_mult { + euler_matrix_q0 = dot(hGate, euler_matrix_q0); + append_1q_sequence(gates, euler_matrix_q0, 0); + + auto rx_0 = rx_matrix(euler_q1[0][0]); + auto rz = rz_matrix(euler_q1[0][1]); + auto rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]); + auto euler_matrix_q1 = dot(rz, rx_0); + euler_matrix_q1 = dot(rx_1, euler_matrix_q1); + euler_matrix_q1 = dot(hGate, euler_matrix_q1); + append_1q_sequence(gates, euler_matrix_q1, 1); + + gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); + + if (x12_is_pi_mult) { // even or odd multiple - if x12_is_non_zero { - global_phase += x12_phase; + if (x12_is_non_zero) { + gates.globalPhase += x12_phase; } - if x12_is_non_zero - &&x12_is_old_mult.unwrap() { - gates.push((Some(StandardGate::RZ), smallvec ![-euler_q0[1][1]], - smallvec ![0], )); - } - else { - gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[1][1]], - smallvec ![0], )); - global_phase += PI; + if (x12_is_non_zero && x12_is_old_mult) { + gates.gates.push_back({.type = qc::RZ, + .parameter = {-euler_q0[1][1]}, + .qubit_id = {0}}); + } else { + gates.gates.push_back( + {.type = qc::RZ, .parameter = {euler_q0[1][1]}, .qubit_id = {0}}); + gates.globalPhase += qc::PI; } } - if x12_is_half_pi { - gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![0])); - global_phase -= PI4; - } else if x12_is_non_zero - &&!x12_is_pi_mult { - if self - .pulse_optimize.is_none() { - self.append_1q_sequence(&mut gates, &mut global_phase, - rx_matrix(x12).view(), 0); - } - else { - return None; - } - } - if abs_diff_eq - !(euler_q1[1][1], PI2, epsilon = atol) { - gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![1])); - global_phase -= PI4 - } - else if self - .pulse_optimize.is_none() { - self.append_1q_sequence(&mut gates, &mut global_phase, - rx_matrix(euler_q1[1][1]).view(), 1, ); + if (x12_is_half_pi) { + gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); + gates.globalPhase -= qc::PI_4; + } else if (x12_is_non_zero && !x12_is_pi_mult) { + if (!pulse_optimize.has_value()) { + append_1q_sequence(gates, rx_matrix(x12), 0); + } else { + return std::nullopt; } - else { - return None; } - gates.push((Some(StandardGate::RZ), - smallvec ![euler_q1[1][2] + euler_q1[2][0]], - smallvec ![1], )); - gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); - gates.push((Some(StandardGate::RZ), smallvec ![euler_q0[2][1]], - smallvec ![0], )); - if abs_diff_eq - !(euler_q1[2][1], PI2, epsilon = atol) { - gates.push((Some(StandardGate::SX), smallvec ![], smallvec ![1])); - global_phase -= PI4; - } - else if self - .pulse_optimize.is_none() { - self.append_1q_sequence(&mut gates, &mut global_phase, - rx_matrix(euler_q1[2][1]).view(), 1, ); - } - else { - return None; + if (std::abs(euler_q1[1][1] - qc::PI_2) < atol) { + gates.gates.push_back({.type = qc::SX, .qubit_id = {1}}); + gates.globalPhase -= qc::PI_4; + } else if (!pulse_optimize.has_value()) { + append_1q_sequence(gates, rx_matrix(euler_q1[1][1]), 1); + } else { + return std::nullopt; + } + gates.gates.push_back({.type = qc::RZ, + .parameter = {euler_q1[1][2] + euler_q1[2][0]}, + .qubit_id = {1}}); + gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); + gates.gates.push_back( + {.type = qc::RZ, .parameter = {euler_q1[2][1]}, .qubit_id = {0}}); + if (std::abs(euler_q1[2][1] - qc::PI_2) < atol) { + gates.gates.push_back({.type = qc::SX, .qubit_id = {1}}); + gates.globalPhase -= qc::PI_4; + } else if (!pulse_optimize.has_value()) { + append_1q_sequence(gates, rx_matrix(euler_q1[2][1]), 1); + } else { + return std::nullopt; } - gates.push((Some(StandardGate::CX), smallvec ![], smallvec ![ 1, 0 ])); - let mut euler_matrix = - rz_matrix(euler_q0[2][2] + euler_q0[3][0]).dot(&aview2(&H_GATE)); - euler_matrix = rx_matrix(euler_q0[3][1]).dot(&euler_matrix); - euler_matrix = rz_matrix(euler_q0[3][2]).dot(&euler_matrix); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix.view(), 0); - - let mut euler_matrix = - rx_matrix(euler_q1[2][2] + euler_q1[3][0]).dot(&aview2(&H_GATE)); - euler_matrix = rz_matrix(euler_q1[3][1]).dot(&euler_matrix); - euler_matrix = rx_matrix(euler_q1[3][2]).dot(&euler_matrix); - self.append_1q_sequence(&mut gates, &mut global_phase, - euler_matrix.view(), 1); - - let out_unitary = compute_unitary(&gates, global_phase); + gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); + auto eulerMatrix = dot(rz_matrix(euler_q0[2][2] + euler_q0[3][0]), hGate); + eulerMatrix = dot(rx_matrix(euler_q0[3][1]), eulerMatrix); + eulerMatrix = dot(rz_matrix(euler_q0[3][2]), eulerMatrix); + append_1q_sequence(gates, eulerMatrix, 0); + + eulerMatrix = dot(rx_matrix(euler_q1[2][2] + euler_q1[3][0]), hGate); + eulerMatrix = dot(rz_matrix(euler_q1[3][1]), eulerMatrix); + eulerMatrix = dot(rx_matrix(euler_q1[3][2]), eulerMatrix); + append_1q_sequence(gates, eulerMatrix, 1); + + auto out_unitary = compute_unitary(gates, gates.globalPhase); // TODO: fix the sign problem to avoid correction here - if abs_diff_eq - !(target_decomposed.unitary_matrix[[ 0, 0 ]], -out_unitary[[ 0, 0 ]], - epsilon = atol) { - global_phase += PI; - } - Some(TwoQubitGateSequence{ - gates, - global_phase, - }) + if (std::abs(target_decomposed.unitary_matrix.at(0 * 4 + 0) - + (-out_unitary.at(0 * 4 + 0))) < atol) { + gates.globalPhase += qc::PI; + } + return gates; } - void append_1q_sequence(std::vector& gates, qc::fp& global_phase, - matrix4x4 unitary, std::uint8_t qubit) { + matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, + qc::fp global_phase) { + auto phase = std::exp(std::complex{0, global_phase}); + matrix4x4 matrix; + matrix[0 * 4 + 0] = phase; + matrix[1 * 4 + 1] = phase; + matrix[2 * 4 + 2] = phase; + matrix[3 * 4 + 3] = phase; + + for (auto&& gate : sequence.gates) { + matrix4x4 gate_matrix = getTwoQubitMatrix(gate); + + matrix = dot(gate_matrix, matrix); + } + return matrix; + } + + void append_1q_sequence(TwoQubitGateSequence& twoQubitSequence, + matrix2x2 unitary, std::uint8_t qubit) { std::vector target_1q_basis_list; target_1q_basis_list.push_back(euler_basis); auto sequence = unitary_to_gate_sequence_inner( - unitary, &target_1q_basis_list, qubit as usize, None, true, None, ); - if let - Some(sequence) = sequence { - *global_phase += sequence.global_phase; - for - gate in sequence.gates { - gates.push((Some(gate.0), gate.1, smallvec ![qubit])); - } - } + unitary, target_1q_basis_list, qubit, {}, true, std::nullopt); + twoQubitSequence.globalPhase += sequence.globalPhase; + for (auto&& gate : sequence.gates) { + twoQubitSequence.gates.push_back({.type = gate.type, + .parameter = gate.parameter, + .qubit_id = {qubit}}); + } } [[nodiscard]] std::array @@ -1375,28 +1391,135 @@ struct GateDecompositionPattern final }; } - std::optional> unitary_to_gate_sequence_inner( - matrix4x4 unitary_mat, const std::vector& target_basis_list, + OneQubitGateSequence generate_circuit(EulerBasis target_basis, + const matrix2x2& unitaryMatrix, + bool simplify, + std::optional atol) { + auto [theta, phi, lamda, phase] = + angles_from_unitary(unitaryMatrix, target_basis); + + switch (target_basis) { + case EulerBasis::ZYZ: + return calculateRotationGates(theta, phi, lamda, phase, qc::RZ, qc::RY, + simplify, atol); + case EulerBasis::ZXZ: + return calculateRotationGates(theta, phi, lamda, phase, qc::RZ, qc::RX, + simplify, atol); + case EulerBasis::XZX: + return calculateRotationGates(theta, phi, lamda, phase, qc::RX, qc::RZ, + simplify, atol); + case EulerBasis::XYX: + return calculateRotationGates(theta, phi, lamda, phase, qc::RX, qc::RY, + simplify, atol); + default: + // TODO: allow other bases + throw std::invalid_argument{"Unsupported base for circuit generation!"}; + } + } + + OneQubitGateSequence unitary_to_gate_sequence_inner( + matrix2x2 unitary_mat, const std::vector& target_basis_list, std::size_t qubit, - std::vector> error_map, + const std::vector>& + error_map, // TODO: remove error_map+qubit for platform independence bool simplify, std::optional atol) { - target_basis_list.get_bases() - .map(| target_basis | - { - let[theta, phi, lam, phase] = - angles_from_unitary(unitary_mat, target_basis); - generate_circuit(&target_basis, theta, phi, lam, phase, - simplify, atol) - .unwrap() - }) - .min_by( - | a, b | { - let error_a = compare_error_fn(a, &error_map, qubit); - let error_b = compare_error_fn(b, &error_map, qubit); - error_a.partial_cmp(&error_b).unwrap_or(Ordering::Equal) - }) + auto calculateError = [](const OneQubitGateSequence& sequence) { + return sequence.gates.size(); + }; + + auto minError = std::numeric_limits::max(); + OneQubitGateSequence bestCircuit; + for (std::size_t i = 0; i < target_basis_list.size(); ++i) { + auto& target_basis = target_basis_list[i]; + auto circuit = + generate_circuit(target_basis, unitary_mat, simplify, atol); + auto error = calculateError(circuit); + if (error < minError) { + bestCircuit = circuit; + minError = error; + } + return bestCircuit; + } } - }; + + // TODO: copied+adapted from single-qubit decomposition + /** + * @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 + * http://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 + calculateRotationGates(qc::fp theta, qc::fp phi, qc::fp lambda, + qc::fp phase, qc::OpType kGate, qc::OpType aGate, + bool simplify, std::optional atol) { + qc::fp angleZeroEpsilon = atol.value_or(1e-12); + if (simplify) { + angleZeroEpsilon = -1.0; + } + + auto remEuclid = [](qc::fp a, qc::fp b) { + auto r = std::fmod(a, b); + return (r < 0.0) ? r + std::abs(b) : r; + }; + // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp + // to -π + auto mod2pi = [&](qc::fp angle) -> qc::fp { + // remEuclid() isn't exactly the same as Python's % operator, but + // because the RHS here is a constant and positive it is effectively + // equivalent for this case + auto wrapped = remEuclid(angle + qc::PI, 2. * qc::PI) - qc::PI; + if (std::abs(wrapped - qc::PI) < angleZeroEpsilon) { + return -qc::PI; + } + return wrapped; + }; + + qc::fp globalPhase = phase - ((phi + lambda) / 2.); + + std::vector gates; + if (std::abs(theta) < angleZeroEpsilon) { + lambda += phi; + lambda = mod2pi(lambda); + if (std::abs(lambda) > angleZeroEpsilon) { + gates.push_back({kGate, lambda}); + globalPhase += lambda / 2.0; + } + return {gates, globalPhase}; + } + + if (std::abs(theta - qc::PI) < angleZeroEpsilon) { + globalPhase += phi; + lambda -= phi; + phi = 0.0; + } + if (std::abs(mod2pi(lambda + qc::PI)) < angleZeroEpsilon || + std::abs(mod2pi(phi + qc::PI)) < angleZeroEpsilon) { + lambda += qc::PI; + theta = -theta; + phi += qc::PI; + } + lambda = mod2pi(lambda); + if (std::abs(lambda) > angleZeroEpsilon) { + globalPhase += lambda / 2.0; + gates.push_back({kGate, lambda}); + } + gates.push_back({aGate, theta}); + phi = mod2pi(phi); + if (std::abs(phi) > angleZeroEpsilon) { + globalPhase += phi / 2.0; + gates.push_back({kGate, phi}); + } + return {gates, globalPhase}; + } + }; // namespace mqt::ir::opt }; /** From 20324366025144c0fcb6e2b6bf4a6f72fd470785 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 13 Oct 2025 21:41:51 +0200 Subject: [PATCH 010/100] compiling snapshot --- .../Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- .../Transforms/GateDecompositionPattern.cpp | 366 +++++++++++++++--- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 27 +- 3 files changed, 332 insertions(+), 63 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index e7f1f55da..6badce698 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -set(LIBRARIES ${dialect_libs} MQT::CoreIR) +set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD) add_compile_options(-fexceptions) file(GLOB_RECURSE TRANSFORMS_SOURCES *.cpp) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 090ca3353..7f2ebb541 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -43,18 +43,29 @@ struct GateDecompositionPattern final matchAndRewrite(UnitaryInterface op, mlir::PatternRewriter& rewriter) const override { auto series = getTwoQubitSeries(op); + llvm::errs() << "SERIES SIZE: " << series.size() << '\n'; + if (series.size() <= 3) { return mlir::failure(); } - dd::TwoQubitGateMatrix unitaryMatrix = dd::opToTwoQubitGateMatrix(qc::I); + matrix4x4 unitaryMatrix = + helpers::flatten(dd::opToTwoQubitGateMatrix(qc::I)); for (auto&& gate : series) { if (auto gateMatrix = helpers::getUnitaryMatrix(gate)) { - unitaryMatrix = helpers::multiply(unitaryMatrix, *gateMatrix); + unitaryMatrix = + helpers::multiply(unitaryMatrix, helpers::flatten(*gateMatrix)); } } - twoQubitDecompose(unitaryMatrix); + TwoQubitBasisDecomposer decomposer; + auto sequence = decomposer.twoQubitDecompose(unitaryMatrix, DEFAULT_FIDELITY, true, + std::nullopt); + if (!sequence) { + return mlir::failure(); + } + + applySeries(rewriter, series, *sequence); return mlir::success(); } @@ -71,14 +82,22 @@ struct GateDecompositionPattern final } else { return result; } + result.push_back(op); + while (true) { - for (auto&& user : op->getUsers()) { - auto userUnitary = llvm::cast(user); + for (auto&& user : llvm::concat(qubits[0].getUsers(), + qubits[1].getUsers())) { + auto userUnitary = mlir::dyn_cast(user); + if (!userUnitary) { + return result; + } + if (helpers::isSingleQubitOperation(userUnitary)) { auto&& operand = userUnitary->getOperand(0); auto* it = llvm::find(qubits, operand); if (it == qubits.end()) { - return result; + throw std::logic_error{"Operand of single-qubit op and user of " + "qubit is not in qubits"}; } *it = userUnitary->getResult(0); @@ -99,7 +118,48 @@ struct GateDecompositionPattern final return result; } } - return result; + } + return result; + } + + /** + * @brief Creates a new rotation gate with no controls. + * + * @tparam OpType The type of the operation to be created. + * @param op The first instance of the rotation gate. + * @param rewriter The pattern rewriter. + * @return A new rotation gate. + */ + template + static OpType createOneParameterGate(mlir::PatternRewriter& rewriter, + mlir::Location location, + qc::fp parameter, mlir::ValueRange inQubits) { + auto parameterValue = rewriter.create( + location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); + + return rewriter.create( + location, inQubits.getType(), mlir::TypeRange{}, mlir::TypeRange{}, + mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + mlir::ValueRange{parameterValue}, inQubits, mlir::ValueRange{}, + mlir::ValueRange{}); + } + + + struct QubitGateSequence { + struct Gate { + qc::OpType type{qc::I}; + std::vector parameter; + std::vector qubit_id = {0}; + }; + std::vector gates; + qc::fp globalPhase; + }; + using OneQubitGateSequence = QubitGateSequence; + using TwoQubitGateSequence = QubitGateSequence; + + static void applySeries(mlir::PatternRewriter& rewriter, const llvm::SmallVector& series, const TwoQubitGateSequence& sequence) { + if (sequence.globalPhase != 0.0) { + createOneParameterGate(rewriter, series[0]->getLoc(), sequence.globalPhase, {}); } } @@ -154,6 +214,21 @@ struct GateDecompositionPattern final return (r < 0.0) ? r + std::abs(b) : r; } + // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp + // to -π + static qc::fp + mod2pi(qc::fp angle, + qc::fp angleZeroEpsilon = std::numeric_limits::epsilon()) { + // remEuclid() isn't exactly the same as Python's % operator, but + // because the RHS here is a constant and positive it is effectively + // equivalent for this case + auto wrapped = remEuclid(angle + qc::PI, 2. * qc::PI) - qc::PI; + if (std::abs(wrapped - qc::PI) < angleZeroEpsilon) { + return -qc::PI; + } + return wrapped; + } + static matrix2x2 dot(const matrix2x2& lhs, const matrix2x2& rhs) { return lhs; } @@ -164,6 +239,13 @@ struct GateDecompositionPattern final static matrix2x2 transpose(const matrix2x2& x) { return x; } static matrix4x4 transpose(const matrix4x4& x) { return x; } + static matrix2x2 transpose_conjugate(const matrix2x2& matrix) { + auto result = transpose(matrix); + llvm::transform(result, result.begin(), + [](auto&& x) { return std::conj(x); }); + return result; + }; + static qfp determinant(const matrix2x2& x) { return 0.0; }; static qfp determinant(const matrix4x4& x) { return 0.0; }; @@ -308,7 +390,45 @@ struct GateDecompositionPattern final throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"}; } - static std::array params_xyx_inner(const matrix2x2& matrix) {} + static std::array params_zyz_inner(const matrix2x2& matrix) { + auto getIndex = [](auto x, auto y) { return (y * 2) + x; }; + auto determinant = [getIndex](auto&& matrix) { + return (matrix.at(getIndex(0, 0)) * matrix.at(getIndex(1, 1))) - + (matrix.at(getIndex(1, 0)) * matrix.at(getIndex(0, 1))); + }; + + auto detArg = std::arg(determinant(matrix)); + auto phase = 0.5 * detArg; + auto theta = 2. * std::atan2(std::abs(matrix.at(getIndex(1, 0))), + std::abs(matrix.at(getIndex(0, 0)))); + auto ang1 = std::arg(matrix.at(getIndex(1, 1))); + auto ang2 = std::arg(matrix.at(getIndex(1, 0))); + auto phi = ang1 + ang2 - detArg; + auto lam = ang1 - ang2; + return {theta, phi, lam, phase}; + } + + static std::array params_xyx_inner(const matrix2x2& matrix) { + auto mat_zyz = std::array{ + 0.5 * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + 0.5 * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + 0.5 * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + 0.5 * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + }; + auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); + auto new_phi = mod2pi(phi + qc::PI, 0.); + auto new_lam = mod2pi(lam + qc::PI, 0.); + return { + theta, + new_phi, + new_lam, + phase + (new_phi + new_lam - phi - lam) / 2., + }; + } static constexpr std::complex C_ZERO{0., 0.}; static constexpr std::complex C_ONE{1., 0.}; @@ -316,6 +436,13 @@ struct GateDecompositionPattern final static constexpr std::complex IM{0., 1.}; static constexpr std::complex M_IM{0., -1.}; + static constexpr std::array, 4> IPZ = {IM, C_ZERO, + C_ZERO, M_IM}; + static constexpr std::array, 4> IPY = {C_ZERO, C_ONE, + C_M_ONE, C_ZERO}; + static constexpr std::array, 4> IPX = {C_ZERO, IM, IM, + C_ZERO}; + struct TwoQubitWeylDecomposition { qc::fp a; qc::fp b; @@ -336,13 +463,6 @@ struct GateDecompositionPattern final std::optional fidelity, std::optional _specialization) { - constexpr std::array, 4> IPZ = {IM, C_ZERO, C_ZERO, - M_IM}; - constexpr std::array, 4> IPY = {C_ZERO, C_ONE, - C_M_ONE, C_ZERO}; - constexpr std::array, 4> IPX = {C_ZERO, IM, IM, - C_ZERO}; - auto& u = unitary_matrix; auto det_u = determinant(u); auto det_pow = std::pow(det_u, static_cast(-0.25)); @@ -837,6 +957,7 @@ struct GateDecompositionPattern final if (specialization == Specialization::General) { return general; } + throw std::logic_error{"Unknown specialization"}; }; TwoQubitWeylDecomposition specialized = get_specialized_decomposition(); @@ -877,7 +998,7 @@ struct GateDecompositionPattern final struct TwoQubitBasisDecomposer { qc::OpType gate; - std::optional gate_params; + std::vector gate_params; qc::fp basis_fidelity; EulerBasis euler_basis; std::optional pulse_optimize; @@ -903,17 +1024,166 @@ struct GateDecompositionPattern final matrix2x2 q2l; matrix2x2 q2r; - struct QubitGateSequence { - struct Gate { - qc::OpType type{qc::I}; - std::optional parameter; - std::vector qubit_id = {0}; + TwoQubitBasisDecomposer + new_inner(qc::OpType gate = qc::X, // CX + const std::vector& gate_params = {}, + matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, + 1, 0}, // CX matrix + qc::fp basis_fidelity = 1.0, + EulerBasis euler_basis = EulerBasis::ZYZ, + std::optional pulse_optimize = std::nullopt) { + auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, + auto&& max_relative) { + // Handle same infinities + if (lhs == rhs) { + return true; + } + + // Handle remaining infinities + if (std::isinf(lhs) || std::isinf(rhs)) { + return false; + } + + auto abs_diff = std::abs(lhs - rhs); + + // For when the numbers are really close together + if (abs_diff <= epsilon) { + return true; + } + + auto abs_lhs = std::abs(lhs); + auto abs_rhs = std::abs(rhs); + if (abs_rhs > abs_lhs) { + return abs_diff <= abs_rhs * max_relative; + } + return abs_diff <= abs_lhs * max_relative; }; - std::vector gates; - qc::fp globalPhase; - }; - using OneQubitGateSequence = QubitGateSequence; - using TwoQubitGateSequence = QubitGateSequence; + constexpr auto FRAC_1_SQRT_2 = 0.707106781186547524400844362104849039; + constexpr auto K12R_ARR = std::array{ + qfp(0., FRAC_1_SQRT_2), + qfp(FRAC_1_SQRT_2, 0.), + qfp(-FRAC_1_SQRT_2, 0.), + qfp(0., -FRAC_1_SQRT_2), + }; + constexpr auto K12L_ARR = std::array{ + qfp(0.5, 0.5), + qfp(0.5, 0.5), + qfp(-0.5, 0.5), + qfp(0.5, -0.5), + }; + + auto basis_decomposer = TwoQubitWeylDecomposition::new_inner( + gate_matrix, DEFAULT_FIDELITY, std::nullopt); + auto super_controlled = + relative_eq(basis_decomposer.a, qc::PI_4, + std::numeric_limits::epsilon(), 1e-09) && + relative_eq(basis_decomposer.c, 0.0, + std::numeric_limits::epsilon(), 1e-09); + + // Create some useful matrices U1, U2, U3 are equivalent to the basis, + // expand as Ui = Ki1.Ubasis.Ki2 + auto b = basis_decomposer.b; + auto temp = qfp(0.5, -0.5); + auto k11l = std::array{ + temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b)), + temp * (M_IM * std::exp(qfp(0., b))), temp * -(std::exp(qfp(0., b)))}; + auto k11r = std::array{FRAC_1_SQRT_2 * std::exp((IM * qfp(0., -b))), + FRAC_1_SQRT_2 * -std::exp(qfp(0., -b)), + FRAC_1_SQRT_2 * std::exp(qfp(0., b)), + FRAC_1_SQRT_2 * (M_IM * std::exp(qfp(0., b)))}; + auto k32l_k21l = std::array{FRAC_1_SQRT_2 * std::cos(qfp(1., (2. * b))), + FRAC_1_SQRT_2 * (IM * std::sin((2. * b))), + FRAC_1_SQRT_2 * (IM * std::sin(2. * b)), + FRAC_1_SQRT_2 * qfp(1., -std::cos(2. * b))}; + temp = qfp(0.5, 0.5); + auto k21r = std::array{ + temp * (M_IM * std::exp(qfp(0., -2. * b))), + temp * std::exp(qfp(0., -2. * b)), + temp * (IM * std::exp(qfp(0., 2. * b))), + temp * std::exp(qfp(0., 2. * b)), + }; + constexpr auto K22L_ARR = std::array{ + qfp(FRAC_1_SQRT_2, 0.), + qfp(-FRAC_1_SQRT_2, 0.), + qfp(FRAC_1_SQRT_2, 0.), + qfp(FRAC_1_SQRT_2, 0.), + }; + constexpr auto K22R_ARR = std::array{C_ZERO, C_ONE, C_M_ONE, C_ZERO}; + auto k31l = std::array{ + FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), + FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), + FRAC_1_SQRT_2 * -std::exp(qfp(0., b)), + FRAC_1_SQRT_2 * std::exp(qfp(0., b)), + }; + auto k31r = std::array{ + IM * std::exp(qfp(0., b)), + C_ZERO, + C_ZERO, + M_IM * std::exp(qfp(0., -b)), + }; + temp = qfp(0.5, 0.5); + auto k32r = std::array{ + temp * std::exp(qfp(0., b)), + temp * -std::exp(qfp(0., -b)), + temp * (M_IM * std::exp(qfp(0., b))), + temp * (M_IM * std::exp(qfp(0., -b))), + }; + auto k1ld = transpose_conjugate(basis_decomposer.K1l); + auto k1rd = transpose_conjugate(basis_decomposer.K1r); + auto k2ld = transpose_conjugate(basis_decomposer.K2l); + auto k2rd = transpose_conjugate(basis_decomposer.K2r); + // Pre-build the fixed parts of the matrices used in 3-part decomposition + auto u0l = dot(k31l, k1ld); + auto u0r = dot(k31r, k1rd); + auto u1l = dot(dot(k2ld, k32l_k21l), k1ld); + auto u1ra = dot(k2rd, k32r); + auto u1rb = dot(k21r, k1rd); + auto u2la = dot(k2ld, K22L_ARR); + auto u2lb = dot(k11l, k1ld); + auto u2ra = dot(k2rd, K22R_ARR); + auto u2rb = dot(k11r, k1rd); + auto u3l = dot(k2ld, K12L_ARR); + auto u3r = dot(k2rd, K12R_ARR); + // Pre-build the fixed parts of the matrices used in the 2-part + // decomposition + auto q0l = dot(transpose_conjugate(K12L_ARR), k1ld); + auto q0r = dot(dot(transpose_conjugate(K12R_ARR), IPZ), k1rd); + auto q1la = dot(k2ld, transpose_conjugate(k11l)); + auto q1lb = dot(k11l, k1ld); + auto q1ra = dot(dot(k2rd, IPZ), transpose_conjugate(k11r)); + auto q1rb = dot(k11r, k1rd); + auto q2l = dot(k2ld, K12L_ARR); + auto q2r = dot(k2rd, K12R_ARR); + + return TwoQubitBasisDecomposer{ + gate, + gate_params, + basis_fidelity, + euler_basis, + pulse_optimize, + basis_decomposer, + super_controlled, + u0l, + u0r, + u1l, + u1ra, + u1rb, + u2la, + u2lb, + u2ra, + u2rb, + u3l, + u3r, + q0l, + q0r, + q1la, + q1lb, + q1ra, + q1rb, + q2l, + q2r, + }; + } std::optional twoQubitDecompose(const matrix4x4& unitaryMatrix, @@ -971,6 +1241,7 @@ struct GateDecompositionPattern final decomp, target_1q_basis_list, 0, {}, true, std::nullopt); euler_decompositions.push_back(euler_decomp); } + TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q gate // We might overallocate a bit if the euler basis is different but // the worst case is just 16 extra elements with just a String and 2 @@ -978,7 +1249,6 @@ struct GateDecompositionPattern final // aren't long lived and are just used to create a QuantumCircuit or // DAGCircuit when we return to Python space. constexpr auto TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY = 21; - TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; gates.gates.reserve(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); gates.globalPhase -= best_nbasis * basis_decomposer.global_phase; if (best_nbasis == 2) { @@ -1022,13 +1292,6 @@ struct GateDecompositionPattern final [[nodiscard]] std::vector decomp1_inner(const TwoQubitWeylDecomposition& target) const { - auto transpose_conjugate = [](auto&& matrix) { - auto result = transpose(matrix); - llvm::transform(result, result.begin(), - [](auto&& x) { return std::conj(x); }); - return result; - }; - // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) return { @@ -1081,7 +1344,7 @@ struct GateDecompositionPattern final return std::nullopt; } - if (gate != "cx") { + if (gate != qc::X) { // CX if (pulse_optimize.has_value()) { throw std::runtime_error{ "pulse_optimizer currently only works with CNOT entangling gate"}; @@ -1108,7 +1371,7 @@ struct GateDecompositionPattern final return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; } if (gate.type == qc::RZ) { - return rz_matrix(gate.parameter.value()); + return rz_matrix(gate.parameter.at(0)); } if (gate.type == qc::X) { return {0, 1, 1, 0}; @@ -1190,11 +1453,11 @@ struct GateDecompositionPattern final {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); gates.gates.push_back({.type = qc::RZ, - .parameter = euler_q0[1][1] - qc::PI, + .parameter = {euler_q0[1][1] - qc::PI}, .qubit_id = {0}}); gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); gates.gates.push_back( - {.type = qc::RZ, .parameter = euler_q1[1][1], .qubit_id = {1}}); + {.type = qc::RZ, .parameter = {euler_q1[1][1]}, .qubit_id = {1}}); gates.globalPhase += qc::PI_2; gates.gates.push_back( {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? @@ -1438,8 +1701,8 @@ struct GateDecompositionPattern final bestCircuit = circuit; minError = error; } - return bestCircuit; } + return bestCircuit; } // TODO: copied+adapted from single-qubit decomposition @@ -1465,23 +1728,6 @@ struct GateDecompositionPattern final angleZeroEpsilon = -1.0; } - auto remEuclid = [](qc::fp a, qc::fp b) { - auto r = std::fmod(a, b); - return (r < 0.0) ? r + std::abs(b) : r; - }; - // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp - // to -π - auto mod2pi = [&](qc::fp angle) -> qc::fp { - // remEuclid() isn't exactly the same as Python's % operator, but - // because the RHS here is a constant and positive it is effectively - // equivalent for this case - auto wrapped = remEuclid(angle + qc::PI, 2. * qc::PI) - qc::PI; - if (std::abs(wrapped - qc::PI) < angleZeroEpsilon) { - return -qc::PI; - } - return wrapped; - }; - qc::fp globalPhase = phase - ((phi + lambda) / 2.); std::vector gates; @@ -1489,7 +1735,7 @@ struct GateDecompositionPattern final lambda += phi; lambda = mod2pi(lambda); if (std::abs(lambda) > angleZeroEpsilon) { - gates.push_back({kGate, lambda}); + gates.push_back({kGate, {lambda}}); globalPhase += lambda / 2.0; } return {gates, globalPhase}; @@ -1509,13 +1755,13 @@ struct GateDecompositionPattern final lambda = mod2pi(lambda); if (std::abs(lambda) > angleZeroEpsilon) { globalPhase += lambda / 2.0; - gates.push_back({kGate, lambda}); + gates.push_back({kGate, {lambda}}); } - gates.push_back({aGate, theta}); + gates.push_back({aGate, {theta}}); phi = mod2pi(phi); if (std::abs(phi) > angleZeroEpsilon) { globalPhase += phi / 2.0; - gates.push_back({kGate, phi}); + gates.push_back({kGate, {phi}}); } return {gates, globalPhase}; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index f4976b44e..66936284c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -21,6 +21,14 @@ namespace mqt::ir::opt::helpers { +inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { + std::array, 16> result; + for (std::size_t i = 0; i < result.size(); ++i) { + result[i] = matrix[i / 4][i % 4]; + } + return result; +} + std::optional mlirValueToFp(mlir::Value value); template @@ -127,7 +135,6 @@ inline std::optional mlirValueToFp(mlir::Value value) { auto&& outQubits = op.getOutQubits(); bool isSingleQubitOp = inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled(); - assert(isSingleQubitOp == qc::isSingleQubitGate(getQcType(op))); return isSingleQubitOp; } @@ -143,7 +150,6 @@ inline std::optional mlirValueToFp(mlir::Value value) { auto outQubitSize = outQubits.size() + outPosCtrlQubits.size() + outNegCtrlQubits.size(); bool isTwoQubitOp = inQubitSize == 2 && outQubitSize == 2; - assert(isTwoQubitOp == qc::isTwoQubitGate(getQcType(op))); return isTwoQubitOp; } @@ -154,6 +160,9 @@ getUnitaryMatrix(UnitaryInterface op) { if (isTwoQubitOperation(op)) { return dd::opToTwoQubitGateMatrix(type, parameters); + } else if (isSingleQubitOperation(op)) { + auto matrix = dd::opToSingleQubitGateMatrix(type, parameters); + // TODO } return std::nullopt; } @@ -164,6 +173,20 @@ getUnitaryMatrix(UnitaryInterface op) { factor * matrix.at(3)}; } +[[nodiscard]] inline auto +multiply(const std::array, 16>& lhs, + const std::array, 16>& rhs) { + std::array, 16> result; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + for (int k = 0; k < 4; k++) { + result[i * 4 + j] += lhs[i * 4 + k] * rhs[k * 4 + j]; + } + } + } + return result; +} + [[nodiscard]] inline dd::TwoQubitGateMatrix kroneckerProduct(dd::GateMatrix lhs, dd::GateMatrix rhs) { return {multiply(lhs.at(0), rhs), multiply(lhs.at(1), rhs), From fe81855b9c522ec0df80b55110a5bf5b9f6d94b4 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 15 Oct 2025 20:30:32 +0200 Subject: [PATCH 011/100] tmp --- .../MQTOpt/Transforms/GateDecomposition.cpp | 6 +- .../Transforms/GateDecompositionPattern.cpp | 391 ++++++++++++------ mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 102 +++-- 3 files changed, 353 insertions(+), 146 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index a612aca95..a5c039568 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -35,8 +35,12 @@ struct GateDecomposition final mlir::RewritePatternSet patterns(ctx); populateGateDecompositionPatterns(patterns); + // Configure greedy driver + mlir::GreedyRewriteConfig config; + config.setUseTopDownTraversal(true); + // Apply patterns in an iterative and greedy manner. - if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { + if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns), config))) { signalPassFailure(); } } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 7f2ebb541..1afe8d2e6 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -36,9 +36,6 @@ struct GateDecompositionPattern final explicit GateDecompositionPattern(mlir::MLIRContext* context) : OpInterfaceRewritePattern(context) {} - dd::TwoQubitGateMatrix twoQubitIdentity = { - {{1, 0, 0, 0}, {0, 0, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}}}; - mlir::LogicalResult matchAndRewrite(UnitaryInterface op, mlir::PatternRewriter& rewriter) const override { @@ -49,18 +46,17 @@ struct GateDecompositionPattern final return mlir::failure(); } - matrix4x4 unitaryMatrix = - helpers::flatten(dd::opToTwoQubitGateMatrix(qc::I)); + matrix4x4 unitaryMatrix = kroneckerProduct(identityGate, identityGate); for (auto&& gate : series) { - if (auto gateMatrix = helpers::getUnitaryMatrix(gate)) { - unitaryMatrix = - helpers::multiply(unitaryMatrix, helpers::flatten(*gateMatrix)); - } + auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate), + .parameter = {/*TODO*/}, + .qubit_id = {0, 1}}); + unitaryMatrix = helpers::multiply(unitaryMatrix, gateMatrix); } - TwoQubitBasisDecomposer decomposer; - auto sequence = decomposer.twoQubitDecompose(unitaryMatrix, DEFAULT_FIDELITY, true, - std::nullopt); + auto decomposer = TwoQubitBasisDecomposer::new_inner(); + auto sequence = decomposer.twoQubitDecompose( + unitaryMatrix, DEFAULT_FIDELITY, true, std::nullopt); if (!sequence) { return mlir::failure(); } @@ -84,42 +80,50 @@ struct GateDecompositionPattern final } result.push_back(op); - while (true) { - for (auto&& user : llvm::concat(qubits[0].getUsers(), - qubits[1].getUsers())) { - auto userUnitary = mlir::dyn_cast(user); - if (!userUnitary) { - return result; - } + auto findNextInSeries = [&](mlir::Operation* user) { + auto userUnitary = mlir::dyn_cast(user); + if (!userUnitary) { + return false; + } - if (helpers::isSingleQubitOperation(userUnitary)) { - auto&& operand = userUnitary->getOperand(0); - auto* it = llvm::find(qubits, operand); - if (it == qubits.end()) { - throw std::logic_error{"Operand of single-qubit op and user of " - "qubit is not in qubits"}; - } - *it = userUnitary->getResult(0); - - result.push_back(userUnitary); - } else if (helpers::isTwoQubitOperation(userUnitary)) { - auto&& firstOperand = userUnitary->getOperand(0); - auto&& secondOperand = userUnitary->getOperand(1); - auto* firstQubitIt = llvm::find(qubits, firstOperand); - auto* secondQubitIt = llvm::find(qubits, secondOperand); - if (firstQubitIt == qubits.end() || secondQubitIt == qubits.end()) { - return result; - } - *firstQubitIt = userUnitary->getResult(0); - *secondQubitIt = userUnitary->getResult(1); + if (helpers::isSingleQubitOperation(userUnitary)) { + auto&& operand = userUnitary->getOperand(0); + auto* it = llvm::find(qubits, operand); + if (it == qubits.end()) { + throw std::logic_error{"Operand of single-qubit op and user of " + "qubit is not in qubits"}; + } + *it = userUnitary->getResult(0); - result.push_back(userUnitary); - } else { - return result; + result.push_back(userUnitary); + return true; + } + if (helpers::isTwoQubitOperation(userUnitary)) { + auto&& firstOperand = userUnitary->getOperand(0); + auto&& secondOperand = userUnitary->getOperand(1); + auto* firstQubitIt = llvm::find(qubits, firstOperand); + auto* secondQubitIt = llvm::find(qubits, secondOperand); + if (firstQubitIt == qubits.end() || secondQubitIt == qubits.end()) { + return false; } + *firstQubitIt = userUnitary->getResult(0); + *secondQubitIt = userUnitary->getResult(1); + + result.push_back(userUnitary); + return true; + } + return false; + }; + + while (true) { + assert(qubits[0].hasOneUse()); + assert(qubits[1].hasOneUse()); + bool isSeriesOngoing = findNextInSeries(*qubits[0].getUsers().begin()) || + findNextInSeries(*qubits[1].getUsers().begin()); + if (!isSeriesOngoing) { + return result; } } - return result; } /** @@ -133,7 +137,8 @@ struct GateDecompositionPattern final template static OpType createOneParameterGate(mlir::PatternRewriter& rewriter, mlir::Location location, - qc::fp parameter, mlir::ValueRange inQubits) { + qc::fp parameter, + mlir::ValueRange inQubits) { auto parameterValue = rewriter.create( location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); @@ -144,22 +149,24 @@ struct GateDecompositionPattern final mlir::ValueRange{}); } - - struct QubitGateSequence { - struct Gate { - qc::OpType type{qc::I}; - std::vector parameter; - std::vector qubit_id = {0}; - }; - std::vector gates; - qc::fp globalPhase; + struct QubitGateSequence { + struct Gate { + qc::OpType type{qc::I}; + std::vector parameter; + std::vector qubit_id = {0}; }; - using OneQubitGateSequence = QubitGateSequence; - using TwoQubitGateSequence = QubitGateSequence; + std::vector gates; + qc::fp globalPhase; + }; + using OneQubitGateSequence = QubitGateSequence; + using TwoQubitGateSequence = QubitGateSequence; - static void applySeries(mlir::PatternRewriter& rewriter, const llvm::SmallVector& series, const TwoQubitGateSequence& sequence) { + static void applySeries(mlir::PatternRewriter& rewriter, + const llvm::SmallVector& series, + const TwoQubitGateSequence& sequence) { if (sequence.globalPhase != 0.0) { - createOneParameterGate(rewriter, series[0]->getLoc(), sequence.globalPhase, {}); + createOneParameterGate(rewriter, series[0]->getLoc(), + sequence.globalPhase, {}); } } @@ -230,14 +237,25 @@ struct GateDecompositionPattern final } static matrix2x2 dot(const matrix2x2& lhs, const matrix2x2& rhs) { - return lhs; + return helpers::multiply(lhs, rhs); } static matrix4x4 dot(const matrix4x4& lhs, const matrix4x4& rhs) { - return lhs; + return helpers::multiply(lhs, rhs); } - static matrix2x2 transpose(const matrix2x2& x) { return x; } - static matrix4x4 transpose(const matrix4x4& x) { return x; } + static matrix2x2 transpose(const matrix2x2& matrix) { + return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], + matrix[1 * 2 + 1]}; + } + static matrix4x4 transpose(const matrix4x4& x) { + matrix4x4 result; + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 4; ++j) { + result[j * 4 + i] = x[i * 4 + j]; + } + } + return result; + } static matrix2x2 transpose_conjugate(const matrix2x2& matrix) { auto result = transpose(matrix); @@ -246,8 +264,60 @@ struct GateDecompositionPattern final return result; }; - static qfp determinant(const matrix2x2& x) { return 0.0; }; - static qfp determinant(const matrix4x4& x) { return 0.0; }; + static qfp determinant(const matrix2x2& mat) { + return mat[0] * mat[3] - mat[1] * mat[2]; + } + + static qfp determinant(const std::array& mat) { + return mat[0] * (mat[4] * mat[8] - mat[5] * mat[7]) - + mat[1] * (mat[3] * mat[8] - mat[5] * mat[6]) + + mat[2] * (mat[3] * mat[7] - mat[4] * mat[6]); + } + + static std::array get3x3Submatrix(const matrix4x4& mat, + int rowToBeRemoved, + int columnToBeRemoved) { + std::array, 9> result; + int subIndex = 0; + for (int i = 0; i < 4; ++i) { + if (i != rowToBeRemoved) { + for (int j = 0; j < 4; ++j) { + if (j != columnToBeRemoved) { + result[subIndex++] = mat[i * 4 + j]; + } + } + } + } + return result; + } + + static qfp determinant(const matrix4x4& mat) { + auto [l, u, rowPermutations] = helpers::LUdecomposition(mat); + auto det = C_ONE; + for (int i = 0; i < 4; ++i) { + det *= l[i * 4 + i]; + } + + if (rowPermutations % 2 != 0) { + det = -det; + } + return det; + + // auto det = -C_ZERO; + // for (int column = 0; column < 4; ++column) { + // auto submatrix = get3x3Submatrix(mat, 0, column); + // auto subDet = determinant(submatrix); + // auto tmp = mat[0 * 4 + column] * subDet; + // if (column % 2 == 0 && + // tmp != + // C_ZERO) { // TODO: better way to get negative 0.0 in determinant? + // det += tmp; + // } else if (tmp != -C_ZERO) { + // det -= tmp; + // } + // } + // return det; + } static matrix2x2 multiply(qfp factor, matrix2x2 matrix) { llvm::transform(matrix, matrix.begin(), @@ -270,15 +340,85 @@ struct GateDecompositionPattern final second_quadrant[0 * 2 + 0], second_quadrant[0 * 2 + 1], first_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], second_quadrant[1 * 2 + 0], second_quadrant[1 * 2 + 1], - third_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], + third_quadrant[0 * 2 + 0], third_quadrant[0 * 2 + 1], fourth_quadrant[0 * 2 + 0], fourth_quadrant[0 * 2 + 1], - third_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], + third_quadrant[1 * 2 + 0], third_quadrant[1 * 2 + 1], fourth_quadrant[1 * 2 + 0], fourth_quadrant[1 * 2 + 1], }; } + // Helper function to perform QR decomposition (simplified) + static void qrDecomposition(std::array& A, + std::array& Q, + std::array& R) { + // QR decomposition is simplified for this 4x4 case + for (size_t i = 0; i < 4; ++i) { + qc::fp norm = 0.0; + for (size_t j = 0; j < 4; ++j) { + norm += A[j * 4 + i] * A[j * 4 + i]; + } + norm = std::sqrt(norm); + + // Normalize the column + for (size_t j = 0; j < 4; ++j) { + Q[j * 4 + i] = A[j * 4 + i] / norm; + } + + // Compute the R matrix + for (size_t j = i; j < 4; ++j) { + qc::fp dot = 0.0; + for (size_t k = 0; k < 4; ++k) { + dot += Q[k * 4 + i] * A[k * 4 + j]; + } + R[i * 4 + j] = dot; + } + + // Subtract to make A orthogonal + for (size_t j = 0; j < 4; ++j) { + for (size_t k = i; k < 4; ++k) { + A[j * 4 + k] -= Q[j * 4 + i] * R[i * 4 + k]; + } + } + } + } + + // Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) + static std::array // eigenvectors (4x4) + self_adjoint_evd(std::array& A, // input symmetric matrix (4x4) + std::array& s // eigenvalues + ) { + // Step 1: Zero out the upper triangle (we are only interested in the lower + // half) + for (size_t i = 0; i < 4; ++i) { + for (size_t j = i + 1; j < 4; ++j) { + A[i * 4 + j] = 0; // Set the upper triangle to zero + } + } + + // Step 2: Perform QR decomposition + std::array Q, R; + for (int iter = 0; iter < 1000; + ++iter) { // Arbitrary number of iterations for convergence + qrDecomposition(A, Q, R); + A = helpers::multiply(R, Q); // Update A = R * Q + } + + // Step 3: Extract eigenvalues (diagonal of the matrix) + for (size_t i = 0; i < 4; ++i) { + s[i] = A[i * 4 + i]; // Eigenvalues are the diagonal elements + } + + return Q; + } + // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen - static matrix4x4 self_adjoint_eigen_lower(const matrix4x4& x) { return x; } + static std::array + self_adjoint_eigen_lower(std::array A) { + std::array S; + auto U = self_adjoint_evd(A, S); + + return U; + } static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { @@ -299,9 +439,7 @@ struct GateDecompositionPattern final llvm::transform(r, r.begin(), [&](auto&& x) { return x / std::sqrt(det_r); }); // transpose with complex conjugate of each element - matrix2x2 r_t_conj; - llvm::transform(transpose(r), r_t_conj.begin(), - [](auto&& x) { return std::conj(x); }); + matrix2x2 r_t_conj = transpose_conjugate(r); auto temp = kroneckerProduct(identityGate, r_t_conj); temp = dot(special_unitary, temp); @@ -443,6 +581,48 @@ struct GateDecompositionPattern final static constexpr std::array, 4> IPX = {C_ZERO, IM, IM, C_ZERO}; + static matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { + if (gate.type == qc::SX) { + return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; + } + if (gate.type == qc::RZ) { + return rz_matrix(gate.parameter.at(0)); + } + if (gate.type == qc::X) { + return {0, 1, 1, 0}; + } + throw std::invalid_argument{ + "unsupported gate type for single qubit matrix"}; + } + + static matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { + if (gate.qubit_id.empty()) { + return kroneckerProduct(identityGate, identityGate); + } + if (gate.qubit_id.size() == 1) { + if (gate.qubit_id[0] == 0) { + return kroneckerProduct(identityGate, getSingleQubitMatrix(gate)); + } + if (gate.qubit_id[0] == 1) { + return kroneckerProduct(getSingleQubitMatrix(gate), identityGate); + } + throw std::logic_error{"Invalid qubit ID in compute_unitary"}; + } + if (gate.qubit_id.size() == 2) { + if (gate.type == qc::X) { + // controlled X (CX) + if (gate.qubit_id == std::vector{0, 1}) { + return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; + } + if (gate.qubit_id == std::vector{1, 0}) { + return {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; + } + } + throw std::invalid_argument{"unsupported gate type for two qubit matrix"}; + } + throw std::logic_error{"Invalid number of qubit IDs in compute_unitary"}; + } + struct TwoQubitWeylDecomposition { qc::fp a; qc::fp b; @@ -459,18 +639,24 @@ struct GateDecompositionPattern final matrix4x4 unitary_matrix; static TwoQubitWeylDecomposition - new_inner(matrix4x4 unitary_matrix, - - std::optional fidelity, + new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { auto& u = unitary_matrix; auto det_u = determinant(u); - auto det_pow = std::pow(det_u, static_cast(-0.25)); + llvm::errs() << "DET_U: " << det_u.real() << 'i' << det_u.imag() << '\n'; + // auto cpow = [](auto&& x, auto&& y) { // TODO: remove + // auto mag = std::abs(x); + // auto phase = std::arg(x); + // return std::polar(std::pow(mag, y), phase * y); }; + auto det_pow = std::pow(det_u, -qc::fp(0.25)); + llvm::errs() << "DET_POW: " << det_pow.real() << 'i' << det_pow.imag() + << '\n'; llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); auto global_phase = std::arg(det_u) / 4.; auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); auto m2 = dot(transpose(u_p), u_p); auto default_euler_basis = EulerBasis::ZYZ; + helpers::print(m2); // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -489,8 +675,9 @@ struct GateDecompositionPattern final auto state = std::mt19937{2023}; std::normal_distribution dist; auto found = false; - diagonal4x4 d; - matrix4x4 p; + diagonal4x4 d{{C_ZERO}}; + matrix4x4 p{{C_ZERO}}; + for (int i = 0; i < 100; ++i) { qc::fp rand_a; qc::fp rand_b; @@ -506,11 +693,15 @@ struct GateDecompositionPattern final rand_a = dist(state); rand_b = dist(state); } - matrix4x4 m2_real; + std::array m2_real; llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { return rand_a * val.real() + rand_b * val.imag(); }); - matrix4x4 p_inner = self_adjoint_eigen_lower(m2_real); + auto p_inner_real = + std::get<1>(helpers::LUdecomposition(self_adjoint_eigen_lower(m2_real))); + matrix4x4 p_inner; + llvm::transform(p_inner_real, p_inner.begin(), + [](auto&& x) { return qfp(x, 0.0); }); auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); matrix4x4 diag_d{}; // zero initialization diag_d[0 * 4 + 0] = d_inner[0]; @@ -1024,7 +1215,8 @@ struct GateDecompositionPattern final matrix2x2 q2l; matrix2x2 q2r; - TwoQubitBasisDecomposer + public: + static TwoQubitBasisDecomposer new_inner(qc::OpType gate = qc::X, // CX const std::vector& gate_params = {}, matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, @@ -1366,49 +1558,6 @@ struct GateDecompositionPattern final return result; } - matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { - if (gate.type == qc::SX) { - return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; - } - if (gate.type == qc::RZ) { - return rz_matrix(gate.parameter.at(0)); - } - if (gate.type == qc::X) { - return {0, 1, 1, 0}; - } - throw std::invalid_argument{ - "unsupported gate type for single qubit matrix"}; - } - - matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { - if (gate.qubit_id.empty()) { - return kroneckerProduct(identityGate, identityGate); - } else if (gate.qubit_id.size() == 1) { - if (gate.qubit_id[0] == 0) { - return kroneckerProduct(identityGate, getSingleQubitMatrix(gate)); - } else if (gate.qubit_id[0] == 1) { - return kroneckerProduct(getSingleQubitMatrix(gate), identityGate); - } else { - throw std::logic_error{"Invalid qubit ID in compute_unitary"}; - } - } else if (gate.qubit_id.size() == 2) { - if (gate.type == qc::X) { - // controlled X (CX) - if (gate.qubit_id == std::vector{0, 1}) { - return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; - } - if (gate.qubit_id == std::vector{1, 0}) { - return {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; - } - } - throw std::invalid_argument{ - "unsupported gate type for two qubit matrix"}; - } else { - throw std::logic_error{ - "Invalid number of qubit IDs in compute_unitary"}; - } - } - /// /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT /// gates assuming two CNOT gates are needed. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 66936284c..e94951804 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -21,6 +21,20 @@ namespace mqt::ir::opt::helpers { +// TODO: remove + template + void print(std::array, N> matrix) { + int i{}; + for (auto&& a : matrix) { + llvm::errs() << a.real() << 'i' << a.imag() << ' '; + if (++i % 4 == 0) { + llvm::errs() << '\n'; + } + } + llvm::errs() << '\n'; + + } + inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { std::array, 16> result; for (std::size_t i = 0; i < result.size(); ++i) { @@ -153,34 +167,22 @@ inline std::optional mlirValueToFp(mlir::Value value) { return isTwoQubitOp; } -[[nodiscard]] inline std::optional -getUnitaryMatrix(UnitaryInterface op) { - auto type = getQcType(op); - auto parameters = getParameters(op); - - if (isTwoQubitOperation(op)) { - return dd::opToTwoQubitGateMatrix(type, parameters); - } else if (isSingleQubitOperation(op)) { - auto matrix = dd::opToSingleQubitGateMatrix(type, parameters); - // TODO - } - return std::nullopt; -} - [[nodiscard]] inline dd::GateMatrix multiply(std::complex factor, dd::GateMatrix matrix) { return {factor * matrix.at(0), factor * matrix.at(1), factor * matrix.at(2), factor * matrix.at(3)}; } +template [[nodiscard]] inline auto -multiply(const std::array, 16>& lhs, - const std::array, 16>& rhs) { - std::array, 16> result; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - for (int k = 0; k < 4; k++) { - result[i * 4 + j] += lhs[i * 4 + k] * rhs[k * 4 + j]; +multiply(const std::array& lhs, + const std::array& rhs) { + std::array result; + const auto n = std::sqrt(N); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + for (int k = 0; k < n; k++) { + result[i * n + j] += lhs[i * n + k] * rhs[k * n + j]; } } } @@ -193,8 +195,60 @@ kroneckerProduct(dd::GateMatrix lhs, dd::GateMatrix rhs) { multiply(lhs.at(2), rhs), multiply(lhs.at(3), rhs)}; } -[[nodiscard]] inline dd::TwoQubitGateMatrix -multiply(dd::TwoQubitGateMatrix lhs, dd::TwoQubitGateMatrix rhs) { - return {}; +template +[[nodiscard]] inline auto LUdecomposition(std::array matrix) { + std::array L{}; + std::array U{}; + int rowPermutations = 0; + + for (int i = 0; i < 4; i++) { + // --- Partial pivoting: find max row in column i --- + int pivotRow = i; + auto maxVal = matrix[i * 4 + i]; + + for (int r = i + 1; r < 4; r++) { + auto val = matrix[r * 4 + i]; + if (std::abs(val) > std::abs(maxVal)) { + maxVal = val; + pivotRow = r; + } + } + + // --- Swap rows in matrix if needed --- + if (pivotRow != i) { + for (int col = 0; col < 4; ++col) { + std::swap(matrix[i * 4 + col], matrix[pivotRow * 4 + col]); + } + ++rowPermutations; + } + + // --- Compute L matrix (column-wise) --- + for (int j = 0; j < 4; j++) { + if (j < i) + L[j * 4 + i] = 0; + else { + L[j * 4 + i] = matrix[j * 4 + i]; + for (int k = 0; k < i; k++) { + L[j * 4 + i] -= L[j * 4 + k] * U[k * 4 + i]; + } + } + } + + // --- Compute U matrix (row-wise) --- + for (int j = 0; j < 4; j++) { + if (j < i) + U[i * 4 + j] = 0; + else if (j == i) + U[i * 4 + j] = 1; // Diagonal of U is set to 1 + else { + U[i * 4 + j] = matrix[i * 4 + j] / L[i * 4 + i]; + for (int k = 0; k < i; k++) { + U[i * 4 + j] -= (L[i * 4 + k] * U[k * 4 + j]) / L[i * 4 + i]; + } + } + } + } + + return std::make_tuple(L, U, rowPermutations); } } // namespace mqt::ir::opt::helpers From 3df1e5c15e8fe30cc0c61f090bd4343155da9af3 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 16 Oct 2025 20:05:00 +0200 Subject: [PATCH 012/100] switch to long double --- .../Transforms/GateDecompositionPattern.cpp | 213 +++++++++-------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 216 ++++++++++-------- 2 files changed, 229 insertions(+), 200 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 1afe8d2e6..85cf3ac4c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -137,7 +137,7 @@ struct GateDecompositionPattern final template static OpType createOneParameterGate(mlir::PatternRewriter& rewriter, mlir::Location location, - qc::fp parameter, + fp parameter, mlir::ValueRange inQubits) { auto parameterValue = rewriter.create( location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); @@ -152,11 +152,11 @@ struct GateDecompositionPattern final struct QubitGateSequence { struct Gate { qc::OpType type{qc::I}; - std::vector parameter; + std::vector parameter; std::vector qubit_id = {0}; }; std::vector gates; - qc::fp globalPhase; + fp globalPhase; }; using OneQubitGateSequence = QubitGateSequence; using TwoQubitGateSequence = QubitGateSequence; @@ -205,27 +205,21 @@ struct GateDecompositionPattern final ZSX = 11, }; - using qfp = std::complex; - using diagonal4x4 = std::array; - using vector2d = std::vector; - using matrix2x2 = std::array; - using matrix4x4 = std::array; - - static constexpr auto sqrt2 = static_cast(1.4142135623730950488L); + static constexpr auto sqrt2 = static_cast(1.4142135623730950488L); static constexpr matrix2x2 identityGate = {1, 0, 0, 1}; static constexpr matrix2x2 hGate = {1.0 / sqrt2, 1.0 / sqrt2, 1.0 / sqrt2, -1.0 / sqrt2}; - static qc::fp remEuclid(qc::fp a, qc::fp b) { + static fp remEuclid(fp a, fp b) { auto r = std::fmod(a, b); return (r < 0.0) ? r + std::abs(b) : r; } // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp // to -π - static qc::fp - mod2pi(qc::fp angle, - qc::fp angleZeroEpsilon = std::numeric_limits::epsilon()) { + static fp + mod2pi(fp angle, + fp angleZeroEpsilon = std::numeric_limits::epsilon()) { // remEuclid() isn't exactly the same as Python's % operator, but // because the RHS here is a constant and positive it is effectively // equivalent for this case @@ -247,11 +241,11 @@ struct GateDecompositionPattern final return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], matrix[1 * 2 + 1]}; } - static matrix4x4 transpose(const matrix4x4& x) { + static matrix4x4 transpose(const matrix4x4& matrix) { matrix4x4 result; for (size_t i = 0; i < 4; ++i) { for (size_t j = 0; j < 4; ++j) { - result[j * 4 + i] = x[i * 4 + j]; + result[j * 4 + i] = matrix[i * 4 + j]; } } return result; @@ -277,7 +271,7 @@ struct GateDecompositionPattern final static std::array get3x3Submatrix(const matrix4x4& mat, int rowToBeRemoved, int columnToBeRemoved) { - std::array, 9> result; + std::array, 9> result; int subIndex = 0; for (int i = 0; i < 4; ++i) { if (i != rowToBeRemoved) { @@ -310,7 +304,8 @@ struct GateDecompositionPattern final // auto tmp = mat[0 * 4 + column] * subDet; // if (column % 2 == 0 && // tmp != - // C_ZERO) { // TODO: better way to get negative 0.0 in determinant? + // C_ZERO) { // TODO: better way to get negative 0.0 in + // determinant? // det += tmp; // } else if (tmp != -C_ZERO) { // det -= tmp; @@ -348,12 +343,12 @@ struct GateDecompositionPattern final } // Helper function to perform QR decomposition (simplified) - static void qrDecomposition(std::array& A, - std::array& Q, - std::array& R) { + static void qrDecomposition(std::array& A, + std::array& Q, + std::array& R) { // QR decomposition is simplified for this 4x4 case for (size_t i = 0; i < 4; ++i) { - qc::fp norm = 0.0; + fp norm = 0.0; for (size_t j = 0; j < 4; ++j) { norm += A[j * 4 + i] * A[j * 4 + i]; } @@ -366,7 +361,7 @@ struct GateDecompositionPattern final // Compute the R matrix for (size_t j = i; j < 4; ++j) { - qc::fp dot = 0.0; + fp dot = 0.0; for (size_t k = 0; k < 4; ++k) { dot += Q[k * 4 + i] * A[k * 4 + j]; } @@ -383,9 +378,9 @@ struct GateDecompositionPattern final } // Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) - static std::array // eigenvectors (4x4) - self_adjoint_evd(std::array& A, // input symmetric matrix (4x4) - std::array& s // eigenvalues + static std::array // eigenvectors (4x4) + self_adjoint_evd(std::array& A, // input symmetric matrix (4x4) + std::array& s // eigenvalues ) { // Step 1: Zero out the upper triangle (we are only interested in the lower // half) @@ -396,7 +391,7 @@ struct GateDecompositionPattern final } // Step 2: Perform QR decomposition - std::array Q, R; + std::array Q, R; for (int iter = 0; iter < 1000; ++iter) { // Arbitrary number of iterations for convergence qrDecomposition(A, Q, R); @@ -412,15 +407,15 @@ struct GateDecompositionPattern final } // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen - static std::array - self_adjoint_eigen_lower(std::array A) { - std::array S; + static std::array + self_adjoint_eigen_lower(std::array A) { + std::array S; auto U = self_adjoint_evd(A, S); return U; } - static std::tuple + static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { // first quadrant matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], @@ -489,38 +484,38 @@ struct GateDecompositionPattern final throw std::logic_error{"Unknown MagicBasisTransform direction!"}; } - static qc::fp trace_to_fid(const qfp& x) { + static fp trace_to_fid(const qfp& x) { auto x_abs = std::abs(x); return (4.0 + x_abs * x_abs) / 20.0; } - static qc::fp closest_partial_swap(qc::fp a, qc::fp b, qc::fp c) { + static fp closest_partial_swap(fp a, fp b, fp c) { auto m = (a + b + c) / 3.; auto [am, bm, cm] = std::array{a - m, b - m, c - m}; auto [ab, bc, ca] = std::array{a - b, b - c, c - a}; return m + am * bm * cm * (6. + ab * ab + bc * bc + ca * ca) / 18.; } - static matrix2x2 rx_matrix(qc::fp theta) { + static matrix2x2 rx_matrix(fp theta) { auto half_theta = theta / 2.; auto cos = qfp(std::cos(half_theta), 0.); auto isin = qfp(0., -std::sin(half_theta)); return {cos, isin, isin, cos}; } - static matrix2x2 ry_matrix(qc::fp theta) { + static matrix2x2 ry_matrix(fp theta) { auto half_theta = theta / 2.; auto cos = qfp(std::cos(half_theta), 0.); auto sin = qfp(std::sin(half_theta), 0.); return {cos, -sin, sin, cos}; } - static matrix2x2 rz_matrix(qc::fp theta) { + static matrix2x2 rz_matrix(fp theta) { auto ilam2 = qfp(0., 0.5 * theta); return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; } - static std::array angles_from_unitary(const matrix2x2& matrix, + static std::array angles_from_unitary(const matrix2x2& matrix, EulerBasis basis) { if (basis == EulerBasis::XYX) { return params_xyx_inner(matrix); @@ -528,7 +523,7 @@ struct GateDecompositionPattern final throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"}; } - static std::array params_zyz_inner(const matrix2x2& matrix) { + static std::array params_zyz_inner(const matrix2x2& matrix) { auto getIndex = [](auto x, auto y) { return (y * 2) + x; }; auto determinant = [getIndex](auto&& matrix) { return (matrix.at(getIndex(0, 0)) * matrix.at(getIndex(1, 1))) - @@ -546,15 +541,15 @@ struct GateDecompositionPattern final return {theta, phi, lam, phase}; } - static std::array params_xyx_inner(const matrix2x2& matrix) { + static std::array params_xyx_inner(const matrix2x2& matrix) { auto mat_zyz = std::array{ - 0.5 * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + + static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), - 0.5 * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + + static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - 0.5 * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - + static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - 0.5 * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - + static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), }; auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); @@ -568,17 +563,17 @@ struct GateDecompositionPattern final }; } - static constexpr std::complex C_ZERO{0., 0.}; - static constexpr std::complex C_ONE{1., 0.}; - static constexpr std::complex C_M_ONE{-1., 0.}; - static constexpr std::complex IM{0., 1.}; - static constexpr std::complex M_IM{0., -1.}; + static constexpr qfp C_ZERO{0., 0.}; + static constexpr qfp C_ONE{1., 0.}; + static constexpr qfp C_M_ONE{-1., 0.}; + static constexpr qfp IM{0., 1.}; + static constexpr qfp M_IM{0., -1.}; - static constexpr std::array, 4> IPZ = {IM, C_ZERO, + static constexpr std::array IPZ = {IM, C_ZERO, C_ZERO, M_IM}; - static constexpr std::array, 4> IPY = {C_ZERO, C_ONE, + static constexpr std::array IPY = {C_ZERO, C_ONE, C_M_ONE, C_ZERO}; - static constexpr std::array, 4> IPX = {C_ZERO, IM, IM, + static constexpr std::array IPX = {C_ZERO, IM, IM, C_ZERO}; static matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { @@ -624,38 +619,36 @@ struct GateDecompositionPattern final } struct TwoQubitWeylDecomposition { - qc::fp a; - qc::fp b; - qc::fp c; - qc::fp global_phase; - std::array, 4> K1l; - std::array, 4> K2l; - std::array, 4> K1r; - std::array, 4> K2r; + fp a; + fp b; + fp c; + fp global_phase; + std::array, 4> K1l; + std::array, 4> K2l; + std::array, 4> K1r; + std::array, 4> K2r; Specialization specialization; EulerBasis default_euler_basis; - std::optional requested_fidelity; - qc::fp calculated_fidelity; + std::optional requested_fidelity; + fp calculated_fidelity; matrix4x4 unitary_matrix; static TwoQubitWeylDecomposition - new_inner(matrix4x4 unitary_matrix, std::optional fidelity, + new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { auto& u = unitary_matrix; auto det_u = determinant(u); - llvm::errs() << "DET_U: " << det_u.real() << 'i' << det_u.imag() << '\n'; - // auto cpow = [](auto&& x, auto&& y) { // TODO: remove - // auto mag = std::abs(x); - // auto phase = std::arg(x); - // return std::polar(std::pow(mag, y), phase * y); }; - auto det_pow = std::pow(det_u, -qc::fp(0.25)); - llvm::errs() << "DET_POW: " << det_pow.real() << 'i' << det_pow.imag() - << '\n'; + auto det_pow = std::pow(det_u, -fp(0.25)); llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); + llvm::errs() << "===== U =====\n"; + helpers::print(u); auto global_phase = std::arg(det_u) / 4.; auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); + llvm::errs() << "===== U_P =====\n"; + helpers::print(u_p); auto m2 = dot(transpose(u_p), u_p); auto default_euler_basis = EulerBasis::ZYZ; + llvm::errs() << "===== M2 =====\n"; helpers::print(m2); // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D @@ -673,14 +666,14 @@ struct GateDecompositionPattern final // little bit. The fixed seed is to make failures deterministic; the // value is not important. auto state = std::mt19937{2023}; - std::normal_distribution dist; + std::normal_distribution dist; auto found = false; diagonal4x4 d{{C_ZERO}}; matrix4x4 p{{C_ZERO}}; for (int i = 0; i < 100; ++i) { - qc::fp rand_a; - qc::fp rand_b; + fp rand_a; + fp rand_b; // For debugging the algorithm use the same RNG values from the // previous Python implementation for the first random trial. // In most cases this loop only executes a single iteration and @@ -693,12 +686,12 @@ struct GateDecompositionPattern final rand_a = dist(state); rand_b = dist(state); } - std::array m2_real; + std::array m2_real; llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { return rand_a * val.real() + rand_b * val.imag(); }); - auto p_inner_real = - std::get<1>(helpers::LUdecomposition(self_adjoint_eigen_lower(m2_real))); + auto p_inner_real = std::get<1>( + helpers::LUdecomposition(self_adjoint_eigen_lower(m2_real))); matrix4x4 p_inner; llvm::transform(p_inner_real, p_inner.begin(), [](auto&& x) { return qfp(x, 0.0); }); @@ -723,11 +716,11 @@ struct GateDecompositionPattern final throw std::runtime_error{ "TwoQubitWeylDecomposition: failed to diagonalize M2."}; } - std::array d_real; + std::array d_real; llvm::transform(d, d_real.begin(), [](auto&& x) { return -std::arg(x) / 2.0; }); d_real[3] = -d_real[0] - d_real[1] - d_real[2]; - std::array cs; + std::array cs; for (std::size_t i = 0; i < cs.size(); ++i) { assert(i < d_real.size()); cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::PI_2); @@ -829,11 +822,11 @@ struct GateDecompositionPattern final global_phase -= qc::PI_2; } auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); - auto is_close = [&](qc::fp ap, qc::fp bp, qc::fp cp) -> bool { + auto is_close = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; auto dc = c - cp; - auto tr = 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + auto tr = static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); if (fidelity) { return trace_to_fid(tr) >= *fidelity; @@ -1160,12 +1153,12 @@ struct GateDecompositionPattern final b - specialized.b, -c - specialized.c, }; - return 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + return static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); } else { auto [da, db, dc] = std::array{a - specialized.a, b - specialized.b, c - specialized.c}; - return 4. * qfp(std::cos(da) * std::cos(db) * std::cos(dc), + return static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); } }; @@ -1189,8 +1182,8 @@ struct GateDecompositionPattern final struct TwoQubitBasisDecomposer { qc::OpType gate; - std::vector gate_params; - qc::fp basis_fidelity; + std::vector gate_params; + fp basis_fidelity; EulerBasis euler_basis; std::optional pulse_optimize; TwoQubitWeylDecomposition basis_decomposer; @@ -1218,10 +1211,10 @@ struct GateDecompositionPattern final public: static TwoQubitBasisDecomposer new_inner(qc::OpType gate = qc::X, // CX - const std::vector& gate_params = {}, - matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, - 1, 0}, // CX matrix - qc::fp basis_fidelity = 1.0, + const std::vector& gate_params = {}, + matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, + 0, 0}, // CX matrix + fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, std::optional pulse_optimize = std::nullopt) { auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, @@ -1250,7 +1243,7 @@ struct GateDecompositionPattern final } return abs_diff <= abs_lhs * max_relative; }; - constexpr auto FRAC_1_SQRT_2 = 0.707106781186547524400844362104849039; + constexpr auto FRAC_1_SQRT_2 = static_cast(0.707106781186547524400844362104849039); constexpr auto K12R_ARR = std::array{ qfp(0., FRAC_1_SQRT_2), qfp(FRAC_1_SQRT_2, 0.), @@ -1268,9 +1261,9 @@ struct GateDecompositionPattern final gate_matrix, DEFAULT_FIDELITY, std::nullopt); auto super_controlled = relative_eq(basis_decomposer.a, qc::PI_4, - std::numeric_limits::epsilon(), 1e-09) && + std::numeric_limits::epsilon(), 1e-09) && relative_eq(basis_decomposer.c, 0.0, - std::numeric_limits::epsilon(), 1e-09); + std::numeric_limits::epsilon(), 1e-09); // Create some useful matrices U1, U2, U3 are equivalent to the basis, // expand as Ui = Ki1.Ubasis.Ki2 @@ -1379,20 +1372,20 @@ struct GateDecompositionPattern final std::optional twoQubitDecompose(const matrix4x4& unitaryMatrix, - std::optional _basis_fidelity, bool approximate, + std::optional _basis_fidelity, bool approximate, std::optional _num_basis_uses) { auto get_basis_fidelity = [&]() { if (approximate) { return _basis_fidelity.value_or(this->basis_fidelity); } - return 1.0; + return static_cast(1.0); }; - qc::fp basis_fidelity = get_basis_fidelity(); + fp basis_fidelity = get_basis_fidelity(); auto target_decomposed = TwoQubitWeylDecomposition::new_inner( unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); auto traces = this->traces(target_decomposed); auto get_default_nbasis = [&]() { - auto minValue = std::numeric_limits::max(); + auto minValue = std::numeric_limits::max(); auto minIndex = -1; for (std::size_t i = 0; i < traces.size(); ++i) { auto value = trace_to_fid(traces[i]) * std::pow(basis_fidelity, i); @@ -1575,7 +1568,7 @@ struct GateDecompositionPattern final gates.globalPhase -= 2. * basis_decomposer.global_phase; auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { - std::vector> result; + std::vector> result; for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { auto euler_angles = angles_from_unitary(decomposition[i], basis); gates.globalPhase += euler_angles[3]; @@ -1642,7 +1635,7 @@ struct GateDecompositionPattern final // Decompose source unitaries to zxz auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { - std::vector> result; + std::vector> result; for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { auto euler_angles = angles_from_unitary(decomposition[i], basis); gates.globalPhase += euler_angles[3]; @@ -1756,8 +1749,8 @@ struct GateDecompositionPattern final } matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, - qc::fp global_phase) { - auto phase = std::exp(std::complex{0, global_phase}); + fp global_phase) { + auto phase = std::exp(std::complex{0, global_phase}); matrix4x4 matrix; matrix[0 * 4 + 0] = phase; matrix[1 * 4 + 1] = phase; @@ -1789,10 +1782,10 @@ struct GateDecompositionPattern final [[nodiscard]] std::array traces(TwoQubitWeylDecomposition target) const { return { - 4. * + static_cast(4.) * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), std::sin(target.a) * std::sin(target.b) * std::sin(target.c)), - 4. * qfp(std::cos(qc::PI_4 - target.a) * + static_cast(4.) * qfp(std::cos(qc::PI_4 - target.a) * std::cos(basis_decomposer.b - target.b) * std::cos(target.c), std::sin(qc::PI_4 - target.a) * @@ -1806,7 +1799,7 @@ struct GateDecompositionPattern final OneQubitGateSequence generate_circuit(EulerBasis target_basis, const matrix2x2& unitaryMatrix, bool simplify, - std::optional atol) { + std::optional atol) { auto [theta, phi, lamda, phase] = angles_from_unitary(unitaryMatrix, target_basis); @@ -1832,14 +1825,14 @@ struct GateDecompositionPattern final OneQubitGateSequence unitary_to_gate_sequence_inner( matrix2x2 unitary_mat, const std::vector& target_basis_list, std::size_t qubit, - const std::vector>& + const std::vector>& error_map, // TODO: remove error_map+qubit for platform independence - bool simplify, std::optional atol) { + bool simplify, std::optional atol) { auto calculateError = [](const OneQubitGateSequence& sequence) { return sequence.gates.size(); }; - auto minError = std::numeric_limits::max(); + auto minError = std::numeric_limits::max(); OneQubitGateSequence bestCircuit; for (std::size_t i = 0; i < target_basis_list.size(); ++i) { auto& target_basis = target_basis_list[i]; @@ -1869,15 +1862,15 @@ struct GateDecompositionPattern final * indicating that they have been altered from the originals. */ [[nodiscard]] static OneQubitGateSequence - calculateRotationGates(qc::fp theta, qc::fp phi, qc::fp lambda, - qc::fp phase, qc::OpType kGate, qc::OpType aGate, - bool simplify, std::optional atol) { - qc::fp angleZeroEpsilon = atol.value_or(1e-12); + calculateRotationGates(fp theta, fp phi, fp lambda, + fp phase, qc::OpType kGate, qc::OpType aGate, + bool simplify, std::optional atol) { + fp angleZeroEpsilon = atol.value_or(1e-12); if (simplify) { angleZeroEpsilon = -1.0; } - qc::fp globalPhase = phase - ((phi + lambda) / 2.); + fp globalPhase = phase - ((phi + lambda) / 2.); std::vector gates; if (std::abs(theta) < angleZeroEpsilon) { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index e94951804..6880e934f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -19,34 +19,42 @@ #include #include +namespace mqt::ir::opt { + using fp = long double; + using qfp = std::complex; + using diagonal4x4 = std::array; + using vector2d = std::vector; + using matrix2x2 = std::array; + using matrix4x4 = std::array; +} // namespace mqt::ir::opt + namespace mqt::ir::opt::helpers { // TODO: remove - template - void print(std::array, N> matrix) { - int i{}; - for (auto&& a : matrix) { - llvm::errs() << a.real() << 'i' << a.imag() << ' '; - if (++i % 4 == 0) { - llvm::errs() << '\n'; - } +template +void print(std::array, N> matrix) { + int i{}; + for (auto&& a : matrix) { + std::cerr << std::setprecision(50) << a.real() << 'i' << a.imag() << ' '; + if (++i % 4 == 0) { + llvm::errs() << '\n'; } - llvm::errs() << '\n'; - } + llvm::errs() << '\n'; +} inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { - std::array, 16> result; + std::array, 16> result; for (std::size_t i = 0; i < result.size(); ++i) { result[i] = matrix[i / 4][i % 4]; } return result; } -std::optional mlirValueToFp(mlir::Value value); +std::optional mlirValueToFp(mlir::Value value); template -std::optional performMlirFloatBinaryOp(mlir::Value value, Func&& func) { +std::optional performMlirFloatBinaryOp(mlir::Value value, Func&& func) { if (auto op = value.getDefiningOp()) { auto lhs = mlirValueToFp(op.getLhs()); auto rhs = mlirValueToFp(op.getRhs()); @@ -58,7 +66,7 @@ std::optional performMlirFloatBinaryOp(mlir::Value value, Func&& func) { } template -std::optional performMlirFloatUnaryOp(mlir::Value value, Func&& func) { +std::optional performMlirFloatUnaryOp(mlir::Value value, Func&& func) { if (auto op = value.getDefiningOp()) { if (auto operand = mlirValueToFp(op.getOperand())) { return std::invoke(std::forward(func), *operand); @@ -67,7 +75,7 @@ std::optional performMlirFloatUnaryOp(mlir::Value value, Func&& func) { return std::nullopt; } -inline std::optional mlirValueToFp(mlir::Value value) { +inline std::optional mlirValueToFp(mlir::Value value) { if (auto op = value.getDefiningOp()) { if (auto attr = llvm::dyn_cast(op.getValue())) { return attr.getValueAsDouble(); @@ -75,58 +83,58 @@ inline std::optional mlirValueToFp(mlir::Value value) { return std::nullopt; } if (auto result = performMlirFloatUnaryOp( - value, [](qc::fp a) { return -a; })) { + value, [](fp a) { return -a; })) { return result; } if (auto result = performMlirFloatUnaryOp( - value, [](qc::fp a) { return a; })) { + value, [](fp a) { return a; })) { return result; } if (auto result = performMlirFloatUnaryOp( - value, [](qc::fp a) { return a; })) { + value, [](fp a) { return a; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return std::max(a, b); })) { + value, [](fp a, fp b) { return std::max(a, b); })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return std::max(a, b); })) { + value, [](fp a, fp b) { return std::max(a, b); })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return std::min(a, b); })) { + value, [](fp a, fp b) { return std::min(a, b); })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return std::min(a, b); })) { + value, [](fp a, fp b) { return std::min(a, b); })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return std::fmod(a, b); })) { + value, [](fp a, fp b) { return std::fmod(a, b); })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return a + b; })) { + value, [](fp a, fp b) { return a + b; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return a + b; })) { + value, [](fp a, fp b) { return a + b; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return a + b; })) { + value, [](fp a, fp b) { return a + b; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](qc::fp a, qc::fp b) { return a + b; })) { + value, [](fp a, fp b) { return a + b; })) { return result; } return std::nullopt; } -[[nodiscard]] inline std::vector getParameters(UnitaryInterface op) { - std::vector parameters; +[[nodiscard]] inline std::vector getParameters(UnitaryInterface op) { + std::vector parameters; for (auto&& param : op.getParams()) { if (auto value = helpers::mlirValueToFp(param)) { parameters.push_back(*value); @@ -167,18 +175,52 @@ inline std::optional mlirValueToFp(mlir::Value value) { return isTwoQubitOp; } -[[nodiscard]] inline dd::GateMatrix multiply(std::complex factor, - dd::GateMatrix matrix) { +template +T kahanSum(const std::array& values) { + auto sum = T{}; + auto c = T{}; // Compensation for lost low-order bits + + for (auto&& value : values) { + auto y = value - c; // Correct for error so far + auto t = sum + y; // Add the value to the running sum + c = (t - sum) - y; // Recompute the error + sum = t; + } + + return sum; +} + +// Modify the matrix multiplication to use Kahan summation +template +std::array matrixMultiplyWithKahan(const std::array& lhs, + const std::array& rhs) { + std::array result; + + const std::size_t n = std::sqrt(N); + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + std::array terms; + for (size_t k = 0; k < n; ++k) { + terms[k] = lhs[i * n + k] * rhs[k * n + j]; + } + result[i * n + j] = kahanSum(terms); + } + } + return result; +} + +[[nodiscard]] inline matrix2x2 multiply(qfp factor, + const matrix2x2& matrix) { return {factor * matrix.at(0), factor * matrix.at(1), factor * matrix.at(2), factor * matrix.at(3)}; } -template -[[nodiscard]] inline auto -multiply(const std::array& lhs, - const std::array& rhs) { - std::array result; - const auto n = std::sqrt(N); +template +[[nodiscard]] inline auto multiply(const std::array& lhs, + const std::array& rhs) { + // return matrixMultiplyWithKahan(lhs, rhs); + std::array result{{T{}}}; + const int n = std::sqrt(N); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < n; k++) { @@ -189,66 +231,60 @@ multiply(const std::array& lhs, return result; } -[[nodiscard]] inline dd::TwoQubitGateMatrix -kroneckerProduct(dd::GateMatrix lhs, dd::GateMatrix rhs) { - return {multiply(lhs.at(0), rhs), multiply(lhs.at(1), rhs), - multiply(lhs.at(2), rhs), multiply(lhs.at(3), rhs)}; -} - -template +template [[nodiscard]] inline auto LUdecomposition(std::array matrix) { - std::array L{}; - std::array U{}; - int rowPermutations = 0; - - for (int i = 0; i < 4; i++) { - // --- Partial pivoting: find max row in column i --- - int pivotRow = i; - auto maxVal = matrix[i * 4 + i]; - - for (int r = i + 1; r < 4; r++) { - auto val = matrix[r * 4 + i]; - if (std::abs(val) > std::abs(maxVal)) { - maxVal = val; - pivotRow = r; - } - } + std::array L{}; + std::array U{}; + int rowPermutations = 0; - // --- Swap rows in matrix if needed --- - if (pivotRow != i) { - for (int col = 0; col < 4; ++col) { - std::swap(matrix[i * 4 + col], matrix[pivotRow * 4 + col]); - } - ++rowPermutations; - } + for (int i = 0; i < 4; i++) { + // --- Partial pivoting: find max row in column i --- + int pivotRow = i; + auto maxVal = matrix[i * 4 + i]; + + for (int r = i + 1; r < 4; r++) { + auto val = matrix[r * 4 + i]; + if (std::abs(val) > std::abs(maxVal)) { + maxVal = val; + pivotRow = r; + } + } + + // --- Swap rows in matrix if needed --- + if (pivotRow != i) { + for (int col = 0; col < 4; ++col) { + std::swap(matrix[i * 4 + col], matrix[pivotRow * 4 + col]); + } + ++rowPermutations; + } - // --- Compute L matrix (column-wise) --- - for (int j = 0; j < 4; j++) { - if (j < i) - L[j * 4 + i] = 0; - else { - L[j * 4 + i] = matrix[j * 4 + i]; - for (int k = 0; k < i; k++) { - L[j * 4 + i] -= L[j * 4 + k] * U[k * 4 + i]; - } - } + // --- Compute L matrix (column-wise) --- + for (int j = 0; j < 4; j++) { + if (j < i) + L[j * 4 + i] = 0; + else { + L[j * 4 + i] = matrix[j * 4 + i]; + for (int k = 0; k < i; k++) { + L[j * 4 + i] -= L[j * 4 + k] * U[k * 4 + i]; } + } + } - // --- Compute U matrix (row-wise) --- - for (int j = 0; j < 4; j++) { - if (j < i) - U[i * 4 + j] = 0; - else if (j == i) - U[i * 4 + j] = 1; // Diagonal of U is set to 1 - else { - U[i * 4 + j] = matrix[i * 4 + j] / L[i * 4 + i]; - for (int k = 0; k < i; k++) { - U[i * 4 + j] -= (L[i * 4 + k] * U[k * 4 + j]) / L[i * 4 + i]; - } - } + // --- Compute U matrix (row-wise) --- + for (int j = 0; j < 4; j++) { + if (j < i) + U[i * 4 + j] = 0; + else if (j == i) + U[i * 4 + j] = 1; // Diagonal of U is set to 1 + else { + U[i * 4 + j] = matrix[i * 4 + j] / L[i * 4 + i]; + for (int k = 0; k < i; k++) { + U[i * 4 + j] -= (L[i * 4 + k] * U[k * 4 + j]) / L[i * 4 + i]; } + } } + } - return std::make_tuple(L, U, rowPermutations); + return std::make_tuple(L, U, rowPermutations); } } // namespace mqt::ir::opt::helpers From f080bc69ba39d3aeeb52d5287cb2ddf9de420a5d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 17 Oct 2025 01:00:31 +0200 Subject: [PATCH 013/100] testing a.cpp --- mlir/lib/Dialect/MQTOpt/Transforms/a.cpp | 903 +++++++++++++++++++++++ 1 file changed, 903 insertions(+) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/a.cpp diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp new file mode 100644 index 000000000..d0bd804e4 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp @@ -0,0 +1,903 @@ +#include +#include +#include +#include +#include + +using fp = long double; +using qfp = std::complex; +using diagonal4x4 = std::array; +using vector2d = std::vector; +using matrix2x2 = std::array; +using matrix4x4 = std::array; + +using logical = bool; +using integer = int; +using doublecomplex = qfp; + +matrix4x4 zgemm2(matrix4x4 a, matrix4x4 b) { + matrix4x4 c__; + // a_dim1 = *lda; + // a_offset = 1 + a_dim1; + // a -= a_offset; + // b_dim1 = *ldb; + // b_offset = 1 + b_dim1; + // b -= b_offset; + // c_dim1 = *ldc; + // c_offset = 1 + c_dim1; + // c__ -= c_offset; + + int i__1 = 4; + int i__3{}; + qfp z__1; + qfp z__2; + qfp temp; + qfp alpha {1.0, 0.0}; + for (auto j = 0; j < i__1; ++j) { + for (auto i__ = 0; i__ < 4; ++i__) { + auto i__3 = i__ + j * 4; + c__[i__3].real(0.), c__[i__3].imag(0.); + } + int i__2 = 4; + for (auto l = 0; l < i__2; ++l) { + i__3 = l + j * 4; + if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { + i__3 = l + j * 4; + z__1.real(alpha.real() * b[i__3].real() - + alpha.imag() * b[i__3].imag()), + z__1.imag(alpha.real() * b[i__3].imag() + + alpha.imag() * b[i__3].real()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + i__3 = 4; + for (auto i__ = 0; i__ < i__3; ++i__) { + auto i__4 = i__ + j * 4; + auto i__5 = i__ + j * 4; + auto i__6 = i__ + l * 4; + z__2.real(temp.real() * a[i__6].real() - + temp.imag() * a[i__6].imag()), + z__2.imag(temp.real() * a[i__6].imag() + + temp.imag() * a[i__6].real()); + z__1.real(c__[i__5].real() + z__2.real()), + z__1.imag(c__[i__5].imag() + z__2.imag()); + c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); + /* L70: */ + } + } + /* L80: */ + } + /* L90: */ + } + return c__; +} + +int zgemm_(char* transa, char* transb, integer* m, integer* n, integer* k, + doublecomplex* alpha, doublecomplex* a, integer* lda, + doublecomplex* b, integer* ldb, doublecomplex* beta, + doublecomplex* c__, integer* ldc); +matrix4x4 zgemm(matrix4x4 a, matrix4x4 b) { + qfp alpha{1.0, 0.0}; + qfp beta{1.0, 0.0}; + int dimension = 4; + matrix4x4 result; + zgemm_("n", "n", &dimension, &dimension, &dimension, &alpha, a.data(), + &dimension, b.data(), &dimension, &beta, result.data(), &dimension); + return result; +} + +void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } + +/* Subroutine */ int zgemm_(char* transa, char* transb, integer* m, integer* n, + integer* k, doublecomplex* alpha, doublecomplex* a, + integer* lda, doublecomplex* b, integer* ldb, + doublecomplex* beta, doublecomplex* c__, + integer* ldc) { + /* System generated locals */ + integer a_dim1, a_offset, b_dim1, b_offset, c_dim1, c_offset, i__1, i__2, + i__3, i__4, i__5, i__6; + doublecomplex z__1, z__2, z__3, z__4; + + /* Local variables */ + static integer i__, j, l, info; + static logical nota, notb; + static doublecomplex temp; + static logical conja, conjb; + static integer nrowa, nrowb; + + /* + Purpose + ======= + + ZGEMM performs one of the matrix-matrix operations + + C := alpha*op( A )*op( B ) + beta*C, + + where op( X ) is one of + + op( X ) = X or op( X ) = X' or op( X ) = conjg( X' ), + + alpha and beta are scalars, and A, B and C are matrices, with op( A ) + an m by k matrix, op( B ) a k by n matrix and C an m by n matrix. + + Arguments + ========== + + TRANSA - CHARACTER*1. + On entry, TRANSA specifies the form of op( A ) to be used in + the matrix multiplication as follows: + + TRANSA = 'N' or 'n', op( A ) = A. + + TRANSA = 'T' or 't', op( A ) = A'. + + TRANSA = 'C' or 'c', op( A ) = conjg( A' ). + + Unchanged on exit. + + TRANSB - CHARACTER*1. + On entry, TRANSB specifies the form of op( B ) to be used in + the matrix multiplication as follows: + + TRANSB = 'N' or 'n', op( B ) = B. + + TRANSB = 'T' or 't', op( B ) = B'. + + TRANSB = 'C' or 'c', op( B ) = conjg( B' ). + + Unchanged on exit. + + M - INTEGER. + On entry, M specifies the number of rows of the matrix + op( A ) and of the matrix C. M must be at least zero. + Unchanged on exit. + + N - INTEGER. + On entry, N specifies the number of columns of the matrix + op( B ) and the number of columns of the matrix C. N must be + at least zero. + Unchanged on exit. + + K - INTEGER. + On entry, K specifies the number of columns of the matrix + op( A ) and the number of rows of the matrix op( B ). K must + be at least zero. + Unchanged on exit. + + ALPHA - COMPLEX*16 . + On entry, ALPHA specifies the scalar alpha. + Unchanged on exit. + + A - COMPLEX*16 array of DIMENSION ( LDA, ka ), where ka is + k when TRANSA = 'N' or 'n', and is m otherwise. + Before entry with TRANSA = 'N' or 'n', the leading m by k + part of the array A must contain the matrix A, otherwise + the leading k by m part of the array A must contain the + matrix A. + Unchanged on exit. + + LDA - INTEGER. + On entry, LDA specifies the first dimension of A as declared + in the calling (sub) program. When TRANSA = 'N' or 'n' then + LDA must be at least max( 1, m ), otherwise LDA must be at + least max( 1, k ). + Unchanged on exit. + + B - COMPLEX*16 array of DIMENSION ( LDB, kb ), where kb is + n when TRANSB = 'N' or 'n', and is k otherwise. + Before entry with TRANSB = 'N' or 'n', the leading k by n + part of the array B must contain the matrix B, otherwise + the leading n by k part of the array B must contain the + matrix B. + Unchanged on exit. + + LDB - INTEGER. + On entry, LDB specifies the first dimension of B as declared + in the calling (sub) program. When TRANSB = 'N' or 'n' then + LDB must be at least max( 1, k ), otherwise LDB must be at + least max( 1, n ). + Unchanged on exit. + + BETA - COMPLEX*16 . + On entry, BETA specifies the scalar beta. When BETA is + supplied as zero then C need not be set on input. + Unchanged on exit. + + C - COMPLEX*16 array of DIMENSION ( LDC, n ). + Before entry, the leading m by n part of the array C must + contain the matrix C, except when beta is zero, in which + case C need not be set on entry. + On exit, the array C is overwritten by the m by n matrix + ( alpha*op( A )*op( B ) + beta*C ). + + LDC - INTEGER. + On entry, LDC specifies the first dimension of C as declared + in the calling (sub) program. LDC must be at least + max( 1, m ). + Unchanged on exit. + + Further Details + =============== + + Level 3 Blas routine. + + -- Written on 8-February-1989. + Jack Dongarra, Argonne National Laboratory. + Iain Duff, AERE Harwell. + Jeremy Du Croz, Numerical Algorithms Group Ltd. + Sven Hammarling, Numerical Algorithms Group Ltd. + + ===================================================================== + + + Set NOTA and NOTB as true if A and B respectively are not + conjugated or transposed, set CONJA and CONJB as true if A and + B respectively are to be transposed but not conjugated and set + NROWA and NROWB as the number of rows and columns of A + and the number of rows of B respectively. + */ + + auto lsame_ = [](char* a, char* b) { + return std::string{a} == std::string{b}; + }; + + /* Parameter adjustments */ + a_dim1 = *lda; + a_offset = 1 + a_dim1; + a -= a_offset; + b_dim1 = *ldb; + b_offset = 1 + b_dim1; + b -= b_offset; + c_dim1 = *ldc; + c_offset = 1 + c_dim1; + c__ -= c_offset; + + /* Function Body */ + nota = lsame_(transa, "N"); + notb = lsame_(transb, "N"); + conja = lsame_(transa, "C"); + conjb = lsame_(transb, "C"); + if (nota) { + nrowa = *m; + } else { + nrowa = *k; + } + if (notb) { + nrowb = *k; + } else { + nrowb = *n; + } + + /* Test the input parameters. */ + + info = 0; + if (!nota && !conja && !lsame_(transa, "T")) { + info = 1; + } else if (!notb && !conjb && !lsame_(transb, "T")) { + info = 2; + } else if (*m < 0) { + info = 3; + } else if (*n < 0) { + info = 4; + } else if (*k < 0) { + info = 5; + } else if (*lda < std::max(1, nrowa)) { + info = 8; + } else if (*ldb < std::max(1, nrowb)) { + info = 10; + } else if (*ldc < std::max(1, *m)) { + info = 13; + } + if (info != 0) { + // xerbla_("ZGEMM ", &info); + return 0; + } + + /* Quick return if possible. */ + + if (*m == 0 || *n == 0 || + (alpha->real() == 0. && alpha->imag() == 0. || *k == 0) && + (beta->real() == 1. && beta->imag() == 0.)) { + return 0; + } + + /* And when alpha.eq.zero. */ + + if (alpha->real() == 0. && alpha->imag() == 0.) { + if (beta->real() == 0. && beta->imag() == 0.) { + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + c__[i__3].real(0.), c__[i__3].imag(0.); + /* L10: */ + } + /* L20: */ + } + } else { + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + i__4 = i__ + j * c_dim1; + z__1.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__1.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + /* L30: */ + } + /* L40: */ + } + } + return 0; + } + + /* Start the operations. */ + + if (notb) { + if (nota) { + + /* Form C := alpha*A*B + beta*C. */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + if (beta->real() == 0. && beta->imag() == 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + c__[i__3].real(0.), c__[i__3].imag(0.); + /* L50: */ + } + } else if (beta->real() != 1. || beta->imag() != 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + i__4 = i__ + j * c_dim1; + z__1.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__1.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + /* L60: */ + } + } + i__2 = *k; + for (l = 1; l <= i__2; ++l) { + i__3 = l + j * b_dim1; + if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { + i__3 = l + j * b_dim1; + z__1.real(alpha->real() * b[i__3].real() - + alpha->imag() * b[i__3].imag()), + z__1.imag(alpha->real() * b[i__3].imag() + + alpha->imag() * b[i__3].real()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + i__3 = *m; + for (i__ = 1; i__ <= i__3; ++i__) { + i__4 = i__ + j * c_dim1; + i__5 = i__ + j * c_dim1; + i__6 = i__ + l * a_dim1; + z__2.real(temp.real() * a[i__6].real() - + temp.imag() * a[i__6].imag()), + z__2.imag(temp.real() * a[i__6].imag() + + temp.imag() * a[i__6].real()); + z__1.real(c__[i__5].real() + z__2.real()), + z__1.imag(c__[i__5].imag() + z__2.imag()); + c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); + /* L70: */ + } + } + /* L80: */ + } + /* L90: */ + } + } else if (conja) { + + /* Form C := alpha*conjg( A' )*B + beta*C. */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + d_cnjg(&z__3, &a[l + i__ * a_dim1]); + i__4 = l + j * b_dim1; + z__2.real(z__3.real() * b[i__4].real() - + z__3.imag() * b[i__4].imag()), + z__2.imag(z__3.real() * b[i__4].imag() + + z__3.imag() * b[i__4].real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L100: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L110: */ + } + /* L120: */ + } + } else { + + /* Form C := alpha*A'*B + beta*C */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + i__4 = l + i__ * a_dim1; + i__5 = l + j * b_dim1; + z__2.real(a[i__4].real() * b[i__5].real() - + a[i__4].imag() * b[i__5].imag()), + z__2.imag(a[i__4].real() * b[i__5].imag() + + a[i__4].imag() * b[i__5].real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L130: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L140: */ + } + /* L150: */ + } + } + } else if (nota) { + if (conjb) { + + /* Form C := alpha*A*conjg( B' ) + beta*C. */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + if (beta->real() == 0. && beta->imag() == 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + c__[i__3].real(0.), c__[i__3].imag(0.); + /* L160: */ + } + } else if (beta->real() != 1. || beta->imag() != 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + i__4 = i__ + j * c_dim1; + z__1.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__1.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + /* L170: */ + } + } + i__2 = *k; + for (l = 1; l <= i__2; ++l) { + i__3 = j + l * b_dim1; + if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { + d_cnjg(&z__2, &b[j + l * b_dim1]); + z__1.real(alpha->real() * z__2.real() - + alpha->imag() * z__2.imag()), + z__1.imag(alpha->real() * z__2.imag() + + alpha->imag() * z__2.real()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + i__3 = *m; + for (i__ = 1; i__ <= i__3; ++i__) { + i__4 = i__ + j * c_dim1; + i__5 = i__ + j * c_dim1; + i__6 = i__ + l * a_dim1; + z__2.real(temp.real() * a[i__6].real() - + temp.imag() * a[i__6].imag()), + z__2.imag(temp.real() * a[i__6].imag() + + temp.imag() * a[i__6].real()); + z__1.real(c__[i__5].real() + z__2.real()), + z__1.imag(c__[i__5].imag() + z__2.imag()); + c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); + /* L180: */ + } + } + /* L190: */ + } + /* L200: */ + } + } else { + + /* Form C := alpha*A*B' + beta*C */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + if (beta->real() == 0. && beta->imag() == 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + c__[i__3].real(0.), c__[i__3].imag(0.); + /* L210: */ + } + } else if (beta->real() != 1. || beta->imag() != 0.) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + i__3 = i__ + j * c_dim1; + i__4 = i__ + j * c_dim1; + z__1.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__1.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + /* L220: */ + } + } + i__2 = *k; + for (l = 1; l <= i__2; ++l) { + i__3 = j + l * b_dim1; + if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { + i__3 = j + l * b_dim1; + z__1.real(alpha->real() * b[i__3].real() - + alpha->imag() * b[i__3].imag()), + z__1.imag(alpha->real() * b[i__3].imag() + + alpha->imag() * b[i__3].real()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + i__3 = *m; + for (i__ = 1; i__ <= i__3; ++i__) { + i__4 = i__ + j * c_dim1; + i__5 = i__ + j * c_dim1; + i__6 = i__ + l * a_dim1; + z__2.real(temp.real() * a[i__6].real() - + temp.imag() * a[i__6].imag()), + z__2.imag(temp.real() * a[i__6].imag() + + temp.imag() * a[i__6].real()); + z__1.real(c__[i__5].real() + z__2.real()), + z__1.imag(c__[i__5].imag() + z__2.imag()); + c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); + /* L230: */ + } + } + /* L240: */ + } + /* L250: */ + } + } + } else if (conja) { + if (conjb) { + + /* Form C := alpha*conjg( A' )*conjg( B' ) + beta*C. */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + d_cnjg(&z__3, &a[l + i__ * a_dim1]); + d_cnjg(&z__4, &b[j + l * b_dim1]); + z__2.real(z__3.real() * z__4.real() - z__3.imag() * z__4.imag()), + z__2.imag(z__3.real() * z__4.imag() + + z__3.imag() * z__4.real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L260: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L270: */ + } + /* L280: */ + } + } else { + + /* Form C := alpha*conjg( A' )*B' + beta*C */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + d_cnjg(&z__3, &a[l + i__ * a_dim1]); + i__4 = j + l * b_dim1; + z__2.real(z__3.real() * b[i__4].real() - + z__3.imag() * b[i__4].imag()), + z__2.imag(z__3.real() * b[i__4].imag() + + z__3.imag() * b[i__4].real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L290: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L300: */ + } + /* L310: */ + } + } + } else { + if (conjb) { + + /* Form C := alpha*A'*conjg( B' ) + beta*C */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + i__4 = l + i__ * a_dim1; + d_cnjg(&z__3, &b[j + l * b_dim1]); + z__2.real(a[i__4].real() * z__3.real() - + a[i__4].imag() * z__3.imag()), + z__2.imag(a[i__4].real() * z__3.imag() + + a[i__4].imag() * z__3.real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L320: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L330: */ + } + /* L340: */ + } + } else { + + /* Form C := alpha*A'*B' + beta*C */ + + i__1 = *n; + for (j = 1; j <= i__1; ++j) { + i__2 = *m; + for (i__ = 1; i__ <= i__2; ++i__) { + temp.real(0.), temp.imag(0.); + i__3 = *k; + for (l = 1; l <= i__3; ++l) { + i__4 = l + i__ * a_dim1; + i__5 = j + l * b_dim1; + z__2.real(a[i__4].real() * b[i__5].real() - + a[i__4].imag() * b[i__5].imag()), + z__2.imag(a[i__4].real() * b[i__5].imag() + + a[i__4].imag() * b[i__5].real()); + z__1.real(temp.real() + z__2.real()), + z__1.imag(temp.imag() + z__2.imag()); + temp.real(z__1.real()), temp.imag(z__1.imag()); + /* L350: */ + } + if (beta->real() == 0. && beta->imag() == 0.) { + i__3 = i__ + j * c_dim1; + z__1.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__1.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } else { + i__3 = i__ + j * c_dim1; + z__2.real(alpha->real() * temp.real() - + alpha->imag() * temp.imag()), + z__2.imag(alpha->real() * temp.imag() + + alpha->imag() * temp.real()); + i__4 = i__ + j * c_dim1; + z__3.real(beta->real() * c__[i__4].real() - + beta->imag() * c__[i__4].imag()), + z__3.imag(beta->real() * c__[i__4].imag() + + beta->imag() * c__[i__4].real()); + z__1.real(z__2.real() + z__3.real()), + z__1.imag(z__2.imag() + z__3.imag()); + c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + } + /* L360: */ + } + /* L370: */ + } + } + } + + return 0; + + /* End of ZGEMM . */ + +} /* zgemm_ */ + +template void print(std::array, N> matrix) { + int i{}; + for (auto&& a : matrix) { + std::cerr << std::setprecision(50) << a.real() << '+' << a.imag() << "i, "; + if (++i % 4 == 0) { + std::cerr << '\n'; + } + } + std::cerr << '\n'; +} + +auto mul(qfp a, qfp b) { + return qfp((a.real() * b.real() - a.imag() * b.imag()), + (a.real() * b.imag() + a.imag() * b.real())); +} + +// Function to perform SYRK (Symmetric Rank-K update) on 4x4 matrices stored in +// std::array +matrix4x4 syrk(bool upper, fp alpha, const matrix4x4& A, fp beta) { + matrix4x4 C; + // Iterate over the matrix rows and columns + for (size_t i = 0; i < 4; ++i) { + for (size_t j = (upper ? i : 0); j < 4; ++j) { + // Compute the dot product A[i, :] * A[j, :] (real and imaginary + // separately) + qfp sum{0.0, 0.0}; // Initialize sum as a complex number (0.0 + 0.0i) + + for (size_t k = 0; k < 4; ++k) { + sum += A[i * 4 + k] * std::conj(A[j * 4 + k]); // A[i, :] * A[j, :] + } + + // Apply the SYRK update: C(i, j) = alpha * sum + beta * C(i, j) + C[i * 4 + j] = alpha * sum + beta * C[i * 4 + j]; + + // If updating the lower triangle, mirror the values from the upper + // triangle + if (!upper && i != j) { + C[j * 4 + i] = C[i * 4 + j]; // Maintain symmetry + } + } + } + return C; +} + +template +[[nodiscard]] inline auto multiply(const std::array& lhs, + const std::array& rhs) { + // return matrixMultiplyWithKahan(lhs, rhs); + std::array result{}; + const int n = std::sqrt(N); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + for (int k = 0; k < n; k++) { + std::cout << std::setprecision(17) << lhs[i * n + k] << " * " + << rhs[k * n + j] << " = " + << mul(lhs[i * n + k], rhs[k * n + j]) << '\n'; + result[i * n + j] += mul(lhs[i * n + k], rhs[k * n + j]); + std::cout << std::setprecision(50) << "->" << result[i * n + j] << '\n'; + } + std::cout << "\n===\n"; + } + } + return result; +} + +static matrix4x4 transpose(const matrix4x4& matrix) { + matrix4x4 result; + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 4; ++j) { + result[j * 4 + i] = matrix[i * 4 + j]; + } + } + return result; +} + +int main() { + + matrix4x4 a = {qfp(0.3535533905932738, +0.35355339059327373), + qfp(-0.35355339059327373, +0.3535533905932738), + qfp(-0.35355339059327373, +0.3535533905932738), + qfp(0.3535533905932738, +0.35355339059327373), + qfp(0.35355339059327373, -0.3535533905932738), + qfp(0.3535533905932738, +0.35355339059327373), + qfp(-0.3535533905932738, -0.35355339059327373), + qfp(-0.35355339059327373, +0.3535533905932738), + qfp(0.35355339059327373, -0.3535533905932738), + qfp(-0.3535533905932738, -0.35355339059327373), + qfp(0.3535533905932738, +0.35355339059327373), + qfp(-0.35355339059327373, +0.3535533905932738), + qfp(0.3535533905932738, +0.35355339059327373), + qfp(0.35355339059327373, -0.3535533905932738), + qfp(0.35355339059327373, -0.3535533905932738), + qfp(0.3535533905932738, +0.35355339059327373)}; + + // print(multiply(transpose(a), transpose(transpose(a)))); + // print(syrk(false, 1.0, transpose(a), 0.0)); + print(zgemm2(transpose(a), a)); +} From 792d082770fd8a60b4112dcbd88b04ee73fb8deb Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 19 Oct 2025 20:26:57 +0200 Subject: [PATCH 014/100] add zgemm for testing, start euler decomposition implementation --- .../Transforms/GateDecompositionPattern.cpp | 344 +++++++++++++----- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 2 + mlir/lib/Dialect/MQTOpt/Transforms/a.cpp | 77 ++-- 3 files changed, 285 insertions(+), 138 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 85cf3ac4c..756cf5b85 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -136,8 +136,7 @@ struct GateDecompositionPattern final */ template static OpType createOneParameterGate(mlir::PatternRewriter& rewriter, - mlir::Location location, - fp parameter, + mlir::Location location, fp parameter, mlir::ValueRange inQubits) { auto parameterValue = rewriter.create( location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); @@ -217,9 +216,8 @@ struct GateDecompositionPattern final // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp // to -π - static fp - mod2pi(fp angle, - fp angleZeroEpsilon = std::numeric_limits::epsilon()) { + static fp mod2pi(fp angle, + fp angleZeroEpsilon = std::numeric_limits::epsilon()) { // remEuclid() isn't exactly the same as Python's % operator, but // because the RHS here is a constant and positive it is effectively // equivalent for this case @@ -241,11 +239,14 @@ struct GateDecompositionPattern final return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], matrix[1 * 2 + 1]}; } - static matrix4x4 transpose(const matrix4x4& matrix) { - matrix4x4 result; - for (size_t i = 0; i < 4; ++i) { - for (size_t j = 0; j < 4; ++j) { - result[j * 4 + i] = matrix[i * 4 + j]; + + template + static std::array transpose(const std::array& matrix) { + const std::size_t n = std::sqrt(N); + std::array result; + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + result[j * n + i] = matrix[i * n + j]; } } return result; @@ -342,77 +343,216 @@ struct GateDecompositionPattern final }; } - // Helper function to perform QR decomposition (simplified) - static void qrDecomposition(std::array& A, - std::array& Q, - std::array& R) { - // QR decomposition is simplified for this 4x4 case - for (size_t i = 0; i < 4; ++i) { - fp norm = 0.0; - for (size_t j = 0; j < 4; ++j) { - norm += A[j * 4 + i] * A[j * 4 + i]; - } - norm = std::sqrt(norm); + // return Q, R such that A = Q * R + static void qrDecomposition(const rmatrix4x4& A, rmatrix4x4& Q, + rmatrix4x4& R) { + // array of factor Q1, Q2, ... Qm + std::vector qv(4); - // Normalize the column - for (size_t j = 0; j < 4; ++j) { - Q[j * 4 + i] = A[j * 4 + i] / norm; - } + // temp array + auto z(A); + rmatrix4x4 z1; - // Compute the R matrix - for (size_t j = i; j < 4; ++j) { - fp dot = 0.0; - for (size_t k = 0; k < 4; ++k) { - dot += Q[k * 4 + i] * A[k * 4 + j]; - } - R[i * 4 + j] = dot; - } + auto vmadd = [](const auto& a, const auto& b, double s, auto& c) { + for (int i = 0; i < 4; i++) + c[i] = a[i] + s * b[i]; + }; - // Subtract to make A orthogonal - for (size_t j = 0; j < 4; ++j) { - for (size_t k = i; k < 4; ++k) { - A[j * 4 + k] -= Q[j * 4 + i] * R[i * 4 + k]; - } + auto compute_householder_factor = [](rmatrix4x4& mat, + const rdiagonal4x4& v) { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + mat[i + 4 * j] = -2.0 * v[i] * v[j]; + for (int i = 0; i < 4; i++) + mat[i * 4 + i] += 1; + }; + + // take c-th column of a matrix, put results in Vector v + auto extract_column = [](const rmatrix4x4& m, rdiagonal4x4& v, int c) { + for (int i = 0; i < 4; i++) + v[i] = m[i + 4 * c]; + }; + + auto compute_minor = [](rmatrix4x4& lhs, const rmatrix4x4& rhs, int d) { + for (int i = 0; i < d; i++) + lhs[i * 4 + i] = 1.0; + for (int i = d; i < 4; i++) + for (int j = d; j < 4; j++) + lhs[i + 4 * j] = rhs[i + 4 * j]; + }; + + auto norm = [](auto&& m) { + double sum = 0; + for (int i = 0; i < m.size(); i++) + sum += m[i] * m[i]; + return sqrt(sum); + }; + + auto rescale_unit = [&](auto& m) { + auto factor = norm(m); + for (int i = 0; i < m.size(); i++) + m[i] /= factor; + }; + + for (int k = 0; k < 4 && k < 4 - 1; k++) { + + rdiagonal4x4 e{}, x{}; + double a{}; + + // compute minor + compute_minor(z1, z, k); + + // extract k-th column into x + extract_column(z1, x, k); + + a = norm(x); + if (A[k * 4 + k] > 0) + a = -a; + + for (int i = 0; i < 4; i++) + e[i] = (i == k) ? 1 : 0; + + // e = x + a*e + vmadd(x, e, a, e); + + // e = e / ||e|| + rescale_unit(e); + + // qv[k] = I - 2 *e*e^T + compute_householder_factor(qv[k], e); + + // z = qv[k] * z1 + z = helpers::multiply(qv[k], z1); + } + + Q = qv[0]; + + // after this loop, we will obtain Q (up to a transpose operation) + for (int i = 1; i < 4 && i < 4 - 1; i++) { + + z1 = helpers::multiply(qv[i], Q); + Q = z1; + } + + R = helpers::multiply(Q, A); + Q = transpose(Q); + } + + void tridiagonalization_inplace(rmatrix4x4& mat, CoeffVectorType& hCoeffs) { + auto n = 4; + + auto makeHouseholder = [](llvm::SmallVector& essential, fp& tau, fp& beta) { + std::vector tail { essential.begin() + 1, essential.end() }; + + auto squaredNorm = [](auto&& v) { + qfp sum{}; + for (auto&& x : v) { + sum += qfp(std::real(x) * std::real(x), std::imag(x) * std::imag(x)); } + return sum.real() + sum.imag(); + }; + + auto tailSqNorm = essential.size() == 1 ? 0.0 : squaredNorm(tail); + fp c0 = essential[0]; + const fp tol = (std::numeric_limits::min)(); + + if (tailSqNorm <= tol && std::norm(std::imag(c0)) <= tol) { + tau = 0; + beta = std::real(c0); + llvm::fill(essential, 0); + } else { + beta = std::sqrt(std::norm(c0) + tailSqNorm); + if (std::real(c0) >= fp(0)) beta = -beta; + for (std::size_t i = 0; i < essential.size(); ++i) { + essential[i] = tail[i] / (c0 - beta); + } + tau = (beta - c0) / beta; // TODO: std::conj for complex? + } + }; + + for (auto i = 0; i < n - 1; ++i) { + auto remainingSize = n - i - 1; + fp beta; + fp h; + + // matA.col(i).tail(remainingSize).makeHouseholderInPlace(h, beta); + llvm::SmallVector tmp; + for (int j = n - remainingSize; j < n; ++j) { + tmp.push_back(mat[j * n + i]); } + makeHouseholder(tmp, h, beta); + + // Apply similarity transformation to remaining columns, + // i.e., A = H A H' where H = I - h v v' and v = matA.col(i).tail(n-i-1) + matA.col(i).coeffRef(i + 1) = fp(1); + + hCoeffs.tail(n - i - 1).noalias() = + (matA.bottomRightCorner(remainingSize, remainingSize).template selfadjointView() * + (conj(h) * matA.col(i).tail(remainingSize))); + + hCoeffs.tail(n - i - 1) += + (conj(h) * RealScalar(-0.5) * (hCoeffs.tail(remainingSize).dot(matA.col(i).tail(remainingSize)))) * + matA.col(i).tail(n - i - 1); + + matA.bottomRightCorner(remainingSize, remainingSize) + .template selfadjointView() + .rankUpdate(matA.col(i).tail(remainingSize), hCoeffs.tail(remainingSize), Scalar(-1)); + + matA.col(i).coeffRef(i + 1) = beta; + hCoeffs.coeffRef(i) = h; } +} // Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) - static std::array // eigenvectors (4x4) - self_adjoint_evd(std::array& A, // input symmetric matrix (4x4) - std::array& s // eigenvalues + static rmatrix4x4 // eigenvectors (4x4) + self_adjoint_evd(rmatrix4x4 A, // input symmetric matrix (4x4) + rdiagonal4x4& s // eigenvalues ) { - // Step 1: Zero out the upper triangle (we are only interested in the lower - // half) - for (size_t i = 0; i < 4; ++i) { - for (size_t j = i + 1; j < 4; ++j) { - A[i * 4 + j] = 0; // Set the upper triangle to zero + rmatrix4x4 U = {1, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 1}; // Start with identity + + auto isConverged = [](const rmatrix4x4& A, double tol = 1e-10) -> bool { + double sum = 0.0; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + if (i != j) + sum += A[i + 4 * j] * A[i + 4 * j]; + } } - } + return std::sqrt(sum) < tol; + }; - // Step 2: Perform QR decomposition - std::array Q, R; - for (int iter = 0; iter < 1000; - ++iter) { // Arbitrary number of iterations for convergence + rmatrix4x4 Q{}; + rmatrix4x4 R{}; + + constexpr auto maxIters = 100; + for (int iter = 0; iter < maxIters; ++iter) { qrDecomposition(A, Q, R); - A = helpers::multiply(R, Q); // Update A = R * Q - } - // Step 3: Extract eigenvalues (diagonal of the matrix) - for (size_t i = 0; i < 4; ++i) { - s[i] = A[i * 4 + i]; // Eigenvalues are the diagonal elements + // A = R * Q + A = helpers::multiply(R, Q); + + // eigenVectors = eigenVectors * Q + U = helpers::multiply(U, Q); + + if (isConverged(A)) { + break; + } } - return Q; + for (int i = 0; i < 4; ++i) { + s[i] = A[i * 4 + i]; + } + return U; } // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen - static std::array + static std::pair, std::array> self_adjoint_eigen_lower(std::array A) { std::array S; auto U = self_adjoint_evd(A, S); - return U; + return {U, S}; } static std::tuple @@ -516,7 +656,7 @@ struct GateDecompositionPattern final } static std::array angles_from_unitary(const matrix2x2& matrix, - EulerBasis basis) { + EulerBasis basis) { if (basis == EulerBasis::XYX) { return params_xyx_inner(matrix); } @@ -543,14 +683,18 @@ struct GateDecompositionPattern final static std::array params_xyx_inner(const matrix2x2& matrix) { auto mat_zyz = std::array{ - static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + static_cast(0.5) * + (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + static_cast(0.5) * + (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + static_cast(0.5) * + (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + static_cast(0.5) * + (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), }; auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); auto new_phi = mod2pi(phi + qc::PI, 0.); @@ -569,12 +713,9 @@ struct GateDecompositionPattern final static constexpr qfp IM{0., 1.}; static constexpr qfp M_IM{0., -1.}; - static constexpr std::array IPZ = {IM, C_ZERO, - C_ZERO, M_IM}; - static constexpr std::array IPY = {C_ZERO, C_ONE, - C_M_ONE, C_ZERO}; - static constexpr std::array IPX = {C_ZERO, IM, IM, - C_ZERO}; + static constexpr std::array IPZ = {IM, C_ZERO, C_ZERO, M_IM}; + static constexpr std::array IPY = {C_ZERO, C_ONE, C_M_ONE, C_ZERO}; + static constexpr std::array IPX = {C_ZERO, IM, IM, C_ZERO}; static matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { if (gate.type == qc::SX) { @@ -623,10 +764,10 @@ struct GateDecompositionPattern final fp b; fp c; fp global_phase; - std::array, 4> K1l; - std::array, 4> K2l; - std::array, 4> K1r; - std::array, 4> K2r; + std::array K1l; + std::array K2l; + std::array K1r; + std::array K2r; Specialization specialization; EulerBasis default_euler_basis; std::optional requested_fidelity; @@ -638,7 +779,7 @@ struct GateDecompositionPattern final std::optional _specialization) { auto& u = unitary_matrix; auto det_u = determinant(u); - auto det_pow = std::pow(det_u, -fp(0.25)); + auto det_pow = std::pow(det_u, static_cast(-0.25)); llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); llvm::errs() << "===== U =====\n"; helpers::print(u); @@ -690,8 +831,7 @@ struct GateDecompositionPattern final llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { return rand_a * val.real() + rand_b * val.imag(); }); - auto p_inner_real = std::get<1>( - helpers::LUdecomposition(self_adjoint_eigen_lower(m2_real))); + auto p_inner_real = self_adjoint_eigen_lower(m2_real).first; matrix4x4 p_inner; llvm::transform(p_inner_real, p_inner.begin(), [](auto&& x) { return qfp(x, 0.0); }); @@ -704,7 +844,7 @@ struct GateDecompositionPattern final auto compare = dot(dot(p_inner, diag_d), transpose(p_inner)); found = llvm::all_of_zip(compare, m2, [](auto&& a, auto&& b) { - return std::abs(a - b) < 1.0e-13; + return std::abs(a - b) <= 1.0e-13; }); if (found) { p = p_inner; @@ -826,8 +966,9 @@ struct GateDecompositionPattern final auto da = a - ap; auto db = b - bp; auto dc = c - cp; - auto tr = static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); + auto tr = static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); if (fidelity) { return trace_to_fid(tr) >= *fidelity; } @@ -1153,13 +1294,15 @@ struct GateDecompositionPattern final b - specialized.b, -c - specialized.c, }; - return static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); + return static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); } else { auto [da, db, dc] = std::array{a - specialized.a, b - specialized.b, c - specialized.c}; - return static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); + return static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); } }; auto tr = get_tr(); @@ -1214,8 +1357,7 @@ struct GateDecompositionPattern final const std::vector& gate_params = {}, matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}, // CX matrix - fp basis_fidelity = 1.0, - EulerBasis euler_basis = EulerBasis::ZYZ, + fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, std::optional pulse_optimize = std::nullopt) { auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& max_relative) { @@ -1243,7 +1385,8 @@ struct GateDecompositionPattern final } return abs_diff <= abs_lhs * max_relative; }; - constexpr auto FRAC_1_SQRT_2 = static_cast(0.707106781186547524400844362104849039); + constexpr auto FRAC_1_SQRT_2 = + static_cast(0.707106781186547524400844362104849039); constexpr auto K12R_ARR = std::array{ qfp(0., FRAC_1_SQRT_2), qfp(FRAC_1_SQRT_2, 0.), @@ -1785,12 +1928,13 @@ struct GateDecompositionPattern final static_cast(4.) * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), std::sin(target.a) * std::sin(target.b) * std::sin(target.c)), - static_cast(4.) * qfp(std::cos(qc::PI_4 - target.a) * - std::cos(basis_decomposer.b - target.b) * - std::cos(target.c), - std::sin(qc::PI_4 - target.a) * - std::sin(basis_decomposer.b - target.b) * - std::sin(target.c)), + static_cast(4.) * + qfp(std::cos(qc::PI_4 - target.a) * + std::cos(basis_decomposer.b - target.b) * + std::cos(target.c), + std::sin(qc::PI_4 - target.a) * + std::sin(basis_decomposer.b - target.b) * + std::sin(target.c)), qfp(4. * std::cos(target.c), 0.), qfp(4., 0.), }; @@ -1862,9 +2006,9 @@ struct GateDecompositionPattern final * indicating that they have been altered from the originals. */ [[nodiscard]] static OneQubitGateSequence - calculateRotationGates(fp theta, fp phi, fp lambda, - fp phase, qc::OpType kGate, qc::OpType aGate, - bool simplify, std::optional atol) { + calculateRotationGates(fp theta, fp phi, fp lambda, fp phase, + qc::OpType kGate, qc::OpType aGate, bool simplify, + std::optional atol) { fp angleZeroEpsilon = atol.value_or(1e-12); if (simplify) { angleZeroEpsilon = -1.0; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 6880e934f..cb302440d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -23,9 +23,11 @@ namespace mqt::ir::opt { using fp = long double; using qfp = std::complex; using diagonal4x4 = std::array; + using rdiagonal4x4 = std::array; using vector2d = std::vector; using matrix2x2 = std::array; using matrix4x4 = std::array; + using rmatrix4x4 = std::array; } // namespace mqt::ir::opt namespace mqt::ir::opt::helpers { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp index d0bd804e4..a4da79e47 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp @@ -71,25 +71,27 @@ matrix4x4 zgemm2(matrix4x4 a, matrix4x4 b) { } int zgemm_(char* transa, char* transb, integer* m, integer* n, integer* k, - doublecomplex* alpha, doublecomplex* a, integer* lda, - doublecomplex* b, integer* ldb, doublecomplex* beta, - doublecomplex* c__, integer* ldc); + doublecomplex* alpha, matrix4x4 a, integer* lda, + matrix4x4 b, integer* ldb, doublecomplex* beta, + matrix4x4& c__, integer* ldc); matrix4x4 zgemm(matrix4x4 a, matrix4x4 b) { qfp alpha{1.0, 0.0}; qfp beta{1.0, 0.0}; int dimension = 4; - matrix4x4 result; - zgemm_("n", "n", &dimension, &dimension, &dimension, &alpha, a.data(), - &dimension, b.data(), &dimension, &beta, result.data(), &dimension); + matrix4x4 result{}; + // zgemm_("n", "n", &dimension, &dimension, &dimension, &alpha, a.data(), + // &dimension, b.data(), &dimension, &beta, result.data(), &dimension); + zgemm_("N", "N", &dimension, &dimension, &dimension, &alpha, a, + &dimension, b, &dimension, &beta, result, &dimension); return result; } void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } /* Subroutine */ int zgemm_(char* transa, char* transb, integer* m, integer* n, - integer* k, doublecomplex* alpha, doublecomplex* a, - integer* lda, doublecomplex* b, integer* ldb, - doublecomplex* beta, doublecomplex* c__, + integer* k, doublecomplex* alpha, matrix4x4 a, + integer* lda, matrix4x4 b, integer* ldb, + doublecomplex* beta, matrix4x4& c__, integer* ldc) { /* System generated locals */ integer a_dim1, a_offset, b_dim1, b_offset, c_dim1, c_offset, i__1, i__2, @@ -242,13 +244,13 @@ void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } /* Parameter adjustments */ a_dim1 = *lda; a_offset = 1 + a_dim1; - a -= a_offset; + // a -= a_offset; b_dim1 = *ldb; b_offset = 1 + b_dim1; - b -= b_offset; + // b -= b_offset; c_dim1 = *ldc; c_offset = 1 + c_dim1; - c__ -= c_offset; + // c__ -= c_offset; /* Function Body */ nota = lsame_(transa, "N"); @@ -346,7 +348,7 @@ void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } i__2 = *m; for (i__ = 1; i__ <= i__2; ++i__) { i__3 = i__ + j * c_dim1; - c__[i__3].real(0.), c__[i__3].imag(0.); + c__[i__3 - c_offset].real(0.), c__[i__3 - c_offset].imag(0.); /* L50: */ } } else if (beta->real() != 1. || beta->imag() != 0.) { @@ -354,36 +356,36 @@ void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } for (i__ = 1; i__ <= i__2; ++i__) { i__3 = i__ + j * c_dim1; i__4 = i__ + j * c_dim1; - z__1.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__1.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + z__1.real(beta->real() * c__[i__4 - c_offset].real() - + beta->imag() * c__[i__4 - c_offset].imag()), + z__1.imag(beta->real() * c__[i__4 - c_offset].imag() + + beta->imag() * c__[i__4 - c_offset].real()); + c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); /* L60: */ } } i__2 = *k; for (l = 1; l <= i__2; ++l) { i__3 = l + j * b_dim1; - if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { + if (b[i__3 - b_offset].real() != 0. || b[i__3 - b_offset].imag() != 0.) { i__3 = l + j * b_dim1; - z__1.real(alpha->real() * b[i__3].real() - - alpha->imag() * b[i__3].imag()), - z__1.imag(alpha->real() * b[i__3].imag() + - alpha->imag() * b[i__3].real()); + z__1.real(alpha->real() * b[i__3 - b_offset].real() - + alpha->imag() * b[i__3 - b_offset].imag()), + z__1.imag(alpha->real() * b[i__3 - b_offset].imag() + + alpha->imag() * b[i__3 - b_offset].real()); temp.real(z__1.real()), temp.imag(z__1.imag()); i__3 = *m; for (i__ = 1; i__ <= i__3; ++i__) { i__4 = i__ + j * c_dim1; i__5 = i__ + j * c_dim1; i__6 = i__ + l * a_dim1; - z__2.real(temp.real() * a[i__6].real() - - temp.imag() * a[i__6].imag()), - z__2.imag(temp.real() * a[i__6].imag() + - temp.imag() * a[i__6].real()); - z__1.real(c__[i__5].real() + z__2.real()), - z__1.imag(c__[i__5].imag() + z__2.imag()); - c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); + z__2.real(temp.real() * a[i__6 - a_offset].real() - + temp.imag() * a[i__6 - a_offset].imag()), + z__2.imag(temp.real() * a[i__6 - a_offset].imag() + + temp.imag() * a[i__6 - a_offset].real()); + z__1.real(c__[i__5 - c_offset].real() + z__2.real()), + z__1.imag(c__[i__5 - c_offset].imag() + z__2.imag()); + c__[i__4 - c_offset].real(z__1.real()), c__[i__4 - c_offset].imag(z__1.imag()); /* L70: */ } } @@ -419,7 +421,7 @@ void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } alpha->imag() * temp.imag()), z__1.imag(alpha->real() * temp.imag() + alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); } else { i__3 = i__ + j * c_dim1; z__2.real(alpha->real() * temp.real() - @@ -427,13 +429,13 @@ void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } z__2.imag(alpha->real() * temp.imag() + alpha->imag() * temp.real()); i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); + z__3.real(beta->real() * c__[i__4 - c_offset].real() - + beta->imag() * c__[i__4 - c_offset].imag()), + z__3.imag(beta->real() * c__[i__4 - c_offset].imag() + + beta->imag() * c__[i__4 - c_offset].real()); z__1.real(z__2.real() + z__3.real()), z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); + c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); } /* L110: */ } @@ -879,7 +881,6 @@ static matrix4x4 transpose(const matrix4x4& matrix) { } int main() { - matrix4x4 a = {qfp(0.3535533905932738, +0.35355339059327373), qfp(-0.35355339059327373, +0.3535533905932738), qfp(-0.35355339059327373, +0.3535533905932738), @@ -899,5 +900,5 @@ int main() { // print(multiply(transpose(a), transpose(transpose(a)))); // print(syrk(false, 1.0, transpose(a), 0.0)); - print(zgemm2(transpose(a), a)); + print(zgemm(transpose(a), a)); } From 5bd31b4b1929704791ab893c4914d990f44b9f42 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 21 Oct 2025 22:15:51 +0200 Subject: [PATCH 015/100] non-working but finished snapshot --- .../Transforms/GateDecompositionPattern.cpp | 436 +++++----------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 268 +++++++++- mlir/lib/Dialect/MQTOpt/Transforms/b.h | 474 ++++++++++++++++++ 3 files changed, 838 insertions(+), 340 deletions(-) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/b.h diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 756cf5b85..959aa2c7e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -25,6 +25,8 @@ #include #include +#include "b.h" + namespace mqt::ir::opt { /** @@ -46,7 +48,7 @@ struct GateDecompositionPattern final return mlir::failure(); } - matrix4x4 unitaryMatrix = kroneckerProduct(identityGate, identityGate); + matrix4x4 unitaryMatrix = helpers::kroneckerProduct(identityGate, identityGate); for (auto&& gate : series) { auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate), .parameter = {/*TODO*/}, @@ -228,335 +230,109 @@ struct GateDecompositionPattern final return wrapped; } - static matrix2x2 dot(const matrix2x2& lhs, const matrix2x2& rhs) { - return helpers::multiply(lhs, rhs); - } - static matrix4x4 dot(const matrix4x4& lhs, const matrix4x4& rhs) { - return helpers::multiply(lhs, rhs); - } - - static matrix2x2 transpose(const matrix2x2& matrix) { - return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], - matrix[1 * 2 + 1]}; - } - - template - static std::array transpose(const std::array& matrix) { - const std::size_t n = std::sqrt(N); - std::array result; - for (size_t i = 0; i < n; ++i) { - for (size_t j = 0; j < n; ++j) { - result[j * n + i] = matrix[i * n + j]; - } - } - return result; - } - - static matrix2x2 transpose_conjugate(const matrix2x2& matrix) { - auto result = transpose(matrix); - llvm::transform(result, result.begin(), - [](auto&& x) { return std::conj(x); }); - return result; - }; - - static qfp determinant(const matrix2x2& mat) { - return mat[0] * mat[3] - mat[1] * mat[2]; - } - - static qfp determinant(const std::array& mat) { - return mat[0] * (mat[4] * mat[8] - mat[5] * mat[7]) - - mat[1] * (mat[3] * mat[8] - mat[5] * mat[6]) + - mat[2] * (mat[3] * mat[7] - mat[4] * mat[6]); - } - - static std::array get3x3Submatrix(const matrix4x4& mat, - int rowToBeRemoved, - int columnToBeRemoved) { - std::array, 9> result; - int subIndex = 0; - for (int i = 0; i < 4; ++i) { - if (i != rowToBeRemoved) { - for (int j = 0; j < 4; ++j) { - if (j != columnToBeRemoved) { - result[subIndex++] = mat[i * 4 + j]; + // GPT generated + void tridiagonalize_inplace(rmatrix4x4& A) { + auto dot3 = [](auto&& a, auto&& b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + }; + // Householder for column 0 + { + auto x = std::array{A[1 * 4 + 0], A[8 + 0], + A[12 + 0]}; // Elements [1,0], [2,0], [3,0] + auto norm_x = std::sqrt(dot3(x, x)); + if (norm_x > 1e-12) { + auto sign = (x[0] >= 0) ? 1.0 : -1.0; + auto u0 = x[0] + sign * norm_x; + auto denom = std::sqrt(u0 * u0 + x[1] * x[1] + x[2] * x[2]); + auto v = std::array{u0 / denom, x[1] / denom, x[2] / denom}; + + // Apply H = I - 2vvᵀ to A from both sides: A := H A H + // Only affect rows and columns 1-3 + + // temp = A * v + std::array temp = {0, 0, 0}; + for (int i = 1; i < 4; ++i) { + for (int j = 1; j < 4; ++j) { + temp[i - 1] += A[i * 4 + j] * v[j - 1]; } } - } - } - return result; - } - - static qfp determinant(const matrix4x4& mat) { - auto [l, u, rowPermutations] = helpers::LUdecomposition(mat); - auto det = C_ONE; - for (int i = 0; i < 4; ++i) { - det *= l[i * 4 + i]; - } - - if (rowPermutations % 2 != 0) { - det = -det; - } - return det; - - // auto det = -C_ZERO; - // for (int column = 0; column < 4; ++column) { - // auto submatrix = get3x3Submatrix(mat, 0, column); - // auto subDet = determinant(submatrix); - // auto tmp = mat[0 * 4 + column] * subDet; - // if (column % 2 == 0 && - // tmp != - // C_ZERO) { // TODO: better way to get negative 0.0 in - // determinant? - // det += tmp; - // } else if (tmp != -C_ZERO) { - // det -= tmp; - // } - // } - // return det; - } - - static matrix2x2 multiply(qfp factor, matrix2x2 matrix) { - llvm::transform(matrix, matrix.begin(), - [&](auto&& x) { return factor * x; }); - return matrix; - } - - static matrix4x4 kroneckerProduct(const matrix2x2& lhs, - const matrix2x2& rhs) { - return from(multiply(lhs[0 * 2 + 0], rhs), multiply(lhs[0 * 2 + 1], rhs), - multiply(lhs[1 * 2 + 0], rhs), multiply(lhs[1 * 2 + 1], rhs)); - } - - static matrix4x4 from(const matrix2x2& first_quadrant, - const matrix2x2& second_quadrant, - const matrix2x2& third_quadrant, - const matrix2x2& fourth_quadrant) { - return { - first_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], - second_quadrant[0 * 2 + 0], second_quadrant[0 * 2 + 1], - first_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], - second_quadrant[1 * 2 + 0], second_quadrant[1 * 2 + 1], - third_quadrant[0 * 2 + 0], third_quadrant[0 * 2 + 1], - fourth_quadrant[0 * 2 + 0], fourth_quadrant[0 * 2 + 1], - third_quadrant[1 * 2 + 0], third_quadrant[1 * 2 + 1], - fourth_quadrant[1 * 2 + 0], fourth_quadrant[1 * 2 + 1], - }; - } - - // return Q, R such that A = Q * R - static void qrDecomposition(const rmatrix4x4& A, rmatrix4x4& Q, - rmatrix4x4& R) { - // array of factor Q1, Q2, ... Qm - std::vector qv(4); - - // temp array - auto z(A); - rmatrix4x4 z1; - - auto vmadd = [](const auto& a, const auto& b, double s, auto& c) { - for (int i = 0; i < 4; i++) - c[i] = a[i] + s * b[i]; - }; - - auto compute_householder_factor = [](rmatrix4x4& mat, - const rdiagonal4x4& v) { - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - mat[i + 4 * j] = -2.0 * v[i] * v[j]; - for (int i = 0; i < 4; i++) - mat[i * 4 + i] += 1; - }; - - // take c-th column of a matrix, put results in Vector v - auto extract_column = [](const rmatrix4x4& m, rdiagonal4x4& v, int c) { - for (int i = 0; i < 4; i++) - v[i] = m[i + 4 * c]; - }; - - auto compute_minor = [](rmatrix4x4& lhs, const rmatrix4x4& rhs, int d) { - for (int i = 0; i < d; i++) - lhs[i * 4 + i] = 1.0; - for (int i = d; i < 4; i++) - for (int j = d; j < 4; j++) - lhs[i + 4 * j] = rhs[i + 4 * j]; - }; - - auto norm = [](auto&& m) { - double sum = 0; - for (int i = 0; i < m.size(); i++) - sum += m[i] * m[i]; - return sqrt(sum); - }; - - auto rescale_unit = [&](auto& m) { - auto factor = norm(m); - for (int i = 0; i < m.size(); i++) - m[i] /= factor; - }; - - for (int k = 0; k < 4 && k < 4 - 1; k++) { - - rdiagonal4x4 e{}, x{}; - double a{}; - - // compute minor - compute_minor(z1, z, k); - - // extract k-th column into x - extract_column(z1, x, k); - a = norm(x); - if (A[k * 4 + k] > 0) - a = -a; - - for (int i = 0; i < 4; i++) - e[i] = (i == k) ? 1 : 0; - - // e = x + a*e - vmadd(x, e, a, e); - - // e = e / ||e|| - rescale_unit(e); - - // qv[k] = I - 2 *e*e^T - compute_householder_factor(qv[k], e); - - // z = qv[k] * z1 - z = helpers::multiply(qv[k], z1); - } - - Q = qv[0]; - - // after this loop, we will obtain Q (up to a transpose operation) - for (int i = 1; i < 4 && i < 4 - 1; i++) { - - z1 = helpers::multiply(qv[i], Q); - Q = z1; - } - - R = helpers::multiply(Q, A); - Q = transpose(Q); - } - - void tridiagonalization_inplace(rmatrix4x4& mat, CoeffVectorType& hCoeffs) { - auto n = 4; - - auto makeHouseholder = [](llvm::SmallVector& essential, fp& tau, fp& beta) { - std::vector tail { essential.begin() + 1, essential.end() }; - - auto squaredNorm = [](auto&& v) { - qfp sum{}; - for (auto&& x : v) { - sum += qfp(std::real(x) * std::real(x), std::imag(x) * std::imag(x)); + // w = 2 * (A*v - (vᵀ*A*v)*v) + auto alpha = dot3(v, temp); + std::array w; + for (int i = 0; i < 3; ++i) + w[i] = 2.0 * (temp[i] - alpha * v[i]); + + // Update A = A - v wᵀ - w vᵀ + for (int i = 1; i < 4; ++i) { + for (int j = i; j < 4; ++j) { + auto delta = v[i - 1] * w[j - 1] + w[i - 1] * v[j - 1]; + A[i * 4 + j] -= delta; + if (i != j) + A[j * 4 + i] -= delta; // symmetry + } + } } - return sum.real() + sum.imag(); - }; - - auto tailSqNorm = essential.size() == 1 ? 0.0 : squaredNorm(tail); - fp c0 = essential[0]; - const fp tol = (std::numeric_limits::min)(); - - if (tailSqNorm <= tol && std::norm(std::imag(c0)) <= tol) { - tau = 0; - beta = std::real(c0); - llvm::fill(essential, 0); - } else { - beta = std::sqrt(std::norm(c0) + tailSqNorm); - if (std::real(c0) >= fp(0)) beta = -beta; - for (std::size_t i = 0; i < essential.size(); ++i) { - essential[i] = tail[i] / (c0 - beta); } - tau = (beta - c0) / beta; // TODO: std::conj for complex? - } - }; - - for (auto i = 0; i < n - 1; ++i) { - auto remainingSize = n - i - 1; - fp beta; - fp h; - - // matA.col(i).tail(remainingSize).makeHouseholderInPlace(h, beta); - llvm::SmallVector tmp; - for (int j = n - remainingSize; j < n; ++j) { - tmp.push_back(mat[j * n + i]); - } - makeHouseholder(tmp, h, beta); - - // Apply similarity transformation to remaining columns, - // i.e., A = H A H' where H = I - h v v' and v = matA.col(i).tail(n-i-1) - matA.col(i).coeffRef(i + 1) = fp(1); - hCoeffs.tail(n - i - 1).noalias() = - (matA.bottomRightCorner(remainingSize, remainingSize).template selfadjointView() * - (conj(h) * matA.col(i).tail(remainingSize))); - - hCoeffs.tail(n - i - 1) += - (conj(h) * RealScalar(-0.5) * (hCoeffs.tail(remainingSize).dot(matA.col(i).tail(remainingSize)))) * - matA.col(i).tail(n - i - 1); - - matA.bottomRightCorner(remainingSize, remainingSize) - .template selfadjointView() - .rankUpdate(matA.col(i).tail(remainingSize), hCoeffs.tail(remainingSize), Scalar(-1)); - - matA.col(i).coeffRef(i + 1) = beta; - hCoeffs.coeffRef(i) = h; - } -} - - // Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) - static rmatrix4x4 // eigenvectors (4x4) - self_adjoint_evd(rmatrix4x4 A, // input symmetric matrix (4x4) - rdiagonal4x4& s // eigenvalues - ) { - rmatrix4x4 U = {1, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 1}; // Start with identity - - auto isConverged = [](const rmatrix4x4& A, double tol = 1e-10) -> bool { - double sum = 0.0; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - if (i != j) - sum += A[i + 4 * j] * A[i + 4 * j]; + // Householder for column 1 (submatrix 2x2 at bottom right) + { + auto x = std::array{A[10], A[15]}; // Elements [2,1], [3,1] + auto norm_x = std::sqrt(x[0] * x[0] + x[1] * x[1]); + if (norm_x > 1e-12) { + auto sign = (x[0] >= 0) ? 1.0 : -1.0; + auto u0 = x[0] + sign * norm_x; + auto denom = std::sqrt(u0 * u0 + x[1] * x[1]); + auto v = std::array{u0 / denom, x[1] / denom}; + + // temp = A * v + std::array temp = {0, 0}; + for (int i = 2; i < 4; ++i) { + for (int j = 2; j < 4; ++j) { + temp[i - 2] += A[i * 4 + j] * v[j - 2]; + } } - } - return std::sqrt(sum) < tol; - }; - - rmatrix4x4 Q{}; - rmatrix4x4 R{}; - - constexpr auto maxIters = 100; - for (int iter = 0; iter < maxIters; ++iter) { - qrDecomposition(A, Q, R); - - // A = R * Q - A = helpers::multiply(R, Q); - - // eigenVectors = eigenVectors * Q - U = helpers::multiply(U, Q); - if (isConverged(A)) { - break; + // w = 2 * (A*v - (vᵀ*A*v)*v) + auto alpha = v[0] * temp[0] + v[1] * temp[1]; + std::array w; + for (int i = 0; i < 2; ++i) + w[i] = 2.0 * (temp[i] - alpha * v[i]); + + // Update A = A - v wᵀ - w vᵀ + for (int i = 2; i < 4; ++i) { + for (int j = i; j < 4; ++j) { + auto delta = v[i - 2] * w[j - 2] + w[i - 2] * v[j - 2]; + A[i * 4 + j] -= delta; + if (i != j) + A[j * 4 + i] -= delta; + } + } } } - for (int i = 0; i < 4; ++i) { - s[i] = A[i * 4 + i]; - } - return U; + // Now A is tridiagonal } - // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen - static std::pair, std::array> - self_adjoint_eigen_lower(std::array A) { - std::array S; - auto U = self_adjoint_evd(A, S); + static std::pair + self_adjoint_eigen_lower(rmatrix4x4 A) { + // rdiagonal4x4 S; + // auto U = self_adjoint_evd(A, S); + + // rmatrix4x4 U; + // jacobi_eigen_decomposition(A, U, S); + + auto [U, S] = self_adjoint_evd(A); return {U, S}; } static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { + using helpers::dot; + using helpers::kroneckerProduct; + using helpers::transpose_conjugate; + using helpers::determinant; // first quadrant matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; @@ -597,13 +373,9 @@ struct GateDecompositionPattern final return {l, r, phase}; } - static diagonal4x4 diagonal(const matrix4x4& matrix) { - return {matrix[0 * 4 + 0], matrix[1 * 4 + 1], matrix[2 * 4 + 2], - matrix[3 * 4 + 3]}; - } - static matrix4x4 magic_basis_transform(const matrix4x4& unitary, MagicBasisTransform direction) { + using helpers::dot; constexpr matrix4x4 B_NON_NORMALIZED = { C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, @@ -707,12 +479,6 @@ struct GateDecompositionPattern final }; } - static constexpr qfp C_ZERO{0., 0.}; - static constexpr qfp C_ONE{1., 0.}; - static constexpr qfp C_M_ONE{-1., 0.}; - static constexpr qfp IM{0., 1.}; - static constexpr qfp M_IM{0., -1.}; - static constexpr std::array IPZ = {IM, C_ZERO, C_ZERO, M_IM}; static constexpr std::array IPY = {C_ZERO, C_ONE, C_M_ONE, C_ZERO}; static constexpr std::array IPX = {C_ZERO, IM, IM, C_ZERO}; @@ -732,6 +498,8 @@ struct GateDecompositionPattern final } static matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { + using helpers::kroneckerProduct; + if (gate.qubit_id.empty()) { return kroneckerProduct(identityGate, identityGate); } @@ -777,6 +545,10 @@ struct GateDecompositionPattern final static TwoQubitWeylDecomposition new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { + using helpers::dot; + using helpers::determinant; + using helpers::transpose; + using helpers::diagonal; auto& u = unitary_matrix; auto det_u = determinant(u); auto det_pow = std::pow(det_u, static_cast(-0.25)); @@ -836,6 +608,11 @@ struct GateDecompositionPattern final llvm::transform(p_inner_real, p_inner.begin(), [](auto&& x) { return qfp(x, 0.0); }); auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); + + llvm::errs() << "===== D_INNER =====\n"; + helpers::print(d_inner); + llvm::errs() << "===== P_INNER =====\n"; + helpers::print(p_inner); matrix4x4 diag_d{}; // zero initialization diag_d[0 * 4 + 0] = d_inner[0]; diag_d[1 * 4 + 1] = d_inner[1]; @@ -843,6 +620,8 @@ struct GateDecompositionPattern final diag_d[3 * 4 + 3] = d_inner[3]; auto compare = dot(dot(p_inner, diag_d), transpose(p_inner)); + llvm::errs() << "===== COMPARE =====\n"; + helpers::print(compare); found = llvm::all_of_zip(compare, m2, [](auto&& a, auto&& b) { return std::abs(a - b) <= 1.0e-13; }); @@ -1359,6 +1138,9 @@ struct GateDecompositionPattern final 0, 0}, // CX matrix fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, std::optional pulse_optimize = std::nullopt) { + using helpers::dot; + using helpers::transpose_conjugate; + auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& max_relative) { // Handle same infinities @@ -1612,14 +1394,17 @@ struct GateDecompositionPattern final private: [[nodiscard]] std::vector decomp0_inner(const TwoQubitWeylDecomposition& target) const { + using helpers::dot; return { - dot(target.K1r, target.K2r), + dot(target.K1r, target.K2r), dot(target.K1l, target.K2l), }; } [[nodiscard]] std::vector decomp1_inner(const TwoQubitWeylDecomposition& target) const { + using helpers::dot; + using helpers::transpose_conjugate; // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) return { @@ -1632,6 +1417,7 @@ struct GateDecompositionPattern final [[nodiscard]] std::vector decomp2_supercontrolled_inner( const TwoQubitWeylDecomposition& target) const { + using helpers::dot; return { dot(q2r, target.K2r), dot(q2l, target.K2l), @@ -1644,6 +1430,7 @@ struct GateDecompositionPattern final [[nodiscard]] std::vector decomp3_supercontrolled_inner( const TwoQubitWeylDecomposition& target) const { + using helpers::dot; return { dot(u3r, target.K2r), dot(u3l, target.K2l), @@ -1707,6 +1494,7 @@ struct GateDecompositionPattern final TwoQubitGateSequence get_sx_vz_2cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { + using helpers::dot; TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; gates.globalPhase -= 2. * basis_decomposer.global_phase; @@ -1771,6 +1559,7 @@ struct GateDecompositionPattern final std::optional get_sx_vz_3cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { + using helpers::dot; TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; gates.globalPhase -= 3. * basis_decomposer.global_phase; gates.globalPhase = remEuclid(gates.globalPhase, qc::TAU); @@ -1893,6 +1682,7 @@ struct GateDecompositionPattern final matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, fp global_phase) { + using helpers::dot; auto phase = std::exp(std::complex{0, global_phase}); matrix4x4 matrix; matrix[0 * 4 + 0] = phase; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index cb302440d..3ae7eaa7b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -20,21 +20,27 @@ #include namespace mqt::ir::opt { - using fp = long double; - using qfp = std::complex; - using diagonal4x4 = std::array; - using rdiagonal4x4 = std::array; - using vector2d = std::vector; - using matrix2x2 = std::array; - using matrix4x4 = std::array; - using rmatrix4x4 = std::array; +using fp = double; +using qfp = std::complex; +using diagonal4x4 = std::array; +using rdiagonal4x4 = std::array; +using vector2d = std::vector; +using matrix2x2 = std::array; +using matrix4x4 = std::array; +using rmatrix4x4 = std::array; + +constexpr qfp C_ZERO{0., 0.}; +constexpr qfp C_ONE{1., 0.}; +constexpr qfp C_M_ONE{-1., 0.}; +constexpr qfp IM{0., 1.}; +constexpr qfp M_IM{0., -1.}; + } // namespace mqt::ir::opt namespace mqt::ir::opt::helpers { // TODO: remove -template -void print(std::array, N> matrix) { +template void print(std::array, N> matrix) { int i{}; for (auto&& a : matrix) { std::cerr << std::setprecision(50) << a.real() << 'i' << a.imag() << ' '; @@ -211,18 +217,25 @@ std::array matrixMultiplyWithKahan(const std::array& lhs, return result; } -[[nodiscard]] inline matrix2x2 multiply(qfp factor, - const matrix2x2& matrix) { - return {factor * matrix.at(0), factor * matrix.at(1), factor * matrix.at(2), - factor * matrix.at(3)}; +template +[[nodiscard]] inline Container multiply(fp factor, Container matrix) { + llvm::transform(matrix, std::begin(matrix), + [&](auto&& x) { return factor * x; }); + return matrix; +} + +template +[[nodiscard]] inline Container multiply(qfp factor, Container matrix) { + llvm::transform(matrix, std::begin(matrix), + [&](auto&& x) { return factor * x; }); + return matrix; } template [[nodiscard]] inline auto multiply(const std::array& lhs, const std::array& rhs) { - // return matrixMultiplyWithKahan(lhs, rhs); - std::array result{{T{}}}; - const int n = std::sqrt(N); + std::array result{}; + const int n = std::sqrt(lhs.size()); for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < n; k++) { @@ -233,6 +246,25 @@ template return result; } +template +[[nodiscard]] inline auto multiply(const std::vector& lhs, + const std::vector& rhs, int columnsLhs) { + int rowsLhs = lhs.size() / columnsLhs; + int rowsRhs = columnsLhs; + int columnsRhs = rhs.size() / rowsRhs; + + std::vector result(rowsLhs * columnsRhs, T{}); + for (int i = 0; i < rowsLhs; i++) { + for (int j = 0; j < columnsRhs; j++) { + for (int k = 0; k < columnsLhs; k++) { + result[i * columnsRhs + j] += + lhs[i * columnsLhs + k] * rhs[k * columnsRhs + j]; + } + } + } + return result; +} + template [[nodiscard]] inline auto LUdecomposition(std::array matrix) { std::array L{}; @@ -289,4 +321,206 @@ template return std::make_tuple(L, U, rowPermutations); } + +template +using ValueType = typename std::remove_cvref_t::value_type; + +template +static auto diagonal(Container&& matrix) { + const int n = std::sqrt(matrix.size()); + auto result = [&]() { + using T = std::remove_cvref_t; + if constexpr (std::is_same_v && Offset == 0) { + return diagonal4x4{}; + } else if constexpr (std::is_same_v && Offset == 0) { + return rdiagonal4x4{}; + } else { + return std::vector>(n - std::abs(Offset)); + } + }(); + for (std::size_t i = 0; i < result.size(); ++i) { + auto x = Offset > 0 ? i + Offset : i; + auto y = Offset < 0 ? i - Offset : i; + result[i] = matrix[y * n + x]; + } + return result; +} + +template +auto submatrix(Container&& matrix, int rowStart, int columnStart, int numRows, + int numColumns) { + const int n = std::sqrt(matrix.size()); + assert((rowStart + numRows) <= n); + assert((columnStart + numColumns) <= n); + + std::vector> result(numRows * numColumns); + for (int i = 0; i < numColumns; ++i) { + for (int j = 0; j < numRows; ++j) { + result[j * numColumns + i] = matrix[(rowStart + j) * n + (columnStart + i)]; + } + } + return result; +} + +template +auto assignSubmatrix(Lhs&& lhs, Rhs&& rhs, int rowStart, int columnStart, + int rowSize, int columnSize) { + const int n = std::sqrt(lhs.size()); + assert((rowStart + rowSize) < n); + assert((columnStart + columnSize) < n); + assert(columnSize * rowSize == rhs.size()); + + for (int i = 0; i < columnSize; ++i) { + for (int j = 0; j < rowSize; ++j) { + lhs[(rowStart + j) * 4 + (columnStart + i)] = rhs[j * rowSize + i]; + } + } +} + +template inline auto dot(Args&&... args) { + return helpers::multiply(std::forward(args)...); +} + +template +inline auto vectorsDot(Lhs&& lhs, Rhs&& rhs) { + return std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), + typename Lhs::value_type{}); +} + +template auto add(Lhs lhs, Rhs&& rhs) { + assert(lhs.size() == rhs.size()); + for (int i = 0; i < lhs.size(); ++i) { + lhs[i] += rhs[i]; + } + return lhs; +} + +inline matrix2x2 transpose(const matrix2x2& matrix) { + return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], + matrix[1 * 2 + 1]}; +} + +template auto transpose(Container&& matrix) { + const std::size_t n = std::sqrt(matrix.size()); + auto result{matrix}; + for (size_t i = 0; i < n; ++i) { + for (size_t j = 0; j < n; ++j) { + result[j * n + i] = matrix[i * n + j]; + } + } + return result; +} + +template auto conj(T&& x) { + using U = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return std::conj(x); + } else if constexpr (std::is_same_v) { + return x; + } else { + static_assert(!sizeof(U), "Unimplemented case for helpers::conj"); + } +} + +template auto conjugate(Container matrix) { + llvm::transform(matrix, matrix.begin(), [](auto&& x) { return conj(x); }); + return matrix; +} + +template +inline auto transpose_conjugate(Container&& matrix) { + auto result = transpose(matrix); + result = conjugate(matrix); + return result; +} + +inline qfp determinant(const matrix2x2& mat) { + return mat[0] * mat[3] - mat[1] * mat[2]; +} + +inline qfp determinant(const std::array& mat) { + return mat[0] * (mat[4] * mat[8] - mat[5] * mat[7]) - + mat[1] * (mat[3] * mat[8] - mat[5] * mat[6]) + + mat[2] * (mat[3] * mat[7] - mat[4] * mat[6]); +} + +inline std::array get3x3Submatrix(const matrix4x4& mat, + int rowToBeRemoved, + int columnToBeRemoved) { + std::array, 9> result; + int subIndex = 0; + for (int i = 0; i < 4; ++i) { + if (i != rowToBeRemoved) { + for (int j = 0; j < 4; ++j) { + if (j != columnToBeRemoved) { + result[subIndex++] = mat[i * 4 + j]; + } + } + } + } + return result; +} + +inline qfp determinant(const matrix4x4& mat) { + auto [l, u, rowPermutations] = helpers::LUdecomposition(mat); + auto det = C_ONE; + for (int i = 0; i < 4; ++i) { + det *= l[i * 4 + i]; + } + + if (rowPermutations % 2 != 0) { + det = -det; + } + return det; + + // auto det = -C_ZERO; + // for (int column = 0; column < 4; ++column) { + // auto submatrix = get3x3Submatrix(mat, 0, column); + // auto subDet = determinant(submatrix); + // auto tmp = mat[0 * 4 + column] * subDet; + // if (column % 2 == 0 && + // tmp != + // C_ZERO) { // TODO: better way to get negative 0.0 in + // determinant? + // det += tmp; + // } else if (tmp != -C_ZERO) { + // det -= tmp; + // } + // } + // return det; +} + +template +inline std::array from(const std::array& first_quadrant, + const std::array& second_quadrant, + const std::array& third_quadrant, + const std::array& fourth_quadrant) { + return { + first_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], + second_quadrant[0 * 2 + 0], second_quadrant[0 * 2 + 1], + first_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], + second_quadrant[1 * 2 + 0], second_quadrant[1 * 2 + 1], + third_quadrant[0 * 2 + 0], third_quadrant[0 * 2 + 1], + fourth_quadrant[0 * 2 + 0], fourth_quadrant[0 * 2 + 1], + third_quadrant[1 * 2 + 0], third_quadrant[1 * 2 + 1], + fourth_quadrant[1 * 2 + 0], fourth_quadrant[1 * 2 + 1], + }; +} + +template inline auto toArray4(const std::vector& vec) { + std::array result; + assert(vec.size() == result.size()); + for (std::size_t i = 0; i < vec.size(); ++i) { + result[i] = vec[i]; + } + return result; +} + +template +inline std::array kroneckerProduct(const std::array& lhs, + const std::array& rhs) { + return from(multiply(lhs[0 * 2 + 0], rhs), multiply(lhs[0 * 2 + 1], rhs), + multiply(lhs[1 * 2 + 0], rhs), multiply(lhs[1 * 2 + 1], rhs)); +} + } // namespace mqt::ir::opt::helpers diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/b.h b/mlir/lib/Dialect/MQTOpt/Transforms/b.h new file mode 100644 index 000000000..6b3af6b29 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/b.h @@ -0,0 +1,474 @@ +#pragma once + +#include "Helpers.h" + +namespace mqt::ir::opt { +void tridiagonalization_inplace(rmatrix4x4& mat, rdiagonal4x4& hCoeffs) { + auto n = 4; + + auto makeHouseholder = [](llvm::SmallVector& essential, fp& tau, + fp& beta) { + std::vector tail{essential.begin(), essential.end()}; + + auto squaredNorm = [](auto&& v) { + qfp sum{}; + for (auto&& x : v) { + sum += qfp(std::real(x) * std::real(x), std::imag(x) * std::imag(x)); + } + return sum.real() + sum.imag(); + }; + + auto tailSqNorm = essential.size() == 1 ? 0.0 : squaredNorm(tail); + fp c0 = essential[0]; + const fp tol = (std::numeric_limits::min)(); + + if (tailSqNorm <= tol && std::norm(std::imag(c0)) <= tol) { + tau = 0; + beta = std::real(c0); + llvm::fill(essential, 0); + } else { + beta = std::sqrt(std::norm(c0) + tailSqNorm); + if (std::real(c0) >= 0.0) { + beta = -beta; + } + for (std::size_t i = 0; i < essential.size(); ++i) { + essential[i] = tail[i] / (c0 - beta); + } + tau = helpers::conj((beta - c0) / beta); + } + }; + + auto lowerSelfadjointView = [](auto matrix) { + const int n = std::sqrt(matrix.size()); + for (int i = 0; i < n; ++i) { + for (int j = 0; j <= i; ++j) { + matrix[j * n + i] = matrix[i * n + j]; + } + } + return matrix; + }; + + auto bottomRightCorner = [](auto&& matrix, int rows, int columns) { + const int n = std::sqrt(matrix.size()); + return helpers::submatrix(matrix, n - rows, n - columns, rows, columns); + }; + + auto getColumn = [](const rmatrix4x4& matrix, int column) { + rdiagonal4x4 result; + for (int j = 0; j < 4; ++j) { + result[j] = matrix[j * 4 + column]; + } + return result; + }; + + auto getTail = [](auto&& array, int size) { + std::vector result(size); + for (int i = 0; i < size; ++i) { + result[i] = array[array.size() - size + i]; + } + return result; + }; + + auto rankUpdate = [](auto matrix, auto&& u, auto&& v, auto&& alpha) { + rmatrix4x4 add1{}; + for (int i = 0; i < u.size(); ++i) { + for (int j = 0; j < v.size(); ++j) { + add1[j * 4 + i] += alpha * u[i] * helpers::conj(v[j]); + } + } + rmatrix4x4 add2{}; + for (int i = 0; i < v.size(); ++i) { + for (int j = 0; j < u.size(); ++j) { + add2[j * 4 + i] = helpers::conj(alpha) * v[i] * helpers::conj(u[j]); + } + } + for (int i = 0; i < matrix.size(); ++i) { + matrix[i] += add1[i] + add2[i]; + } + return matrix; + }; + + for (auto i = 0; i < n - 1; ++i) { + auto remainingSize = n - i - 1; + fp beta; + fp h; + + // matA.col(i).tail(remainingSize).makeHouseholderInPlace(h, beta); + llvm::SmallVector tmp; + for (int j = n - remainingSize; j < n; ++j) { + tmp.push_back(mat[j * n + i]); + } + makeHouseholder(tmp, h, beta); + + // Apply similarity transformation to remaining columns, + // i.e., A = H A H' where H = I - h v v' and v = matA.col(i).tail(n-i-1) + // matA.col(i).coeffRef(i + 1) = fp(1); + mat[(i + 1) * n + i] = 1.0; + + // hCoeffs.tail(n - i - 1).noalias() = + // (matA.bottomRightCorner(remainingSize, remainingSize).template + // selfadjointView() * + // (conj(h) * matA.col(i).tail(remainingSize))); + auto tmp2 = helpers::multiply( + lowerSelfadjointView( + bottomRightCorner(mat, remainingSize, remainingSize)), + helpers::multiply(helpers::conj(h), + getTail(getColumn(mat, i), remainingSize)), + remainingSize); + for (int a = 0; a < remainingSize; ++a) { + hCoeffs[i + 1 + a] = tmp2[a]; + } + + // hCoeffs.tail(n - i - 1) += + // (conj(h) * RealScalar(-0.5) * + // (hCoeffs.tail(remainingSize).dot(matA.col(i).tail(remainingSize)))) + // * matA.col(i).tail(n - i - 1); + auto tmpFactor = + helpers::conj(h) * static_cast(-0.5) * + helpers::vectorsDot(getTail(hCoeffs, remainingSize), + getTail(getColumn(mat, i), remainingSize)); + tmp2 = helpers::multiply(tmpFactor, + helpers::submatrix(mat, i + 1, i, n - i - 1, 1)); + for (int a = 0; a < remainingSize; ++a) { + hCoeffs[i + 1 + a] += tmp2[a]; + } + + // matA.bottomRightCorner(remainingSize, remainingSize) + // .template selfadjointView() + // .rankUpdate(matA.col(i).tail(remainingSize), + // hCoeffs.tail(remainingSize), Scalar(-1)); + auto updatedMatrix = bottomRightCorner(mat, remainingSize, remainingSize); + updatedMatrix = lowerSelfadjointView(updatedMatrix); + updatedMatrix = rankUpdate( + updatedMatrix, helpers::submatrix(mat, n - remainingSize, i, remainingSize, 1), + std::vector{hCoeffs.begin() + (n - remainingSize), hCoeffs.end()}, + -1.0); + // update bottom right corner + helpers::assignSubmatrix(mat, updatedMatrix, n - remainingSize, + n - remainingSize, remainingSize, remainingSize); + + // matA.col(i).coeffRef(i + 1) = beta; + mat[(i + 1) * n + i] = beta; + // hCoeffs.coeffRef(i) = h; + hCoeffs[i] = h; + } +} + +void tridiagonal_qr_step(rdiagonal4x4& diag, std::vector& subdiag, + int start, int end, rmatrix4x4& matrixQ) { + // Wilkinson Shift. + auto td = (diag[end - 1] - diag[end]) * static_cast(0.5); + auto e = subdiag[end - 1]; + // Note that thanks to scaling, e^2 or td^2 cannot overflow, however they can + // still underflow thus leading to inf/NaN values when using the following + // commented code: + // RealScalar e2 = numext::abs2(subdiag[end-1]); + // RealScalar mu = diag[end] - e2 / (td + (td>0 ? 1 : -1) * sqrt(td*td + + // e2)); + // This explain the following, somewhat more complicated, version: + auto mu = diag[end]; + if (td == 0.0) { + mu -= std::abs(e); + } else if (e != 0.0) { + const auto e2 = std::norm(e); + const auto h = std::hypot(td, e); + if (e2 == 0.0) { + mu -= e / ((td + (td > static_cast(0) ? h : -h)) / e); + } else { + mu -= e2 / (td + (td > static_cast(0) ? h : -h)); + } + } + + auto x = diag[start] - mu; + auto z = subdiag[start]; + // If z ever becomes zero, the Givens rotation will be the identity and + // z will stay zero for all future iterations. + for (int k = start; k < end && z != 0.0; ++k) { + struct JacobiRotation { + fp c; + fp s; + + void makeGivens(fp p, fp q) { + if (q == 0.0) { + c = p < 0 ? -1 : 1; + s = 0; + } else if (p == 0.0) { + c = 0; + s = q < 0 ? 1 : -1; + } else if (std::abs(p) > std::abs(q)) { + auto t = q / p; + auto u = std::sqrt(1.0 + std::norm(t)); + if (p < 0.0) + u = -u; + c = 1.0 / u; + s = -t * c; + } else { + auto t = p / q; + auto u = std::sqrt(1.0 + std::norm(t)); + if (q < 0) + u = -u; + s = -1.0 / u; + c = -t * s; + } + } + + void applyOnTheRight(rmatrix4x4& matrix, int p, int q) { + const int n = std::sqrt(matrix.size()); + auto x = helpers::submatrix(matrix, 0, p, n, 1); + auto y = helpers::submatrix(matrix, 0, q, n, 1); + auto j = *this; + j.transpose(); + + if (j.c == 0.0 && j.s == 0.0) { + return; + } + + for (int i = 0; i < n; ++i) { + auto xi = x[i]; + auto yi = y[i]; + x[i] = j.c * xi + helpers::conj(j.s) * yi; + y[i] = -j.s * xi + helpers::conj(j.c) * yi; + } + + helpers::assignSubmatrix(matrix, x, 0, p, n, 1); + helpers::assignSubmatrix(matrix, y, 0, q, n, 1); + } + + void transpose() { s = -helpers::conj(s); } + } rot; + rot.makeGivens(x, z); + + // do T = G' T G + auto sdk = rot.s * diag[k] + rot.c * subdiag[k]; + auto dkp1 = rot.s * subdiag[k] + rot.c * diag[k + 1]; + + diag[k] = rot.c * (rot.c * diag[k] - rot.s * subdiag[k]) - + rot.s * (rot.c * subdiag[k] - rot.s * diag[k + 1]); + diag[k + 1] = rot.s * sdk + rot.c * dkp1; + subdiag[k] = rot.c * sdk - rot.s * dkp1; + + if (k > start) + subdiag[k - 1] = rot.c * subdiag[k - 1] - rot.s * z; + + // "Chasing the bulge" to return to triangular form. + x = subdiag[k]; + if (k < end - 1) { + z = -rot.s * subdiag[k + 1]; + subdiag[k + 1] = rot.c * subdiag[k + 1]; + } + + // apply the givens rotation to the unit matrix Q = Q * G + rot.applyOnTheRight(matrixQ, k, k + 1); + } +} + +void computeFromTridiagonal_impl(rdiagonal4x4& diag, std::vector& subdiag, + const int maxIterations, + bool computeEigenvectors, rmatrix4x4& eivec) { + auto n = diag.size(); + auto end = n - 1; + int start = 0; + int iter = 0; // total number of iterations + + constexpr auto considerAsZero = (std::numeric_limits::min)(); + const auto precision_inv = + static_cast(1.0) / std::numeric_limits::epsilon(); + while (end > 0) { + for (int i = start; i < end; ++i) { + if (std::abs(subdiag[i]) < considerAsZero) { + subdiag[i] = static_cast(0); + } else { + // abs(subdiag[i]) <= epsilon * sqrt(abs(diag[i]) + abs(diag[i+1])) + // Scaled to prevent underflows. + const auto scaled_subdiag = precision_inv * subdiag[i]; + if (scaled_subdiag * scaled_subdiag <= + (std::abs(diag[i]) + std::abs(diag[i + 1]))) { + subdiag[i] = static_cast(0); + } + } + } + + // find the largest unreduced block at the end of the matrix. + while (end > 0 && subdiag[end - 1] == 0.0) { + end--; + } + if (end <= 0) { + break; + } + + // if we spent too many iterations, we give up + iter++; + if (iter > maxIterations * n) { + break; + } + + start = end - 1; + while (start > 0 && subdiag[start - 1] != 0.0) { + start--; + } + + tridiagonal_qr_step(diag, subdiag, start, end, eivec); + } + // Sort eigenvalues and corresponding vectors. + // TODO make the sort optional ? + // TODO use a better sort algorithm !! + if (iter > maxIterations * n) { + throw std::runtime_error{"No convergence for eigenvalue decomposition!"}; + } + for (int i = 0; i < n - 1; ++i) { + // diag.segment(i, n - i).minCoeff(&k); + int k = std::distance(diag.begin() + i, + std::min_element(diag.begin() + i, diag.end())); + if (k > 0 && k < n - i) { + std::swap(diag[i], diag[k + i]); + if (computeEigenvectors) { + for (int j = 0; j < n; ++j) { + std::swap(eivec[j * n + i], eivec[j * n + (k + i)]); + } + } + } + } +} + +void householderSequenceEval(rmatrix4x4& m_vectors, + const rdiagonal4x4& m_coeffs, int length, + int shift) { + auto essentialVector = [&](const rmatrix4x4& vectors, int k) { + int start = k + 1 + shift; + std::vector result; + result.reserve(4 - start); + for (int j = start; j < 4; ++j) { + result.push_back(vectors[j * 4 + k]); + } + return result; + }; + + auto applyThisOnTheLeft = [&](auto&& vectors, auto& dst) { + const auto n = std::sqrt(dst.size()); + for (int k = 0; k < length; ++k) { + int actual_k = n - k - 1; + int dstRows = n - shift - actual_k; + + // TODO + // auto sub_dst = dst.bottomRows(dstRows); + // sub_dst.applyHouseholderOnTheLeft(essentialVector(vectors, actual_k), + // m_coeffs[actual_k]); + } + }; + + auto applyHouseholderOnTheLeft = [](std::vector& matrix, + const std::vector& essential, + const fp& tau) { + const int n = std::sqrt(matrix.size()); + if (tau != 0.0) { + auto firstRow = helpers::submatrix(matrix, 0, 0, 1, n); + auto bottom = helpers::submatrix(matrix, 1, 0, n - 1, n); + + auto tmp = helpers::multiply(helpers::transpose_conjugate(essential), + bottom, essential.size()); + tmp = helpers::add(tmp, firstRow); + + auto tmp2 = helpers::add( + firstRow, helpers::multiply(-1.0, helpers::multiply(tau, tmp))); + // insert first row (first 4 elements) + llvm::copy(tmp2, matrix.begin()); + + tmp2 = helpers::add( + bottom, helpers::multiply(-tau, helpers::multiply(essential, tmp, + essential.size()))); + // insert all rows except first row + llvm::copy(tmp2, matrix.begin() + n); + } + }; + + auto applyHouseholderOnTheRight = [](std::vector& matrix, + const std::vector& essential, + const fp& tau) { + const int n = std::sqrt(matrix.size()); + if (tau != 0.0) { + auto firstColumn = helpers::submatrix(matrix, 0, 0, n, 1); + auto right = helpers::submatrix(matrix, 0, 1, n, n - 1); + + auto tmp = helpers::multiply(right, essential, n - 1); + tmp = helpers::add(tmp, firstColumn); + + auto tmp2 = helpers::add(firstColumn, helpers::multiply(-tau, tmp)); + auto tmp3 = helpers::add( + right, + helpers::multiply( + -tau, helpers::multiply( + tmp, helpers::transpose_conjugate(essential), 1))); + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + if (i == 0) { + // insert first column (first 4 elements) + matrix[j * n + i] = tmp2[j]; + } else { + // insert all except first column + matrix[j * n + i] = tmp3[j * (n - 1) + i]; + } + } + } + } + }; + + rmatrix4x4 dst; + const int n = std::sqrt(m_vectors.size()); + const int vecs = length; + dst = helpers::kroneckerProduct(std::array{1.0, 0.0, 0.0, 1.0}, + {1.0, 0.0, 0.0, 1.0}); + for (int k = vecs - 1; k >= 0; --k) { + int cornerSize = n - k - shift; + auto bottomRightCorner = helpers::submatrix( + dst, n - cornerSize, n - cornerSize, cornerSize, cornerSize); + applyHouseholderOnTheLeft(bottomRightCorner, essentialVector(m_vectors, k), + m_coeffs[k]); + // update bottom right corner + helpers::assignSubmatrix(dst, bottomRightCorner, n - cornerSize, + n - cornerSize, cornerSize, cornerSize); + } +} + +// https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen +static std::pair self_adjoint_evd(rmatrix4x4 matrix) { + + auto lowerTriangularView = [](auto matrix) { + const int n = std::sqrt(matrix.size()); + for (int i = 0; i < n; ++i) { + for (int j = 0; j <= i; ++j) { + matrix[j * n + i] = 0.0; + } + } + return matrix; + }; + + rdiagonal4x4 eigenvalues{1}; + rmatrix4x4 eigenvectors; + int n = 4; + + // map the matrix coefficients to [-1:1] to avoid over- and underflow. + eigenvectors = lowerTriangularView(matrix); + fp scale = *llvm::max_element(eigenvalues, [](auto&& a, auto&& b) { + return std::abs(a) < std::abs(b); + }); + if (scale == 0.0) + scale = static_cast(1.0); + for (auto& eigenvector : eigenvectors) { + eigenvector /= scale; + } + rdiagonal4x4 hCoeffs; + tridiagonalization_inplace(eigenvectors, hCoeffs); + eigenvalues = helpers::diagonal(eigenvectors); + auto subdiag = helpers::diagonal<-1>(eigenvectors); + householderSequenceEval(eigenvectors, helpers::conjugate(hCoeffs), n - 1, 1); + + computeFromTridiagonal_impl(eigenvalues, subdiag, 1000, true, eigenvectors); + + // scale back the eigen values + for (auto& eigenvalue : eigenvalues) { + eigenvalue *= scale; + } + + return {eigenvectors, eigenvalues}; +} +} // namespace mqt::ir::opt From 5982c45eaf76c9b6b87a85d187467c127c75f75d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 22 Oct 2025 11:49:03 +0200 Subject: [PATCH 016/100] fix out-of-bounds issues --- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 16 +++++++++------- mlir/lib/Dialect/MQTOpt/Transforms/b.h | 9 ++------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 3ae7eaa7b..31c0bfcd2 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -252,6 +252,8 @@ template int rowsLhs = lhs.size() / columnsLhs; int rowsRhs = columnsLhs; int columnsRhs = rhs.size() / rowsRhs; + assert(rowsLhs * columnsLhs == lhs.size()); + assert(rowsRhs * columnsRhs == rhs.size()); std::vector result(rowsLhs * columnsRhs, T{}); for (int i = 0; i < rowsLhs; i++) { @@ -364,15 +366,15 @@ auto submatrix(Container&& matrix, int rowStart, int columnStart, int numRows, template auto assignSubmatrix(Lhs&& lhs, Rhs&& rhs, int rowStart, int columnStart, - int rowSize, int columnSize) { + int numRows, int numColumns) { const int n = std::sqrt(lhs.size()); - assert((rowStart + rowSize) < n); - assert((columnStart + columnSize) < n); - assert(columnSize * rowSize == rhs.size()); + assert((rowStart + numRows) <= n); + assert((columnStart + numColumns) <= n); + assert(numColumns * numRows == rhs.size()); - for (int i = 0; i < columnSize; ++i) { - for (int j = 0; j < rowSize; ++j) { - lhs[(rowStart + j) * 4 + (columnStart + i)] = rhs[j * rowSize + i]; + for (int i = 0; i < numColumns; ++i) { + for (int j = 0; j < numRows; ++j) { + lhs[(rowStart + j) * n + (columnStart + i)] = rhs[j * numColumns + i]; } } } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/b.h b/mlir/lib/Dialect/MQTOpt/Transforms/b.h index 6b3af6b29..f8f65d964 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/b.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/b.h @@ -335,12 +335,7 @@ void householderSequenceEval(rmatrix4x4& m_vectors, int shift) { auto essentialVector = [&](const rmatrix4x4& vectors, int k) { int start = k + 1 + shift; - std::vector result; - result.reserve(4 - start); - for (int j = start; j < 4; ++j) { - result.push_back(vectors[j * 4 + k]); - } - return result; + return helpers::submatrix(vectors, start, k, 4 - start, 1); }; auto applyThisOnTheLeft = [&](auto&& vectors, auto& dst) { @@ -375,7 +370,7 @@ void householderSequenceEval(rmatrix4x4& m_vectors, tmp2 = helpers::add( bottom, helpers::multiply(-tau, helpers::multiply(essential, tmp, - essential.size()))); + 1))); // insert all rows except first row llvm::copy(tmp2, matrix.begin() + n); } From 2e8655016a4a55b761c9c27a9cc37242d2fc990a Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 22 Oct 2025 15:51:09 +0200 Subject: [PATCH 017/100] test using Armadillo --- CMakeLists.txt | 18 +++++++++++ .../Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- .../Transforms/GateDecompositionPattern.cpp | 17 ++++++++-- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 21 +++++++++++-- mlir/lib/Dialect/MQTOpt/Transforms/b.h | 2 +- mlir/lib/Dialect/MQTOpt/Transforms/d.h | 31 +++++++++++++++++++ 6 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/d.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cd8e9551..d9bc120c4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,24 @@ include(PackageAddTest) include(Cache) include(AddMQTCoreLibrary) +# include(FetchContent) +# FetchContent_Declare( +# armadillo +# GIT_REPOSITORY https://gitlab.com/conradsnicta/armadillo-code.git +# GIT_TAG 15.0.1 +# ) +# FetchContent_MakeAvailable(armadillo) +find_package(Armadillo 15 REQUIRED) +if(Armadillo_FOUND AND NOT TARGET Armadillo::Armadillo) + add_library(Armadillo::Armadillo INTERFACE IMPORTED) + set_target_properties( + Armadillo::Armadillo + PROPERTIES + INTERFACE_LINK_LIBRARIES "${ARMADILLO_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${ARMADILLO_INCLUDE_DIRS}" + ) +endif() + option(BUILD_MQT_CORE_BINDINGS "Build the MQT Core Python bindings" OFF) if(BUILD_MQT_CORE_BINDINGS) # ensure that the BINDINGS option is set diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index 6badce698..2098ff4af 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD) +set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD Armadillo::Armadillo) add_compile_options(-fexceptions) file(GLOB_RECURSE TRANSFORMS_SOURCES *.cpp) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 959aa2c7e..805fe0839 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -25,7 +25,7 @@ #include #include -#include "b.h" +#include "d.h" namespace mqt::ir::opt { @@ -324,7 +324,7 @@ struct GateDecompositionPattern final auto [U, S] = self_adjoint_evd(A); - return {U, S}; + return std::make_pair(U, S); } static std::tuple @@ -564,6 +564,15 @@ struct GateDecompositionPattern final llvm::errs() << "===== M2 =====\n"; helpers::print(m2); + arma::Mat U(4, 4); + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + U.at(j, i) = u_p[j * 4 + i]; + } + } + auto x = U.st() * U; + std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << std::endl; + // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. // @@ -603,7 +612,7 @@ struct GateDecompositionPattern final llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { return rand_a * val.real() + rand_b * val.imag(); }); - auto p_inner_real = self_adjoint_eigen_lower(m2_real).first; + rmatrix4x4 p_inner_real = self_adjoint_eigen_lower(m2_real).first; matrix4x4 p_inner; llvm::transform(p_inner_real, p_inner.begin(), [](auto&& x) { return qfp(x, 0.0); }); @@ -638,6 +647,7 @@ struct GateDecompositionPattern final std::array d_real; llvm::transform(d, d_real.begin(), [](auto&& x) { return -std::arg(x) / 2.0; }); + helpers::print(d_real, "D_REAL"); d_real[3] = -d_real[0] - d_real[1] - d_real[2]; std::array cs; for (std::size_t i = 0; i < cs.size(); ++i) { @@ -680,6 +690,7 @@ struct GateDecompositionPattern final temp[1 * 4 + 1] = std::exp(IM * d_real[1]); temp[2 * 4 + 2] = std::exp(IM * d_real[2]); temp[3 * 4 + 3] = std::exp(IM * d_real[3]); + helpers::print(temp, "TEMP"); auto k1 = magic_basis_transform(dot(dot(u_p, p), temp), MagicBasisTransform::Into); auto k2 = magic_basis_transform(transpose(p), MagicBasisTransform::Into); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 31c0bfcd2..77c1d12ab 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -40,10 +40,27 @@ constexpr qfp M_IM{0., -1.}; namespace mqt::ir::opt::helpers { // TODO: remove -template void print(std::array, N> matrix) { +template void print(std::array, N> matrix, std::string s = "") { int i{}; + if (!s.empty()) { + llvm::errs() << "=== " << s << " ===\n"; + } + for (auto&& a : matrix) { + std::cerr << std::setprecision(17) << a.real() << 'i' << a.imag() << ' '; + if (++i % 4 == 0) { + llvm::errs() << '\n'; + } + } + llvm::errs() << '\n'; +} + +template void print(std::array matrix, std::string s = "") { + int i{}; + if (!s.empty()) { + llvm::errs() << "=== " << s << " ===\n"; + } for (auto&& a : matrix) { - std::cerr << std::setprecision(50) << a.real() << 'i' << a.imag() << ' '; + std::cerr << std::setprecision(17) << a << ' '; if (++i % 4 == 0) { llvm::errs() << '\n'; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/b.h b/mlir/lib/Dialect/MQTOpt/Transforms/b.h index f8f65d964..3d610ff96 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/b.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/b.h @@ -425,7 +425,7 @@ void householderSequenceEval(rmatrix4x4& m_vectors, } // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen -static std::pair self_adjoint_evd(rmatrix4x4 matrix) { +inline std::pair self_adjoint_evd(rmatrix4x4 matrix) { auto lowerTriangularView = [](auto matrix) { const int n = std::sqrt(matrix.size()); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/d.h b/mlir/lib/Dialect/MQTOpt/Transforms/d.h new file mode 100644 index 000000000..6d2a849f5 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/d.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Helpers.h" + +#include + +namespace mqt::ir::opt { + +auto self_adjoint_evd(rmatrix4x4 A) { + arma::Mat a(4, 4, arma::fill::scalar_holder(0.0)); + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + a.at(j, i) = A[j * 4 + i]; + } + } + arma::Mat vecs; + arma::Col vals; + arma::eig_sym(vals, vecs, a, "std"); + rmatrix4x4 rvecs; + rdiagonal4x4 rvals; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + rvecs[j * 4 + i] = vecs.at(j, i); + } + rvals[i] = vals.at(i); + } + std::cerr << "========\n" << vecs << "========\n" << std::endl; + std::cerr << "========\n" << vals << "========\n" << std::endl; + return std::make_pair(rvecs, rvals); +} +} // namespace mqt::ir::opt From 6b110b88e1f75b1f34034b4b5fd04966d9b8035c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 22 Oct 2025 19:22:44 +0200 Subject: [PATCH 018/100] minor fix --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 805fe0839..a726f9b9a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -1745,21 +1745,21 @@ struct GateDecompositionPattern final const matrix2x2& unitaryMatrix, bool simplify, std::optional atol) { - auto [theta, phi, lamda, phase] = + auto [theta, phi, lambda, phase] = angles_from_unitary(unitaryMatrix, target_basis); switch (target_basis) { case EulerBasis::ZYZ: - return calculateRotationGates(theta, phi, lamda, phase, qc::RZ, qc::RY, + return calculateRotationGates(theta, phi, lambda, phase, qc::RZ, qc::RY, simplify, atol); case EulerBasis::ZXZ: - return calculateRotationGates(theta, phi, lamda, phase, qc::RZ, qc::RX, + return calculateRotationGates(theta, phi, lambda, phase, qc::RZ, qc::RX, simplify, atol); case EulerBasis::XZX: - return calculateRotationGates(theta, phi, lamda, phase, qc::RX, qc::RZ, + return calculateRotationGates(theta, phi, lambda, phase, qc::RX, qc::RZ, simplify, atol); case EulerBasis::XYX: - return calculateRotationGates(theta, phi, lamda, phase, qc::RX, qc::RY, + return calculateRotationGates(theta, phi, lambda, phase, qc::RX, qc::RY, simplify, atol); default: // TODO: allow other bases From 4a6c1bc7921bcdd634748334300b55ffd8bd87b1 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 24 Oct 2025 23:31:15 +0200 Subject: [PATCH 019/100] tmp --- .../Transforms/GateDecompositionPattern.cpp | 124 ++++-------------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 25 +++- 2 files changed, 49 insertions(+), 100 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index a726f9b9a..ea44eb6b9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -230,89 +230,6 @@ struct GateDecompositionPattern final return wrapped; } - // GPT generated - void tridiagonalize_inplace(rmatrix4x4& A) { - auto dot3 = [](auto&& a, auto&& b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - }; - // Householder for column 0 - { - auto x = std::array{A[1 * 4 + 0], A[8 + 0], - A[12 + 0]}; // Elements [1,0], [2,0], [3,0] - auto norm_x = std::sqrt(dot3(x, x)); - if (norm_x > 1e-12) { - auto sign = (x[0] >= 0) ? 1.0 : -1.0; - auto u0 = x[0] + sign * norm_x; - auto denom = std::sqrt(u0 * u0 + x[1] * x[1] + x[2] * x[2]); - auto v = std::array{u0 / denom, x[1] / denom, x[2] / denom}; - - // Apply H = I - 2vvᵀ to A from both sides: A := H A H - // Only affect rows and columns 1-3 - - // temp = A * v - std::array temp = {0, 0, 0}; - for (int i = 1; i < 4; ++i) { - for (int j = 1; j < 4; ++j) { - temp[i - 1] += A[i * 4 + j] * v[j - 1]; - } - } - - // w = 2 * (A*v - (vᵀ*A*v)*v) - auto alpha = dot3(v, temp); - std::array w; - for (int i = 0; i < 3; ++i) - w[i] = 2.0 * (temp[i] - alpha * v[i]); - - // Update A = A - v wᵀ - w vᵀ - for (int i = 1; i < 4; ++i) { - for (int j = i; j < 4; ++j) { - auto delta = v[i - 1] * w[j - 1] + w[i - 1] * v[j - 1]; - A[i * 4 + j] -= delta; - if (i != j) - A[j * 4 + i] -= delta; // symmetry - } - } - } - } - - // Householder for column 1 (submatrix 2x2 at bottom right) - { - auto x = std::array{A[10], A[15]}; // Elements [2,1], [3,1] - auto norm_x = std::sqrt(x[0] * x[0] + x[1] * x[1]); - if (norm_x > 1e-12) { - auto sign = (x[0] >= 0) ? 1.0 : -1.0; - auto u0 = x[0] + sign * norm_x; - auto denom = std::sqrt(u0 * u0 + x[1] * x[1]); - auto v = std::array{u0 / denom, x[1] / denom}; - - // temp = A * v - std::array temp = {0, 0}; - for (int i = 2; i < 4; ++i) { - for (int j = 2; j < 4; ++j) { - temp[i - 2] += A[i * 4 + j] * v[j - 2]; - } - } - - // w = 2 * (A*v - (vᵀ*A*v)*v) - auto alpha = v[0] * temp[0] + v[1] * temp[1]; - std::array w; - for (int i = 0; i < 2; ++i) - w[i] = 2.0 * (temp[i] - alpha * v[i]); - - // Update A = A - v wᵀ - w vᵀ - for (int i = 2; i < 4; ++i) { - for (int j = i; j < 4; ++j) { - auto delta = v[i - 2] * w[j - 2] + w[i - 2] * v[j - 2]; - A[i * 4 + j] -= delta; - if (i != j) - A[j * 4 + i] -= delta; - } - } - } - } - - // Now A is tridiagonal - } // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen static std::pair self_adjoint_eigen_lower(rmatrix4x4 A) { @@ -324,6 +241,14 @@ struct GateDecompositionPattern final auto [U, S] = self_adjoint_evd(A); + // TODO: not in original code + if (helpers::determinant(U) + 1.0 < std::numeric_limits::epsilon()) { + // if determinant of eigenvector matrix is -1.0, multiply first eigenvector by -1.0 + for (int i = 0; i < 4; ++i) { + U[i * 4 + 0] *= -1.0; + } + } + return std::make_pair(U, S); } @@ -333,6 +258,7 @@ struct GateDecompositionPattern final using helpers::kroneckerProduct; using helpers::transpose_conjugate; using helpers::determinant; + helpers::print(special_unitary, "SPECIAL_UNITARY"); // first quadrant matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; @@ -387,6 +313,7 @@ struct GateDecompositionPattern final C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO, C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO, }; + helpers::print(unitary, "UNITARY in MAGIC BASIS TRANSFORM"); if (direction == MagicBasisTransform::OutOf) { return dot(dot(B_NON_NORMALIZED_DAGGER, unitary), B_NON_NORMALIZED); } @@ -564,14 +491,14 @@ struct GateDecompositionPattern final llvm::errs() << "===== M2 =====\n"; helpers::print(m2); - arma::Mat U(4, 4); - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - U.at(j, i) = u_p[j * 4 + i]; - } - } - auto x = U.st() * U; - std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << std::endl; + // arma::Mat U(4, 4); + // for (int i = 0; i < 4; ++i) { + // for (int j = 0; j < 4; ++j) { + // U.at(j, i) = u_p[j * 4 + i]; + // } + // } + // auto x = U.st() * U; + // std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << std::endl; // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -652,8 +579,9 @@ struct GateDecompositionPattern final std::array cs; for (std::size_t i = 0; i < cs.size(); ++i) { assert(i < d_real.size()); - cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::PI_2); + cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::TAU); } + helpers::print(cs, "CS"); decltype(cs) cstemp; llvm::transform(cs, cstemp.begin(), [](auto&& x) { auto tmp = remEuclid(x, qc::PI_2); @@ -662,18 +590,21 @@ struct GateDecompositionPattern final std::array order{0, 1, 2}; llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - std::tie(order[0], order[1], order[2]) = {order[1], order[2], order[0]}; - std::tie(cs[0], cs[1], cs[2]) = {cs[order[0]], cs[order[1]], + helpers::print(order, "ORDER (1)"); + std::tie(order[0], order[1], order[2]) = std::tuple{order[1], order[2], order[0]}; + helpers::print(order, "ORDER (2)"); + std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - std::tie(d_real[0], d_real[1], d_real[2]) = { + std::tie(d_real[0], d_real[1], d_real[2]) = std::tuple{ d_real[order[0]], d_real[order[1]], d_real[order[2]]}; + helpers::print(d_real, "D_REAL (sorted)"); // swap columns of p according to order constexpr auto P_ROW_LENGTH = 4; auto p_orig = p; for (std::size_t i = 0; i < order.size(); ++i) { for (std::size_t row = 0; row < P_ROW_LENGTH; ++row) { - std::swap(p[row * 3 + i], p_orig[row * 3 + order[i]]); + std::swap(p[row * P_ROW_LENGTH + i], p_orig[row * P_ROW_LENGTH + order[i]]); } } @@ -691,6 +622,7 @@ struct GateDecompositionPattern final temp[2 * 4 + 2] = std::exp(IM * d_real[2]); temp[3 * 4 + 3] = std::exp(IM * d_real[3]); helpers::print(temp, "TEMP"); + helpers::print(p, "P"); auto k1 = magic_basis_transform(dot(dot(u_p, p), temp), MagicBasisTransform::Into); auto k2 = magic_basis_transform(transpose(p), MagicBasisTransform::Into); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 77c1d12ab..c323477ef 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -68,6 +68,20 @@ template void print(std::array matrix, std::string s = "" llvm::errs() << '\n'; } +template void print(std::array matrix, std::string s = "") { + int i{}; + if (!s.empty()) { + llvm::errs() << "=== " << s << " ===\n"; + } + for (auto&& a : matrix) { + std::cerr << a << ' '; + if (++i % 4 == 0) { + llvm::errs() << '\n'; + } + } + llvm::errs() << '\n'; +} + inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { std::array, 16> result; for (std::size_t i = 0; i < result.size(); ++i) { @@ -453,11 +467,13 @@ inline auto transpose_conjugate(Container&& matrix) { return result; } -inline qfp determinant(const matrix2x2& mat) { +template +inline T determinant(const std::array& mat) { return mat[0] * mat[3] - mat[1] * mat[2]; } -inline qfp determinant(const std::array& mat) { +template +inline T determinant(const std::array& mat) { return mat[0] * (mat[4] * mat[8] - mat[5] * mat[7]) - mat[1] * (mat[3] * mat[8] - mat[5] * mat[6]) + mat[2] * (mat[3] * mat[7] - mat[4] * mat[6]); @@ -480,9 +496,10 @@ inline std::array get3x3Submatrix(const matrix4x4& mat, return result; } -inline qfp determinant(const matrix4x4& mat) { +template +inline T determinant(const std::array& mat) { auto [l, u, rowPermutations] = helpers::LUdecomposition(mat); - auto det = C_ONE; + T det = 1.0; for (int i = 0; i < 4; ++i) { det *= l[i * 4 + i]; } From 7ef7fde999a53b6ff67d7fb51591796db31c5bc7 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 10:02:41 +0100 Subject: [PATCH 020/100] work on gate application --- .../Transforms/GateDecompositionPattern.cpp | 108 ++++++++++++++---- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index ea44eb6b9..74d9d76f1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -48,7 +48,8 @@ struct GateDecompositionPattern final return mlir::failure(); } - matrix4x4 unitaryMatrix = helpers::kroneckerProduct(identityGate, identityGate); + matrix4x4 unitaryMatrix = + helpers::kroneckerProduct(identityGate, identityGate); for (auto&& gate : series) { auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate), .parameter = {/*TODO*/}, @@ -60,6 +61,7 @@ struct GateDecompositionPattern final auto sequence = decomposer.twoQubitDecompose( unitaryMatrix, DEFAULT_FIDELITY, true, std::nullopt); if (!sequence) { + llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } @@ -150,6 +152,24 @@ struct GateDecompositionPattern final mlir::ValueRange{}); } + template + static OpType createGate(mlir::PatternRewriter& rewriter, + mlir::Location location, mlir::ValueRange inQubits, + mlir::ValueRange ctrlQubits, + std::vector parameters) { + mlir::SmallVector parameterValues; + for (auto&& parameter : parameters) { + auto parameterValue = rewriter.create( + location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); + parameterValues.push_back(parameterValue); + } + + return rewriter.create( + location, inQubits.getType(), ctrlQubits.getType(), mlir::TypeRange{}, + mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, parameterValues, + inQubits, ctrlQubits, mlir::ValueRange{}); + } + struct QubitGateSequence { struct Gate { qc::OpType type{qc::I}; @@ -163,11 +183,40 @@ struct GateDecompositionPattern final using TwoQubitGateSequence = QubitGateSequence; static void applySeries(mlir::PatternRewriter& rewriter, - const llvm::SmallVector& series, + llvm::SmallVector& series, const TwoQubitGateSequence& sequence) { + auto location = series[0]->getLoc(); + auto inQubits = series[0].getAllInQubits(); if (sequence.globalPhase != 0.0) { - createOneParameterGate(rewriter, series[0]->getLoc(), - sequence.globalPhase, {}); + createOneParameterGate(rewriter, location, sequence.globalPhase, + {}); + } + + std::cerr << "GATE SEQUENCE!: " << std::flush; + for (auto&& gate : sequence.gates) { + if (gate.type == qc::X) { + mlir::SmallVector inCtrlQubits; + if (gate.qubit_id.size() > 1) { + inCtrlQubits.push_back(inQubits[gate.qubit_id[1]]); + } + createGate(rewriter, location, {inQubits[0]}, inCtrlQubits, + gate.parameter); + } + if (gate.type == qc::RX) { + mlir::SmallVector qubits; + for (auto&& x : gate.qubit_id) { + qubits.push_back(inQubits[x]); + } + createGate(rewriter, location, qubits, {}, gate.parameter); + } + if (gate.type == qc::RY) { + } + if (gate.type == qc::RZ) { + } + } + + for (auto&& op : series) { + rewriter.replaceOp(op, op.getAllInQubits()); } } @@ -359,6 +408,12 @@ struct GateDecompositionPattern final if (basis == EulerBasis::XYX) { return params_xyx_inner(matrix); } + if (basis == EulerBasis::ZYZ) { + return params_zyz_inner(matrix); + } + if (basis == EulerBasis::ZXZ) { + return params_zxz_inner(matrix); + } throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"}; } @@ -380,20 +435,21 @@ struct GateDecompositionPattern final return {theta, phi, lam, phase}; } + static std::array params_zxz_inner(const matrix2x2& matrix) { + auto [theta, phi, lam, phase] = params_zyz_inner(matrix); + return {theta, phi + qc::PI / 2., lam - qc::PI / 2., phase}; + } + static std::array params_xyx_inner(const matrix2x2& matrix) { auto mat_zyz = std::array{ - static_cast(0.5) * - (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) + - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), - static_cast(0.5) * - (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) + - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * - (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1 * 2 * 2) - - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * - (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1 * 2 * 2) - - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1) + + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1) + + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1) - + matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), + static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1) - + matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), }; auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); auto new_phi = mod2pi(phi + qc::PI, 0.); @@ -472,10 +528,10 @@ struct GateDecompositionPattern final static TwoQubitWeylDecomposition new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { - using helpers::dot; using helpers::determinant; - using helpers::transpose; using helpers::diagonal; + using helpers::dot; + using helpers::transpose; auto& u = unitary_matrix; auto det_u = determinant(u); auto det_pow = std::pow(det_u, static_cast(-0.25)); @@ -591,12 +647,13 @@ struct GateDecompositionPattern final llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); helpers::print(order, "ORDER (1)"); - std::tie(order[0], order[1], order[2]) = std::tuple{order[1], order[2], order[0]}; + std::tie(order[0], order[1], order[2]) = + std::tuple{order[1], order[2], order[0]}; helpers::print(order, "ORDER (2)"); - std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], - cs[order[2]]}; - std::tie(d_real[0], d_real[1], d_real[2]) = std::tuple{ - d_real[order[0]], d_real[order[1]], d_real[order[2]]}; + std::tie(cs[0], cs[1], cs[2]) = + std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; + std::tie(d_real[0], d_real[1], d_real[2]) = + std::tuple{d_real[order[0]], d_real[order[1]], d_real[order[2]]}; helpers::print(d_real, "D_REAL (sorted)"); // swap columns of p according to order @@ -604,7 +661,8 @@ struct GateDecompositionPattern final auto p_orig = p; for (std::size_t i = 0; i < order.size(); ++i) { for (std::size_t row = 0; row < P_ROW_LENGTH; ++row) { - std::swap(p[row * P_ROW_LENGTH + i], p_orig[row * P_ROW_LENGTH + order[i]]); + std::swap(p[row * P_ROW_LENGTH + i], + p_orig[row * P_ROW_LENGTH + order[i]]); } } @@ -1339,7 +1397,7 @@ struct GateDecompositionPattern final decomp0_inner(const TwoQubitWeylDecomposition& target) const { using helpers::dot; return { - dot(target.K1r, target.K2r), + dot(target.K1r, target.K2r), dot(target.K1l, target.K2l), }; } From 0254818f27a9d4e42d9348da0e53f959dcbe6b1f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 10:04:02 +0100 Subject: [PATCH 021/100] use laughing-umbrella implementation for decompose_two_qubit_product_gate --- .../Transforms/GateDecompositionPattern.cpp | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 74d9d76f1..847de97f6 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -303,54 +303,65 @@ struct GateDecompositionPattern final static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { - using helpers::dot; - using helpers::kroneckerProduct; - using helpers::transpose_conjugate; - using helpers::determinant; - helpers::print(special_unitary, "SPECIAL_UNITARY"); - // first quadrant - matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], - special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; - auto det_r = determinant(r); - if (std::abs(det_r) < 0.1) { - // third quadrant - r = {special_unitary[2 * 4 + 0], special_unitary[2 * 4 + 1], - special_unitary[3 * 4 + 0], special_unitary[3 * 4 + 1]}; - det_r = determinant(r); - } - if (std::abs(det_r) < 0.1) { - throw std::runtime_error{ - "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; - } - llvm::transform(r, r.begin(), - [&](auto&& x) { return x / std::sqrt(det_r); }); - // transpose with complex conjugate of each element - matrix2x2 r_t_conj = transpose_conjugate(r); - - auto temp = kroneckerProduct(identityGate, r_t_conj); - temp = dot(special_unitary, temp); - - // [[a, b, c, d], - // [e, f, g, h], => [[a, c], - // [i, j, k, l], [i, k]] - // [m, n, o, p]] - matrix2x2 l = {temp[0 * 4 + 0], temp[0 * 4 + 2], temp[2 * 4 + 0], - temp[2 * 4 + 2]}; - auto det_l = determinant(l); - if (std::abs(det_l) < 0.9) { + using helpers::determinant; + using helpers::dot; + using helpers::kroneckerProduct; + using helpers::transpose_conjugate; + helpers::print(special_unitary, "SPECIAL_UNITARY"); + + auto* it = llvm::max_element(special_unitary, [](auto&& a, auto&& b) { + return std::abs(a) < std::abs(b); + }); + if (it == special_unitary.end()) { throw std::runtime_error{ - "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9"}; + "No maximal element in decompose_two_qubit_product_gate!"}; } - llvm::transform(l, l.begin(), - [&](auto&& x) { return x / std::sqrt(det_l); }); - auto phase = std::arg(det_l) / 2.; + auto pos = std::distance(special_unitary.begin(), it); + int i = pos % 4; + int j = pos / 4; - return {l, r, phase}; + auto u1_set = [](int i) { + if (i % 2 == 0) { + return std::make_pair(0, 2); + } + return std::make_pair(1, 3); + }; + + auto u2_set = [](int i) { + if (i < 2) { + return std::make_pair(0, 1); + } + return std::make_pair(2, 3); + }; + + auto to_su = [](auto&& u) { + return helpers::multiply(std::pow(qfp(determinant(u)), -0.25), u); + }; + + matrix2x2 u1; + u1[0 * 2 + 0] = special_unitary[u1_set(i).first * 4 + u1_set(j).first]; + u1[0 * 2 + 1] = special_unitary[u1_set(i).first * 4 + u1_set(j).second]; + u1[1 * 2 + 0] = special_unitary[u1_set(i).second * 4 + u1_set(j).first]; + u1[1 * 2 + 1] = special_unitary[u1_set(i).second * 4 + u1_set(j).second]; + matrix2x2 u2; + u1[0 * 2 + 0] = special_unitary[u2_set(i).first * 4 + u2_set(j).first]; + u1[0 * 2 + 1] = special_unitary[u2_set(i).first * 4 + u2_set(j).second]; + u1[1 * 2 + 0] = special_unitary[u2_set(i).second * 4 + u2_set(j).first]; + u1[1 * 2 + 1] = special_unitary[u2_set(i).second * 4 + u2_set(j).second]; + + u1 = to_su(u1); + u2 = to_su(u2); + + std::cerr << i << ',' << j << std::endl; + auto phase = special_unitary[i * 4 + j] / + (u1[(i / 2) * 2 + (j / 2)] * u2[(i % 2) * 2 + (j % 2)]); + + return {u1, u2, phase.real()}; // TODO: return only real part? } static matrix4x4 magic_basis_transform(const matrix4x4& unitary, MagicBasisTransform direction) { - using helpers::dot; + using helpers::dot; constexpr matrix4x4 B_NON_NORMALIZED = { C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, From e5f659ed96db8d315b9d90aec39b9e793e00c46e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 10:05:13 +0100 Subject: [PATCH 022/100] add more eigen decomposition implementations --- .../Transforms/GateDecompositionPattern.cpp | 3 +- mlir/lib/Dialect/MQTOpt/Transforms/a.h | 82 ++ mlir/lib/Dialect/MQTOpt/Transforms/c.h | 142 +++ mlir/lib/Dialect/MQTOpt/Transforms/e.h | 976 ++++++++++++++++++ mlir/lib/Dialect/MQTOpt/Transforms/f.h | 97 ++ mlir/lib/Dialect/MQTOpt/Transforms/g.h | 32 + 6 files changed, 1330 insertions(+), 2 deletions(-) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/a.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/c.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/e.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/f.h create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/g.h diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 847de97f6..27c4d4306 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -9,6 +9,7 @@ */ #include "Helpers.h" +#include "g.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" @@ -25,8 +26,6 @@ #include #include -#include "d.h" - namespace mqt::ir::opt { /** diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/a.h b/mlir/lib/Dialect/MQTOpt/Transforms/a.h new file mode 100644 index 000000000..57e4678a3 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/a.h @@ -0,0 +1,82 @@ +#pragma once + +#include "Helpers.h" + +namespace mqt::ir::opt { + +// Function to compute the norm (Frobenius norm) of a matrix +double norm(const std::array& A) { + double sum = 0.0; + for (size_t i = 0; i < 16; ++i) { + sum += A[i] * A[i]; + } + return sqrt(sum); +} + +// Function to perform a Jacobi rotation +void jacobi_rotate(std::array& A, std::array& V, int p, int q) { + // Compute the Jacobi rotation matrix + double theta = 0.5 * atan2(2 * A[p * 4 + q], A[q * 4 + q] - A[p * 4 + p]); + double c = cos(theta); + double s = sin(theta); + + // Apply the rotation to matrix A + for (int i = 0; i < 4; ++i) { + double ap = A[i * 4 + p]; + double aq = A[i * 4 + q]; + A[i * 4 + p] = c * ap - s * aq; + A[i * 4 + q] = s * ap + c * aq; + } + + // Apply the rotation to matrix V (eigenvectors) + for (int i = 0; i < 4; ++i) { + double vi_p = V[i * 4 + p]; + double vi_q = V[i * 4 + q]; + V[i * 4 + p] = c * vi_p - s * vi_q; + V[i * 4 + q] = s * vi_p + c * vi_q; + } +} + +// Function to perform the Jacobi method for eigenvalue decomposition +void jacobi_eigen_decomposition(std::array& A, std::array& V, std::array& eigenvalues, double tolerance = 1e-9, int max_iterations = 1000) { + // Initialize the eigenvector matrix V to identity + V.fill(0.0); + for (int i = 0; i < 4; ++i) { + V[i * 4 + i] = 1.0; + } + + int iterations = 0; + double off_diagonal_norm = norm(A); + + // Jacobi rotation iterations + while (off_diagonal_norm > tolerance && iterations < max_iterations) { + // Find the largest off-diagonal element + double max_off_diag = 0.0; + int p = 0, q = 0; + for (int i = 0; i < 3; ++i) { + for (int j = i + 1; j < 4; ++j) { + double abs_val = fabs(A[i * 4 + j]); + if (abs_val > max_off_diag) { + max_off_diag = abs_val; + p = i; + q = j; + } + } + } + + // Perform a Jacobi rotation if necessary + if (max_off_diag > tolerance) { + jacobi_rotate(A, V, p, q); + } + + // Update the off-diagonal norm + off_diagonal_norm = norm(A); + ++iterations; + } + + // Extract the eigenvalues from the diagonal of A + for (int i = 0; i < 4; ++i) { + eigenvalues[i] = A[i * 4 + i]; + } +} +} diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/c.h b/mlir/lib/Dialect/MQTOpt/Transforms/c.h new file mode 100644 index 000000000..315ed0a69 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/c.h @@ -0,0 +1,142 @@ +#pragma once + +#include "Helpers.h" + +namespace mqt::ir::opt { +// return Q, R such that A = Q * R +static void qrDecomposition(const rmatrix4x4& A, rmatrix4x4& Q, rmatrix4x4& R) { + // array of factor Q1, Q2, ... Qm + std::vector qv(4); + + // temp array + auto z(A); + rmatrix4x4 z1; + + auto vmadd = [](const auto& a, const auto& b, double s, auto& c) { + for (int i = 0; i < 4; i++) + c[i] = a[i] + s * b[i]; + }; + + auto compute_householder_factor = [](rmatrix4x4& mat, const rdiagonal4x4& v) { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + mat[i + 4 * j] = -2.0 * v[i] * v[j]; + for (int i = 0; i < 4; i++) + mat[i * 4 + i] += 1; + }; + + // take c-th column of a matrix, put results in Vector v + auto extract_column = [](const rmatrix4x4& m, rdiagonal4x4& v, int c) { + for (int i = 0; i < 4; i++) + v[i] = m[i + 4 * c]; + }; + + auto compute_minor = [](rmatrix4x4& lhs, const rmatrix4x4& rhs, int d) { + for (int i = 0; i < d; i++) + lhs[i * 4 + i] = 1.0; + for (int i = d; i < 4; i++) + for (int j = d; j < 4; j++) + lhs[i + 4 * j] = rhs[i + 4 * j]; + }; + + auto norm = [](auto&& m) { + double sum = 0; + for (int i = 0; i < m.size(); i++) + sum += m[i] * m[i]; + return sqrt(sum); + }; + + auto rescale_unit = [&](auto& m) { + auto factor = norm(m); + for (int i = 0; i < m.size(); i++) + m[i] /= factor; + }; + + for (int k = 0; k < 4 && k < 4 - 1; k++) { + + rdiagonal4x4 e{}, x{}; + double a{}; + + // compute minor + compute_minor(z1, z, k); + + // extract k-th column into x + extract_column(z1, x, k); + + a = norm(x); + if (A[k * 4 + k] > 0) + a = -a; + + for (int i = 0; i < 4; i++) + e[i] = (i == k) ? 1 : 0; + + // e = x + a*e + vmadd(x, e, a, e); + + // e = e / ||e|| + rescale_unit(e); + + // qv[k] = I - 2 *e*e^T + compute_householder_factor(qv[k], e); + + // z = qv[k] * z1 + z = helpers::multiply(qv[k], z1); + } + + Q = qv[0]; + + // after this loop, we will obtain Q (up to a transpose operation) + for (int i = 1; i < 4 && i < 4 - 1; i++) { + + z1 = helpers::multiply(qv[i], Q); + Q = z1; + } + + R = helpers::multiply(Q, A); + Q = helpers::transpose(Q); +} + +// Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) +static rmatrix4x4 // eigenvectors (4x4) +self_adjoint_evd(rmatrix4x4 A, // input symmetric matrix (4x4) + rdiagonal4x4& s // eigenvalues +) { + rmatrix4x4 U = {1, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 1}; // Start with identity + + auto isConverged = [](const rmatrix4x4& A, double tol = 1e-10) -> bool { + double sum = 0.0; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + if (i != j) + sum += A[i + 4 * j] * A[i + 4 * j]; + } + } + return std::sqrt(sum) < tol; + }; + + rmatrix4x4 Q{}; + rmatrix4x4 R{}; + + constexpr auto maxIters = 100; + for (int iter = 0; iter < maxIters; ++iter) { + qrDecomposition(A, Q, R); + + // A = R * Q + A = helpers::multiply(R, Q); + + // eigenVectors = eigenVectors * Q + U = helpers::multiply(U, Q); + + if (isConverged(A)) { + break; + } + } + + for (int i = 0; i < 4; ++i) { + s[i] = A[i * 4 + i]; + } + return U; +} + +} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/e.h b/mlir/lib/Dialect/MQTOpt/Transforms/e.h new file mode 100644 index 000000000..d407422a0 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/e.h @@ -0,0 +1,976 @@ +// +// Analytical Eigen-decomposition for 4x4 and 3x3 matrices +// +// +// Robin Straebler - George Terzakis +// +// University of Portsmouth 2016 +// + +#ifndef __EIGENDECOMPOSE_H__ +#define __EIGENDECOMPOSE_H__ + +#include "Helpers.h" + +#include +#include +#include +#include +#include +#include + +// The 4x4 implies that the namespace contains algorithms +// for the eigen decomposition of 3x3 or 4x4 matrices. +// +namespace mqt::ir::opt { + +namespace PolySolvers { + +// see http://mathworld.wolfram.com/QuadraticFormula.html +template inline std::vector

SolveQuadratic(P a, P b, P c) { + P delta = b * b - 4 * a * c; + P inv_2a, sqrt_delta; + P x1, x0; + + if (delta < 0) + return std::vector

(); + if (a == 0) { + // solve first order system + x1 = 0; + if (b != 0) { + x0 = -c / b; + return std::vector

({x0}); + } + + x0 = 0; + return std::vector

(); + } + + inv_2a = 0.5 / a; + if (delta == 0) { + x0 = -b * inv_2a; + x1 = x0; + return std::vector

({x0}); + } + + sqrt_delta = sqrt(delta); + x0 = (-b + sqrt_delta) * inv_2a; + x1 = (-b - sqrt_delta) * inv_2a; + return std::vector

({x0, x1}); +} + +/* see http://mathworld.wolfram.com/CubicEquation.html */ +template inline std::vector

SolveCubic(P a, P b, P c, P d) { + P inv_a, b_a, b_a2, c_a, d_a; + P Q, R, Q3, D, b_a_3; + P AD, BD; + + P x0, x1, x2; + + if (a == 0) { + /* solve second order system */ + if (b == 0) { + /* solve first order system */ + if (c == 0) + return std::vector

(); + + x0 = -d / c; + return std::vector

({x0}); + } + + x2 = 0; + return SolveQuadratic

(b, c, d); + } + + /* calculate the normalized form x^3 + a2 * x^2 + a1 * x + a0 = 0 */ + inv_a = 1.0 / a; + b_a = inv_a * b; + b_a2 = b_a * b_a; + c_a = inv_a * c; + d_a = inv_a * d; + + /* solve the cubic equation */ + Q = (3 * c_a - b_a2) / 9; + R = (9 * b_a * c_a - 27 * d_a - 2 * b_a * b_a2) / 54; + Q3 = Q * Q * Q; + D = Q3 + R * R; + b_a_3 = (1.0 / 3.0) * b_a; + + if (Q == 0) { + if (R == 0) { + x0 = x1 = x2 = -b_a_3; + return std::vector

({x0, x1, x2}); + } else { + x0 = pow(2 * R, 1 / 3.0) - b_a_3; + return std::vector

({x0}); + } + } + + if (D <= 0) { + /* three real roots */ + P theta = acos(R / sqrt(-Q3)); + P sqrt_Q = sqrt(-Q); + x0 = 2 * sqrt_Q * cos(theta / 3.0) - b_a_3; + x1 = 2 * sqrt_Q * cos((theta + 2 * M_PI) / 3.0) - b_a_3; + x2 = 2 * sqrt_Q * cos((theta + 4 * M_PI) / 3.0) - b_a_3; + + return std::vector

({x0, x1, x2}); + } + + /* D > 0, only one real root */ + AD = pow(fabs(R) + sqrt(D), 1.0 / 3.0) * (R > 0 ? 1 : (R < 0 ? -1 : 0)); + BD = (AD == 0) ? 0 : -Q / AD; + + /* calculate the sole real root */ + x0 = AD + BD - b_a_3; + + return std::vector

({x0}); +} + +/* see http://mathworld.wolfram.com/QuarticEquation.html */ +template +inline std::vector

SolveQuartic(P a, P b, P c, P d, P e) { + P inv_a, b2, bc, b3, b_4; + P r0; + int nb_real_roots; + P R2, R_2, R, inv_R; + P D2, E2; + + P x0 = 0, x1 = 0, x2 = 0, x3 = 0; + + if (a == 0) { + x3 = 0; + return SolveCubic

(b, c, d, e); + } + + /* normalize coefficients */ + inv_a = 1.0 / a; + b *= inv_a; + c *= inv_a; + d *= inv_a; + e *= inv_a; + b2 = b * b; + bc = b * c; + b3 = b2 * b; + + /* solve resultant cubic */ + auto solution3 = + SolveCubic

(1, -c, d * b - 4 * e, 4 * c * e - d * d - b2 * e); + int n = (solution3.size() == 0) ? 0 : solution3.size(); + + if (n == 0) + return solution3; + + r0 = solution3[0]; + /* calculate R^2 */ + R2 = 0.25 * b2 - c + r0; + if (R2 < 0) + return std::vector

(); + + R = sqrt(R2); + inv_R = 1.0 / R; + + nb_real_roots = 0; + + /* calculate D^2 and E^2 */ + if (R < 10E-12) { + P temp = r0 * r0 - 4 * e; + if (temp < 0) { + D2 = E2 = -1; + } else { + P sqrt_temp = sqrt(temp); + D2 = 0.75 * b2 - 2 * c + 2 * sqrt_temp; + E2 = D2 - 4 * sqrt_temp; + } + } else { + P u = 0.75 * b2 - 2 * c - R2, v = 0.25 * inv_R * (4 * bc - 8 * d - b3); + D2 = u + v; + E2 = u - v; + } + + b_4 = 0.25 * b; + R_2 = 0.5 * R; + if (D2 >= 0) { + P D = sqrt(D2); + P D_2 = 0.5 * D; + nb_real_roots = 2; + x0 = R_2 + D_2 - b_4; + x1 = x0 - D; + } + + /* calculate E^2 */ + if (E2 >= 0) { + P E = sqrt(E2); + P E_2 = 0.5 * E; + if (nb_real_roots == 0) { + x0 = -R_2 + E_2 - b_4; + x1 = x0 - E; + nb_real_roots = 2; + } else { + x2 = -R_2 + E_2 - b_4; + x3 = x2 - E; + nb_real_roots = 4; + } + } + switch (nb_real_roots) { + // case 0: + // return new Tuple(0, null); + // break; // covered by the "default" case + case 1: + return std::vector

({x0}); + // break; + case 2: + return std::vector

({x0, x1}); + // break; + case 3: + return std::vector

({x0, x1, x2}); + // break; + case 4: + return std::vector

({x0, x1, x2, x3}); + // break; + default: + return std::vector

(); // just to shut the compiler up.... + // break; + } +} + +} // end namespace PolySolvers + +///

+/// This function performs two steps of Gauss-Jordan elmination in a 3x3 matrix. +/// NOTE: The function assumes that the matrix provided is RANK-2 and therefore +/// the solution +// will be the null space (vector). This way we obtain an eigen vector in +// two steps. +/// +/// is the coefficient matrix in flat +/// form std::vector

solutions +template std::vector

GaussJordan3x3(std::vector

flat_mat) { + // For the first step of the Gauss-Jordan Pivoting, we need the max of the + // coefficient from list0Pivot + // + auto absmax1it = std::max_element( + flat_mat.begin(), flat_mat.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + + P max1 = *absmax1it; + + // Get the index of max (in absolute value) + int index1 = std::distance(flat_mat.begin(), absmax1it); + + // If max1 = 0, we have a zero matrix and we need to return an empty list + if (max1 == 0) + return std::vector

(); + + // For the second step of the Gauss-Jordan Pivoting, we will require the + // maximum of the elements of the resulting matrix. + P max2; // The value of the absolute max in the next stage of the elimination + // (only need 2 stages for 3x3 matrices) + int index2; // the index of the absolute maximum in the next stage of the + // elimination + std::vector

flat_mat1; // and the next matrix + + // Variable which contain the coordinates of a vector + P x1 = 0, x2 = 0, x3 = 0; + + P* pX[3]; // using a pointer array to store permutations of x1, x2, x3 + P listFinalCoef[2]; + + // Taking cases according to where the maximum lies + if (index1 == 0) { + // List of the value after the first step of the Gauss-Jordan Pivoting + flat_mat1.push_back(flat_mat[4] - ((flat_mat[1] * flat_mat[3]) / max1)); + flat_mat1.push_back(flat_mat[5] - ((flat_mat[2] * flat_mat[3]) / max1)); + flat_mat1.push_back(flat_mat[7] - ((flat_mat[1] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[8] - ((flat_mat[2] * flat_mat[6]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x1; + pX[1] = &x2; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[1]; + listFinalCoef[1] = flat_mat[2]; + + } else if (index1 == 1) { + flat_mat1.push_back(flat_mat[3] - ((flat_mat[0] * flat_mat[4]) / max1)); + flat_mat1.push_back(flat_mat[5] - ((flat_mat[2] * flat_mat[4]) / max1)); + flat_mat1.push_back(flat_mat[6] - ((flat_mat[0] * flat_mat[7]) / max1)); + flat_mat1.push_back(flat_mat[8] - ((flat_mat[2] * flat_mat[7]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x2; + pX[1] = &x1; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[0]; + listFinalCoef[1] = flat_mat[2]; + + } else if (index1 == 2) { + flat_mat1.push_back(flat_mat[3] - ((flat_mat[0] * flat_mat[5]) / max1)); + flat_mat1.push_back(flat_mat[4] - ((flat_mat[1] * flat_mat[5]) / max1)); + flat_mat1.push_back(flat_mat[6] - ((flat_mat[0] * flat_mat[8]) / max1)); + flat_mat1.push_back(flat_mat[7] - ((flat_mat[1] * flat_mat[8]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x3; + pX[1] = &x1; + pX[2] = &x2; + + listFinalCoef[0] = flat_mat[0]; + listFinalCoef[1] = flat_mat[1]; + + } else if (index1 == 3) { + flat_mat1.push_back(flat_mat[1] - ((flat_mat[4] * flat_mat[0]) / max1)); + flat_mat1.push_back(flat_mat[2] - ((flat_mat[0] * flat_mat[5]) / max1)); + flat_mat1.push_back(flat_mat[7] - ((flat_mat[4] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[8] - ((flat_mat[5] * flat_mat[6]) / max1)); + + auto max2it = std::max_element(flat_mat1.begin(), flat_mat1.end()); + auto min2it = std::min_element(flat_mat1.begin(), flat_mat1.end()); + max2 = *max2it; + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x1; + pX[1] = &x2; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[4]; + listFinalCoef[1] = flat_mat[5]; + + } else if (index1 == 4) { + flat_mat1.push_back(flat_mat[0] - ((flat_mat[1] * flat_mat[3]) / max1)); + flat_mat1.push_back(flat_mat[2] - ((flat_mat[5] * flat_mat[1]) / max1)); + flat_mat1.push_back(flat_mat[6] - ((flat_mat[3] * flat_mat[7]) / max1)); + flat_mat1.push_back(flat_mat[8] - ((flat_mat[5] * flat_mat[7]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x2; + pX[1] = &x1; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[3]; + listFinalCoef[1] = flat_mat[5]; + + } else if (index1 == 5) { + flat_mat1.push_back(flat_mat[0] - ((flat_mat[2] * flat_mat[3]) / max1)); + flat_mat1.push_back(flat_mat[1] - ((flat_mat[2] * flat_mat[4]) / max1)); + flat_mat1.push_back(flat_mat[6] - ((flat_mat[8] * flat_mat[3]) / max1)); + flat_mat1.push_back(flat_mat[7] - ((flat_mat[8] * flat_mat[4]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x3; + pX[1] = &x1; + pX[2] = &x2; + + listFinalCoef[0] = flat_mat[3]; + listFinalCoef[1] = flat_mat[4]; + + } else if (index1 == 6) { + flat_mat1.push_back(flat_mat[1] - ((flat_mat[0] * flat_mat[7]) / max1)); + flat_mat1.push_back(flat_mat[2] - ((flat_mat[0] * flat_mat[8]) / max1)); + flat_mat1.push_back(flat_mat[4] - ((flat_mat[3] * flat_mat[7]) / max1)); + flat_mat1.push_back(flat_mat[5] - ((flat_mat[3] * flat_mat[8]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x1; + pX[1] = &x2; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[7]; + listFinalCoef[1] = flat_mat[8]; + + } else if (index1 == 7) { + flat_mat1.push_back(flat_mat[0] - ((flat_mat[1] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[2] - ((flat_mat[1] * flat_mat[8]) / max1)); + flat_mat1.push_back(flat_mat[3] - ((flat_mat[4] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[5] - ((flat_mat[4] * flat_mat[8]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x2; + pX[1] = &x1; + pX[2] = &x3; + + listFinalCoef[0] = flat_mat[6]; + listFinalCoef[1] = flat_mat[8]; + + } else if (index1 == 8) { + flat_mat1.push_back(flat_mat[0] - ((flat_mat[2] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[1] - ((flat_mat[2] * flat_mat[7]) / max1)); + flat_mat1.push_back(flat_mat[3] - ((flat_mat[5] * flat_mat[6]) / max1)); + flat_mat1.push_back(flat_mat[4] - ((flat_mat[5] * flat_mat[7]) / max1)); + + // Get maximum and minimum elements (in order to get the maximum in absolute + // value) + auto absmax2it = std::max_element( + flat_mat1.begin(), flat_mat1.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + max2 = *absmax2it; + + // NOTE: Now max2it points to the absolute maximum ! + index2 = std::distance(flat_mat1.begin(), absmax2it); + + pX[0] = &x3; + pX[1] = &x1; + pX[2] = &x2; + + listFinalCoef[0] = flat_mat[6]; + listFinalCoef[1] = flat_mat[7]; + } + + if (index2 == 0) { + *(pX[2]) = 1; + *(pX[1]) = -flat_mat1[1] / max2; + } else if (index2 == 1) { + *(pX[2]) = -flat_mat1[0] / max2; + *(pX[1]) = 1; + } else if (index2 == 2) { + *(pX[2]) = 1; + *(pX[1]) = -flat_mat1[3] / max2; + } else if (index2 == 3) { + *(pX[2]) = -flat_mat1[2] / max2; + *(pX[1]) = 1; + } + + *(pX[0]) = -((listFinalCoef[0] * *(pX[1])) / max1) - + ((listFinalCoef[1] * *(pX[2])) / max1); + + // normalize + P norm = sqrt(x1 * x1 + x2 * x2 + x3 * x3); + + return std::vector

({x1 / norm, x2 / norm, x3 / norm}); +} + +/// +/// This function eliminates the coefficients of the column "col" based on an +/// element at (row, col) +/// +/// +template +std::vector

GaussJordanFirstStep(std::vector

flat_mat, int row, int col) { + + // the result + std::vector

result; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + + if (i != row && j != col) { + result.push_back(flat_mat[i * 4 + j] - + ((flat_mat[row * 4 + j] * flat_mat[i * 4 + col]) / + flat_mat[row * 4 + col])); + } + } + } + + return result; +} + +/// +/// The Gauss-Jordan elimination for Rank-3 4x4 matrices +/// The steps are fixed and lead to a solution up to arbitrary scale. +/// This is how we solve for the eigenvectors of the a 4x4 marix. +/// +/// The input vector is a flat version of the 4x4 matrix +/// +template std::vector

GaussJordan4x4(std::vector

flat_mat) { + // Working out the the index of the max coefficient (absolute value). + auto absmax1it = std::max_element( + flat_mat.begin(), flat_mat.end(), + [](const int& a, const int& b) { return fabs(a) < fabs(b); }); + + P max1 = *absmax1it; + int index1 = std::distance(flat_mat.begin(), absmax1it); + + // Return empty list if the maximum is zero (zero matrix) + if (max1 == 0) + return std::vector

(); + + // Create new variable which are use after. + std::vector

flat_mat1; + + P x1 = 0, x2 = 0, x3 = 0, x4 = 0; + // Cache for the result of 3x3 Gauss Jordan elimination + std::vector

resultGaussJordan3x3; + + // go... + if (index1 == 0) { + /// We use the GaussJordan3x3 solver after the first step, which is the + /// elimination of coeffcieints along column 0. + flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 0); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x2 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x1 = -(1 / flat_mat[0]) * + (flat_mat[1] * x2 + flat_mat[2] * x3 + flat_mat[3] * x4); + } else if (index1 == 1) { + /// We use the GaussJordan3x3 solver after the first step, which is the + /// elimination of coeffcieints along column 1. + flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 1); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x2 = -(1 / flat_mat[1]) * + (flat_mat[0] * x1 + flat_mat[2] * x3 + flat_mat[3] * x4); + } else if (index1 == 2) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 2); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x3 = -(1 / flat_mat[2]) * + (flat_mat[0] * x1 + flat_mat[1] * x2 + flat_mat[3] * x4); + } else if (index1 == 3) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 3); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x3 = resultGaussJordan3x3[2]; + x4 = -(1 / flat_mat[3]) * + (flat_mat[0] * x1 + flat_mat[1] * x2 + flat_mat[2] * x3); + } else if (index1 == 4) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 0); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x2 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x1 = -(1 / flat_mat[4]) * + (flat_mat[5] * x2 + flat_mat[6] * x3 + flat_mat[7] * x4); + } else if (index1 == 5) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 1); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x2 = -(1 / flat_mat[5]) * + (flat_mat[4] * x1 + flat_mat[6] * x3 + flat_mat[7] * x4); + } else if (index1 == 6) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 2); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x3 = -(1 / flat_mat[6]) * + (flat_mat[4] * x1 + flat_mat[5] * x2 + flat_mat[7] * x4); + } else if (index1 == 7) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 3); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x3 = resultGaussJordan3x3[2]; + x4 = -(1 / flat_mat[7]) * + (flat_mat[4] * x1 + flat_mat[5] * x2 + flat_mat[6] * x3); + } else if (index1 == 8) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 0); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x2 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x1 = -(1 / flat_mat[8]) * + (flat_mat[9] * x2 + flat_mat[10] * x3 + flat_mat[11] * x4); + } else if (index1 == 9) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 1); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x2 = -(1 / flat_mat[9]) * + (flat_mat[8] * x1 + flat_mat[10] * x3 + flat_mat[11] * x4); + } else if (index1 == 10) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 2); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x3 = -(1 / flat_mat[10]) * + (flat_mat[8] * x1 + flat_mat[9] * x2 + flat_mat[11] * x4); + } else if (index1 == 11) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 3); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x3 = resultGaussJordan3x3[2]; + x4 = -(1 / flat_mat[11]) * + (flat_mat[8] * x1 + flat_mat[9] * x2 + flat_mat[10] * x3); + } else if (index1 == 12) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 0); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x2 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x1 = -(1 / flat_mat[12]) * + (flat_mat[13] * x2 + flat_mat[14] * x3 + flat_mat[15] * x4); + + } else if (index1 == 13) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 1); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x3 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x2 = -(1 / flat_mat[13]) * + (flat_mat[12] * x1 + flat_mat[14] * x3 + flat_mat[15] * x4); + } else if (index1 == 14) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 2); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x4 = resultGaussJordan3x3[2]; + x3 = -(1 / flat_mat[14]) * + (flat_mat[12] * x1 + flat_mat[13] * x2 + flat_mat[15] * x4); + } else if (index1 == 15) { + flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 3); + resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); + x1 = resultGaussJordan3x3[0]; + x2 = resultGaussJordan3x3[1]; + x3 = resultGaussJordan3x3[2]; + x4 = -(1 / flat_mat[15]) * + (flat_mat[12] * x1 + flat_mat[13] * x2 + flat_mat[14] * x3); + } + + // normalize the solution + P norm = sqrt(x1 * x1 + x2 * x2 + x3 * x3 + x4 * x4); + + return std::vector

({x1 / norm, x2 / norm, x3 / norm, x4 / norm}); +} + +/// +/// Return the list of the eigenvalue of a 4x4 matrix. +/// Parameter is he argumen is a 1D array representing the 4x4 matrix +/// T +/// +template std::vector

EigenValues4x4(P* array) { + + P a11 = array[0 * 4 + 0], a12 = array[0 * 4 + 1], a13 = array[0 * 4 + 2], + a14 = array[0 * 4 + 3]; + P a21 = array[1 * 4 + 0], a22 = array[1 * 4 + 1], a23 = array[1 * 4 + 2], + a24 = array[1 * 4 + 3]; + P a31 = array[2 * 4 + 0], a32 = array[2 * 4 + 1], a33 = array[2 * 4 + 2], + a34 = array[2 * 4 + 3]; + P a41 = array[3 * 4 + 0], a42 = array[3 * 4 + 1], a43 = array[3 * 4 + 2], + a44 = array[3 * 4 + 3]; + + // 1. Obtaining the coefficients of the characteristics polynomial of A11 - + // λ*I where A11 is the (1, 1) submatrix of A: A11 = [a22-λ a23 a24; + // a32 a33-λ a34; + // a42 a43 a44 - λ] + + // The coefficients of the cubic polynomial det(A11-λI) are: + P C3 = -1, C2 = a22 + a33 + a44; + P C1 = a42 * a24 + a32 * a23 + a34 * a43 - a22 * a33 - a22 * a44 - a33 * a44; + P C0 = a22 * a33 * a44 + a23 * a42 * a34 + a24 * a32 * a43 - a22 * a34 * a43 - + a23 * a32 * a44 - a24 * a42 * a33; + + // 2. Now multiplying with a11 to get a quartic polynomial with coeffcients + // W4, W3, W2, W1, W0 as follows: + P W4 = -C3, W3 = a11 * C3 - C2, W2 = a11 * C2 - C1, W1 = a11 * C1 - C0, + W0 = a11 * C0; + + // 4. Now we obtain the coefficients of the 3 quadratics (from the algebraic + // complements A12, A13, A14) as follows: a. (A-λI)12 (-) + P Q1_2 = -a12 * a21, + Q1_1 = -a12 * (-a21 * (a33 + a44) + a23 * a31 + a24 * a41), + Q1_0 = + -a12 * (a24 * (a31 * a43 - a41 * a33) - a23 * (a31 * a44 - a41 * a34) + + a21 * (a33 * a44 - + a43 * a34)); // good all being well... + // multiplying with -a12 + // P1_2 *= -a12; P1_1 *= -a12; P1_0 *= -a12; + + // b. (A-λ)13 (+) + P Q2_2 = a13 * -a31, + Q2_1 = a13 * ((a31 * a44 - a41 * a34 + a22 * a31) - a21 * a32), + Q2_0 = + a13 * (a24 * (a31 * a42 - a41 * a32) + a22 * (a41 * a34 - a31 * a44) + + a21 * (a32 * a44 - a42 * a34)); + + // c. (A-λ)14 (-) + P Q3_2 = -a14 * a41, + Q3_1 = -a14 * ((a31 * a43 - a41 * a33 - a22 * a41) + a21 * a42), + Q3_0 = + -a14 * (a23 * (a31 * a42 - a41 * a32) - a22 * (a31 * a43 - a41 * a33) + + a21 * (a32 * a43 - a42 * a33)); + + // 5. Final coefficients + P A0 = Q3_0 + Q2_0 + Q1_0 + W0, A1 = Q3_1 + Q2_1 + Q1_1 + W1, + A2 = Q3_2 + Q2_2 + Q1_2 + W2, A3 = W3, A4 = W4; + + std::vector

solution = PolySolvers::SolveQuartic(A4, A3, A2, A1, A0); + + // Put the solutions in ascending order + std::sort(solution.begin(), solution.end()); + + return solution; +} + +/// +/// Return the list of the eigenvalues of a 3x3 matrix. +/// Parameter is he argumen is a 1D array representing the 4x4 matrix +/// T +/// +template std::vector

EigenValues3x3(const P* array) { + + P coef1 = -1; + P coef2 = (array[0 * 3 + 0] + array[1 * 3 + 1] + array[2 * 3 + 2]); + P coef3 = (array[2 * 3 + 0] * array[0 * 3 + 2]) + + (array[1 * 3 + 0] * array[0 * 3 + 1]) + + (array[1 * 3 + 2] * array[2 * 3 + 1]) - + (array[0 * 3 + 0] * array[1 * 3 + 1]) - + (array[0 * 3 + 0] * array[2 * 3 + 2]) - + (array[1 * 3 + 1] * array[2 * 3 + 2]); + + P coef4 = (array[0 * 3 + 0] * array[1 * 3 + 1] * array[2 * 3 + 2]) + + (array[0 * 3 + 1] * array[2 * 3 + 0] * array[1 * 3 + 2]) + + (array[0 * 3 + 2] * array[1 * 3 + 0] * array[2 * 3 + 1]) - + (array[0 * 3 + 0] * array[1 * 3 + 2] * array[2 * 3 + 1]) - + (array[0 * 3 + 1] * array[1 * 3 + 0] * array[2 * 3 + 2]) - + (array[0 * 3 + 2] * array[2 * 3 + 0] * array[1 * 3 + 1]); + + std::vector

solution = PolySolvers::SolveCubic(coef1, coef2, coef3, coef4); + + std::sort(solution.begin(), solution.end()); + + // for (int i = 0; i < solution.size(); i++) + // std::cout << "eigenvalue "< std::vector

PrincipalEigenvector4x4(P* M) { + // First obtain the eigenvalues of M + auto eigenvalues = EigenValues4x4(M); + if (eigenvalues.size() == 0) + return std::vector

({0, 0, 0, 0}); + // auto absmaxit = std::max_element( eigenvalues.begin(), + // eigenvalues.end() , + // [](const int& a, const int& b) { return fabs(a) < + // fabs(b);} + // ); + // P lambda = *absmaxit; + P lambda = eigenvalues[eigenvalues.size() - + 1]; // returning the largest eigenvalue (instead or + // trhe largest in absolute value) + if (lambda == 0) + return std::vector

({0, 0, 0, 0}); + // Now obtaining a vector containing the M-lambda * eye(3) + std::vector

A; + A.push_back(M[0] - lambda); + A.push_back(M[1]); + A.push_back(M[2]); + A.push_back(M[3]); + A.push_back(M[4]); + A.push_back(M[5] - lambda); + A.push_back(M[6]); + A.push_back(M[7]); + A.push_back(M[8]); + A.push_back(M[9]); + A.push_back(M[10] - lambda); + A.push_back(M[11]); + A.push_back(M[12]); + A.push_back(M[13]); + A.push_back(M[14]); + A.push_back(M[15] - lambda); + + // Now get the eigenvector using the Gauss-jordan steps + return GaussJordan4x4(A); +} + +// Get the eigenvector of the largest eigenvalue of a 4x4 matrix. +// NOTE: This function will return the zero vector even if no eigenvalues exist +template std::vector

PrincipalEigenvector3x3(P* M) { + // First obtain the eigenvalues of M + auto eigenvalues = EigenValues3x3(M); + if (eigenvalues.size() == 0) + return std::vector

({0, 0, 0}); + // auto maxit = std::max_element( eigenvalues.begin(), + // eigenvalues.end() , + // [](const int& a, const int& b) { return fabs(a) < + // fabs(b); } + // ); + + // P lambda = *maxit; + P lambda = eigenvalues[eigenvalues.size() - 1]; // get the largest eigenvalue + // if there are only zero eigenvalues, return the zero vector + if (lambda == 0) + return std::vector

({0, 0, 0}); // extra check + + // Now obtaining a vector containing the M-lambda * eye(3) + std::vector

A; + A.push_back(M[0] - lambda); + A.push_back(M[1]); + A.push_back(M[2]); + A.push_back(M[3]); + A.push_back(M[4] - lambda); + A.push_back(M[5]); + A.push_back(M[6]); + A.push_back(M[7]); + A.push_back(M[8] - lambda); + + // Now get the eigenvector using the Gauss-jordan steps + return GaussJordan3x3(A); +} + +// 3x3 Matrix eigen decomposition +template +std::pair, std::vector>> EigenDecompose3x3(P* M) { + // First obtain the eigenvalues of M + auto eigenvalues = EigenValues3x3(M); + // vector of eigenvectors + std::vector> eigenvectors; + // return an empty eigenvalue list and a single eigenvector withg zeros in it + if (eigenvalues.size() == 0) + return std::pair, std::vector>>(eigenvalues, + eigenvectors); + + std::vector

A({M[0], M[1], M[2], M[3], M[4], M[5], M[6], M[7], M[8]}); + + for (int i = 0; i < eigenvalues.size(); i++) { + // Now subtract the eigenvalue from the diagonal + A[0] -= eigenvalues[i]; + A[4] -= eigenvalues[i]; + A[8] -= eigenvalues[i]; + + // compute and add the eigen vector to the list + eigenvectors.push_back(GaussJordan3x3(A)); + + // add the eigenvalue back to the diagonal in order to undo the change in + // the elements of A + A[0] += eigenvalues[i]; + A[4] += eigenvalues[i]; + A[8] += eigenvalues[i]; + } + + // return the decomposition (in ascending eigenvalue order) + return std::pair, std::vector>>(eigenvalues, + eigenvectors); +} + +// 4x4 Matrix eigen decomposition +template +std::pair, std::vector>> EigenDecompose4x4(P* M) { + // First obtain the eigenvalues of M + auto eigenvalues = EigenValues4x4(M); + // vector of eigenvectors + std::vector> eigenvectors; + + // return an empty eigenvalue list and a single eigenvector withg zeros in it + if (eigenvalues.size() == 0) + return std::pair, std::vector>>(eigenvalues, + eigenvectors); + + std::vector

A({M[0], M[1], M[2], M[3], M[4], M[5], M[6], M[7], M[8], M[9], + M[10], M[11], M[12], M[13], M[14], M[15]}); + + for (int i = 0; i < eigenvalues.size(); i++) { + // Now subtract the eigenvalue from the diagonal + A[0] -= eigenvalues[i]; + A[5] -= eigenvalues[i]; + A[10] -= eigenvalues[i]; + A[15] -= eigenvalues[i]; + + // compute and add the eigen vector to the list + eigenvectors.push_back(GaussJordan4x4(A)); + + // add the eigenvalue back to the diagonal in order to undo the change in + // the elements of A + A[0] += eigenvalues[i]; + A[5] += eigenvalues[i]; + A[10] += eigenvalues[i]; + A[15] += eigenvalues[i]; + } + + // return the decomposition (in ascending eigenvalue order) + return std::pair, std::vector>>(eigenvalues, + eigenvectors); +} + +std::pair self_adjoint_evd(rmatrix4x4 A) { + rmatrix4x4 rvectors; // TODO: vectors result in nan because max2 is zero + rdiagonal4x4 rvalues; + auto [values, vectors] = EigenDecompose4x4(A.data()); + for (int i = 0; i < values.size(); ++i) { + for (int j = 0; j < vectors[i].size(); ++j) { + rvectors[j * 4 + i] = vectors[j][i]; + } + rvalues[i] = values[i]; + } + return {rvectors, rvalues}; +} +} // namespace mqt::ir::opt + +#endif diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/f.h b/mlir/lib/Dialect/MQTOpt/Transforms/f.h new file mode 100644 index 000000000..4cbb4c86c --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/f.h @@ -0,0 +1,97 @@ +#pragma once + +#include "Helpers.h" + +namespace mqt::ir::opt { +inline bool jacobi_4x4(double * A, double * D, double * U) +{ + double B[4], Z[4]; + double Id[16] = {1., 0., 0., 0., + 0., 1., 0., 0., + 0., 0., 1., 0., + 0., 0., 0., 1.}; + + memcpy(U, Id, 16 * sizeof(double)); + + B[0] = A[0]; B[1] = A[5]; B[2] = A[10]; B[3] = A[15]; + memcpy(D, B, 4 * sizeof(double)); + memset(Z, 0, 4 * sizeof(double)); + + for(int iter = 0; iter < 50; iter++) { + double sum = fabs(A[1]) + fabs(A[2]) + fabs(A[3]) + fabs(A[6]) + fabs(A[7]) + fabs(A[11]); + + if (sum == 0.0) + return true; + + double tresh = (iter < 3) ? 0.2 * sum / 16. : 0.0; + for(int i = 0; i < 3; i++) { + double * pAij = A + 5 * i + 1; + for(int j = i + 1 ; j < 4; j++) { + double Aij = *pAij; + double eps_machine = 100.0 * fabs(Aij); + + if ( iter > 3 && fabs(D[i]) + eps_machine == fabs(D[i]) && fabs(D[j]) + eps_machine == fabs(D[j]) ) + *pAij = 0.0; + else if (fabs(Aij) > tresh) { + double hh = D[j] - D[i], t; + if (fabs(hh) + eps_machine == fabs(hh)) + t = Aij / hh; + else { + double theta = 0.5 * hh / Aij; + t = 1.0 / (fabs(theta) + sqrt(1.0 + theta * theta)); + if (theta < 0.0) t = -t; + } + + hh = t * Aij; + Z[i] -= hh; + Z[j] += hh; + D[i] -= hh; + D[j] += hh; + *pAij = 0.0; + + double c = 1.0 / sqrt(1 + t * t); + double s = t * c; + double tau = s / (1.0 + c); + for(int k = 0; k <= i - 1; k++) { + double g = A[k * 4 + i], h = A[k * 4 + j]; + A[k * 4 + i] = g - s * (h + g * tau); + A[k * 4 + j] = h + s * (g - h * tau); + } + for(int k = i + 1; k <= j - 1; k++) { + double g = A[i * 4 + k], h = A[k * 4 + j]; + A[i * 4 + k] = g - s * (h + g * tau); + A[k * 4 + j] = h + s * (g - h * tau); + } + for(int k = j + 1; k < 4; k++) { + double g = A[i * 4 + k], h = A[j * 4 + k]; + A[i * 4 + k] = g - s * (h + g * tau); + A[j * 4 + k] = h + s * (g - h * tau); + } + for(int k = 0; k < 4; k++) { + double g = U[k * 4 + i], h = U[k * 4 + j]; + U[k * 4 + i] = g - s * (h + g * tau); + U[k * 4 + j] = h + s * (g - h * tau); + } + } + pAij++; + } + } + + for(int i = 0; i < 4; i++) B[i] += Z[i]; + memcpy(D, B, 4 * sizeof(double)); + memset(Z, 0, 4 * sizeof(double)); + } + + return false; +} + + +std::pair self_adjoint_evd(rmatrix4x4 A) { + rmatrix4x4 rvectors{}; + rdiagonal4x4 rvalues{}; + if (!jacobi_4x4(A.data(), rvectors.data(), rvalues.data())) { + throw std::runtime_error{"bad luck..."}; + } + return {rvectors, rvalues}; +} +} diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/g.h b/mlir/lib/Dialect/MQTOpt/Transforms/g.h new file mode 100644 index 000000000..d2927e943 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/g.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Helpers.h" +#include "eigen3/Eigen/Eigen" + +namespace mqt::ir::opt { + +auto self_adjoint_evd(rmatrix4x4 A) { + Eigen::Matrix4d a; + Eigen::SelfAdjointEigenSolver s; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + a(j, i) = A[j * 4 + i]; + } + } + std::cerr << "=EigIN==\n" << a << "\n========\n" << std::endl; + s.computeDirect(a); + auto vecs = s.eigenvectors(); + auto vals = s.eigenvalues(); + rmatrix4x4 rvecs; + rdiagonal4x4 rvals; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + rvecs[j * 4 + i] = vecs(j, i); + } + rvals[i] = vals(i); + } + std::cerr << "=Eigen==\n" << vecs << "\n========\n" << std::endl; + std::cerr << "=Eigen==\n" << vals << "\n========\n" << std::endl; + return std::make_pair(rvecs, rvals); +} +} // namespace mqt::ir::opt From ae3d08d9581659dae33f1279daa71da7adba5c8d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 12:36:28 +0100 Subject: [PATCH 023/100] work on applySeries() --- .../Transforms/GateDecompositionPattern.cpp | 68 +++++++++++++++++-- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 27c4d4306..a0cb4629f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -43,7 +43,7 @@ struct GateDecompositionPattern final auto series = getTwoQubitSeries(op); llvm::errs() << "SERIES SIZE: " << series.size() << '\n'; - if (series.size() <= 3) { + if (series.size() < 3) { return mlir::failure(); } @@ -185,37 +185,91 @@ struct GateDecompositionPattern final llvm::SmallVector& series, const TwoQubitGateSequence& sequence) { auto location = series[0]->getLoc(); - auto inQubits = series[0].getAllInQubits(); + std::vector inQubits(2); + auto&& firstInQubist = series[0].getAllInQubits(); + if (firstInQubist.size() == 2) { + inQubits[0] = firstInQubist[0]; + inQubits[1] = firstInQubist[1]; + } else { + // TODO + } if (sequence.globalPhase != 0.0) { createOneParameterGate(rewriter, location, sequence.globalPhase, {}); } std::cerr << "GATE SEQUENCE!: " << std::flush; + UnitaryInterface lastGate = series[0]; for (auto&& gate : sequence.gates) { if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubit_id.size() > 1) { inCtrlQubits.push_back(inQubits[gate.qubit_id[1]]); } - createGate(rewriter, location, {inQubits[0]}, inCtrlQubits, - gate.parameter); + lastGate = createGate(rewriter, location, {inQubits[0]}, + inCtrlQubits, gate.parameter); + if (gate.qubit_id.size() == 2) { + inQubits = lastGate.getAllOutQubits(); + } else if (gate.qubit_id.size() == 1) { + inQubits[gate.qubit_id[0]] = + lastGate.getAllOutQubits()[0]; + } else { + throw std::logic_error{"Invalid number of qubit IDs!"}; + } } if (gate.type == qc::RX) { mlir::SmallVector qubits; for (auto&& x : gate.qubit_id) { qubits.push_back(inQubits[x]); } - createGate(rewriter, location, qubits, {}, gate.parameter); + lastGate = + createGate(rewriter, location, qubits, {}, gate.parameter); + if (gate.qubit_id.size() == 2) { + inQubits = lastGate.getAllOutQubits(); + } else if (gate.qubit_id.size() == 1) { + inQubits[gate.qubit_id[0]] = + lastGate.getAllOutQubits()[0]; + } else { + throw std::logic_error{"Invalid number of qubit IDs!"}; + } } if (gate.type == qc::RY) { + mlir::SmallVector qubits; + for (auto&& x : gate.qubit_id) { + qubits.push_back(inQubits[x]); + } + lastGate = + createGate(rewriter, location, qubits, {}, gate.parameter); + if (gate.qubit_id.size() == 2) { + inQubits = lastGate.getAllOutQubits(); + } else if (gate.qubit_id.size() == 1) { + inQubits[gate.qubit_id[0]] = + lastGate.getAllOutQubits()[0]; + } else { + throw std::logic_error{"Invalid number of qubit IDs!"}; + } } if (gate.type == qc::RZ) { + mlir::SmallVector qubits; + for (auto&& x : gate.qubit_id) { + qubits.push_back(inQubits[x]); + } + lastGate = + createGate(rewriter, location, qubits, {}, gate.parameter); + if (gate.qubit_id.size() == 2) { + inQubits = lastGate.getAllOutQubits(); + } else if (gate.qubit_id.size() == 1) { + inQubits[gate.qubit_id[0]] = + lastGate.getAllOutQubits()[0]; + } else { + throw std::logic_error{"Invalid number of qubit IDs!"}; + } } } - for (auto&& op : series) { - rewriter.replaceOp(op, op.getAllInQubits()); + rewriter.replaceAllOpUsesWith(series.back(), inQubits); + for (auto&& op : llvm::reverse(series)) { + rewriter.eraseOp(op); } } From d2f445644f39645a46c010dc6b803bb8f9b4eb41 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 12:37:08 +0100 Subject: [PATCH 024/100] Revert "use laughing-umbrella implementation for decompose_two_qubit_product_gate" This reverts commit 461319afefb439ace9b91ed31a88618f03165640. --- .../Transforms/GateDecompositionPattern.cpp | 93 ++++++++----------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index a0cb4629f..3464f599a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -356,65 +356,54 @@ struct GateDecompositionPattern final static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { - using helpers::determinant; - using helpers::dot; - using helpers::kroneckerProduct; - using helpers::transpose_conjugate; - helpers::print(special_unitary, "SPECIAL_UNITARY"); - - auto* it = llvm::max_element(special_unitary, [](auto&& a, auto&& b) { - return std::abs(a) < std::abs(b); - }); - if (it == special_unitary.end()) { + using helpers::dot; + using helpers::kroneckerProduct; + using helpers::transpose_conjugate; + using helpers::determinant; + helpers::print(special_unitary, "SPECIAL_UNITARY"); + // first quadrant + matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], + special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; + auto det_r = determinant(r); + if (std::abs(det_r) < 0.1) { + // third quadrant + r = {special_unitary[2 * 4 + 0], special_unitary[2 * 4 + 1], + special_unitary[3 * 4 + 0], special_unitary[3 * 4 + 1]}; + det_r = determinant(r); + } + if (std::abs(det_r) < 0.1) { throw std::runtime_error{ - "No maximal element in decompose_two_qubit_product_gate!"}; + "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; } - auto pos = std::distance(special_unitary.begin(), it); - int i = pos % 4; - int j = pos / 4; - - auto u1_set = [](int i) { - if (i % 2 == 0) { - return std::make_pair(0, 2); - } - return std::make_pair(1, 3); - }; - - auto u2_set = [](int i) { - if (i < 2) { - return std::make_pair(0, 1); - } - return std::make_pair(2, 3); - }; - - auto to_su = [](auto&& u) { - return helpers::multiply(std::pow(qfp(determinant(u)), -0.25), u); - }; + llvm::transform(r, r.begin(), + [&](auto&& x) { return x / std::sqrt(det_r); }); + // transpose with complex conjugate of each element + matrix2x2 r_t_conj = transpose_conjugate(r); + + auto temp = kroneckerProduct(identityGate, r_t_conj); + temp = dot(special_unitary, temp); + + // [[a, b, c, d], + // [e, f, g, h], => [[a, c], + // [i, j, k, l], [i, k]] + // [m, n, o, p]] + matrix2x2 l = {temp[0 * 4 + 0], temp[0 * 4 + 2], temp[2 * 4 + 0], + temp[2 * 4 + 2]}; + auto det_l = determinant(l); + if (std::abs(det_l) < 0.9) { + throw std::runtime_error{ + "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9"}; + } + llvm::transform(l, l.begin(), + [&](auto&& x) { return x / std::sqrt(det_l); }); + auto phase = std::arg(det_l) / 2.; - matrix2x2 u1; - u1[0 * 2 + 0] = special_unitary[u1_set(i).first * 4 + u1_set(j).first]; - u1[0 * 2 + 1] = special_unitary[u1_set(i).first * 4 + u1_set(j).second]; - u1[1 * 2 + 0] = special_unitary[u1_set(i).second * 4 + u1_set(j).first]; - u1[1 * 2 + 1] = special_unitary[u1_set(i).second * 4 + u1_set(j).second]; - matrix2x2 u2; - u1[0 * 2 + 0] = special_unitary[u2_set(i).first * 4 + u2_set(j).first]; - u1[0 * 2 + 1] = special_unitary[u2_set(i).first * 4 + u2_set(j).second]; - u1[1 * 2 + 0] = special_unitary[u2_set(i).second * 4 + u2_set(j).first]; - u1[1 * 2 + 1] = special_unitary[u2_set(i).second * 4 + u2_set(j).second]; - - u1 = to_su(u1); - u2 = to_su(u2); - - std::cerr << i << ',' << j << std::endl; - auto phase = special_unitary[i * 4 + j] / - (u1[(i / 2) * 2 + (j / 2)] * u2[(i % 2) * 2 + (j % 2)]); - - return {u1, u2, phase.real()}; // TODO: return only real part? + return {l, r, phase}; } static matrix4x4 magic_basis_transform(const matrix4x4& unitary, MagicBasisTransform direction) { - using helpers::dot; + using helpers::dot; constexpr matrix4x4 B_NON_NORMALIZED = { C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, From 58c4ec36486059fa32e6eebf27eae3acfd8e55fc Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 27 Oct 2025 16:43:54 +0100 Subject: [PATCH 025/100] fix transpose_conjugate --- .../lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 2 +- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 3464f599a..703b9acdd 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -696,7 +696,7 @@ struct GateDecompositionPattern final auto tmp = remEuclid(x, qc::PI_2); return std::min(tmp, qc::PI_2 - tmp); }); - std::array order{0, 1, 2}; + std::array order{2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); helpers::print(order, "ORDER (1)"); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index c323477ef..35d6f02d4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -463,8 +463,7 @@ template auto conjugate(Container matrix) { template inline auto transpose_conjugate(Container&& matrix) { auto result = transpose(matrix); - result = conjugate(matrix); - return result; + return conjugate(result); } template From 5aa5d87239f75863278fb1687d6ac7a69a1e1972 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 13:01:10 +0100 Subject: [PATCH 026/100] add test --- .../MQTOpt/Transforms/gate-decomposition.mlir | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir new file mode 100644 index 000000000..b1a37228c --- /dev/null +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s -split-input-file --gate-decomposition | FileCheck %s + +// ----- +// This test checks if two-qubit consecutive controlled self-inverses are canceled correctly. + +module { + // CHECK-LABEL: func.func @testEvenNegationSeries + func.func @testEvenNegationSeries() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x + + // CHECK: mqtopt.deallocQubit %[[Q0_0]] + // CHECK: mqtopt.deallocQubit %[[Q1_0]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_4, %q1_4 = mqtopt.x() %q0_3 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_5, %q1_5 = mqtopt.x() %q0_4 ctrl %q1_4: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_6, %q1_6 = mqtopt.x() %q0_5 ctrl %q1_5: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_7, %q1_7 = mqtopt.x() %q0_6 ctrl %q1_6: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_8, %q1_8 = mqtopt.x() %q0_7 ctrl %q1_7: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_9, %q1_9 = mqtopt.x() %q0_8 ctrl %q1_8: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_10, %q1_10 = mqtopt.x() %q0_9 ctrl %q1_9: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_11, %q1_11 = mqtopt.x() %q0_10 ctrl %q1_10: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_12, %q1_12 = mqtopt.x() %q0_11 ctrl %q1_11: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_13, %q1_13 = mqtopt.x() %q0_12 ctrl %q1_12: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_14, %q1_14 = mqtopt.x() %q0_13 ctrl %q1_13: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_15, %q1_15 = mqtopt.x() %q0_14 ctrl %q1_14: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_16, %q1_16 = mqtopt.x() %q0_15 ctrl %q1_15: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_17, %q1_17 = mqtopt.x() %q0_16 ctrl %q1_16: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_18, %q1_18 = mqtopt.x() %q0_17 ctrl %q1_17: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_19, %q1_19 = mqtopt.x() %q0_18 ctrl %q1_18: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_20, %q1_20 = mqtopt.x() %q0_19 ctrl %q1_19: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_21, %q1_21 = mqtopt.x() %q0_20 ctrl %q1_20: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_22, %q1_22 = mqtopt.x() %q0_21 ctrl %q1_21: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_23, %q1_23 = mqtopt.x() %q0_22 ctrl %q1_22: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_24, %q1_24 = mqtopt.x() %q0_23 ctrl %q1_23: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_25, %q1_25 = mqtopt.x() %q0_24 ctrl %q1_24: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_27, %q1_27 = mqtopt.x() %q0_25 ctrl %q1_25: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_28, %q1_28 = mqtopt.x() %q0_27 ctrl %q1_27: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_29, %q1_29 = mqtopt.x() %q0_28 ctrl %q1_28: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_30, %q1_30 = mqtopt.x() %q0_29 ctrl %q1_29: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_31, %q1_31 = mqtopt.x() %q0_30 ctrl %q1_30: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_32, %q1_32 = mqtopt.x() %q0_31 ctrl %q1_31: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_33, %q1_33 = mqtopt.x() %q0_32 ctrl %q1_32: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_33 + mqtopt.deallocQubit %q1_33 + + return + } +} + +module { + // CHECK-LABEL: func.func @testOddNegationSeries + func.func @testOddNegationSeries() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x + + // CHECK: mqtopt.deallocQubit %[[Q0_0]] + // CHECK: mqtopt.deallocQubit %[[Q1_0]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_3 = mqtopt.x() %q0_2 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + + return + } +} From 2359f2827a9bad927e0054678ef2bbdfb26ed09f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 20:43:13 +0100 Subject: [PATCH 027/100] actually kind of working snapshot? --- .../Transforms/GateDecompositionPattern.cpp | 479 +++++++++++------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 24 +- .../MQTOpt/Transforms/gate-decomposition.mlir | 84 ++- 3 files changed, 391 insertions(+), 196 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 703b9acdd..21060ae35 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -40,19 +40,26 @@ struct GateDecompositionPattern final mlir::LogicalResult matchAndRewrite(UnitaryInterface op, mlir::PatternRewriter& rewriter) const override { - auto series = getTwoQubitSeries(op); - llvm::errs() << "SERIES SIZE: " << series.size() << '\n'; + auto series = TwoQubitSeries::getTwoQubitSeries(op); + llvm::errs() << "SERIES SIZE: " << series.gates.size() << '\n'; - if (series.size() < 3) { + if (series.gates.size() < 3) { + // too short + return mlir::failure(); + } + if (llvm::is_contained(series.inQubits, mlir::Value{}) || + llvm::is_contained(series.outQubits, mlir::Value{})) { + // only a single-qubit series return mlir::failure(); } matrix4x4 unitaryMatrix = helpers::kroneckerProduct(identityGate, identityGate); - for (auto&& gate : series) { - auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate), - .parameter = {/*TODO*/}, - .qubit_id = {0, 1}}); + for (auto&& gate : series.gates) { + auto gateMatrix = + getTwoQubitMatrix({.type = helpers::getQcType(gate.op), + .parameter = helpers::getParameters(gate.op), + .qubit_id = gate.qubit_id}); unitaryMatrix = helpers::multiply(unitaryMatrix, gateMatrix); } @@ -63,71 +70,132 @@ struct GateDecompositionPattern final llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } + if (sequence->gates.size() >= series.gates.size()) { + // TODO: add more sophisticated metric to determine complexity of + // series/sequence + llvm::errs() << "SEQUENCE LONGER THAN INPUT!\n"; + return mlir::failure(); + } applySeries(rewriter, series, *sequence); return mlir::success(); } - [[nodiscard]] static llvm::SmallVector - getTwoQubitSeries(UnitaryInterface op) { - llvm::SmallVector qubits(2); - llvm::SmallVector result; + struct TwoQubitSeries { + std::array inQubits; + std::array outQubits; - if (helpers::isSingleQubitOperation(op)) { - qubits = {op->getResult(0), mlir::Value{}}; - } else if (helpers::isTwoQubitOperation(op)) { - qubits = op->getResults(); - } else { - return result; - } - result.push_back(op); + struct Gate { + UnitaryInterface op; + llvm::SmallVector qubit_id; + }; + llvm::SmallVector gates; + + [[nodiscard]] static TwoQubitSeries getTwoQubitSeries(UnitaryInterface op) { + TwoQubitSeries result(op); + + auto findNextInSeries = [&](mlir::Operation* user) { + auto userUnitary = mlir::dyn_cast(user); + if (!userUnitary) { + return false; + } - auto findNextInSeries = [&](mlir::Operation* user) { - auto userUnitary = mlir::dyn_cast(user); - if (!userUnitary) { + if (helpers::isSingleQubitOperation(userUnitary)) { + result.appendSingleQubitGate(userUnitary); + return true; + } + if (helpers::isTwoQubitOperation(userUnitary)) { + result.appendTwoQubitGate(userUnitary); + return true; + } return false; - } + }; - if (helpers::isSingleQubitOperation(userUnitary)) { - auto&& operand = userUnitary->getOperand(0); - auto* it = llvm::find(qubits, operand); - if (it == qubits.end()) { - throw std::logic_error{"Operand of single-qubit op and user of " - "qubit is not in qubits"}; + bool isFirstQubitOngoing = result.outQubits[0] != mlir::Value{}; + bool isSecondQubitOngoing = result.outQubits[1] != mlir::Value{}; + while (isFirstQubitOngoing || isSecondQubitOngoing) { + if (result.outQubits[0]) { + assert(result.outQubits[0].hasOneUse()); + isFirstQubitOngoing = + findNextInSeries(*result.outQubits[0].getUsers().begin()); } - *it = userUnitary->getResult(0); + if (result.outQubits[1]) { + assert(result.outQubits[1].hasOneUse()); + isSecondQubitOngoing = + findNextInSeries(*result.outQubits[1].getUsers().begin()); + } + } + return result; + } - result.push_back(userUnitary); - return true; + private: + TwoQubitSeries(UnitaryInterface initialOperation) { + auto&& in = initialOperation.getAllInQubits(); + auto&& out = initialOperation->getResults(); + if (helpers::isSingleQubitOperation(initialOperation)) { + inQubits = {in[0], mlir::Value{}}; + outQubits = {out[0], mlir::Value{}}; + gates.push_back({initialOperation, {0}}); + } else if (helpers::isTwoQubitOperation(initialOperation)) { + inQubits = {in[0], in[1]}; + outQubits = {out[0], out[1]}; + gates.push_back({initialOperation, {0, 1}}); } - if (helpers::isTwoQubitOperation(userUnitary)) { - auto&& firstOperand = userUnitary->getOperand(0); - auto&& secondOperand = userUnitary->getOperand(1); - auto* firstQubitIt = llvm::find(qubits, firstOperand); - auto* secondQubitIt = llvm::find(qubits, secondOperand); - if (firstQubitIt == qubits.end() || secondQubitIt == qubits.end()) { - return false; - } - *firstQubitIt = userUnitary->getResult(0); - *secondQubitIt = userUnitary->getResult(1); + } - result.push_back(userUnitary); - return true; + /** + * @return true if series continues, otherwise false + */ + bool appendSingleQubitGate(UnitaryInterface nextGate) { + auto operand = nextGate.getAllInQubits()[0]; + auto* it = llvm::find(outQubits, operand); + if (it == outQubits.end()) { + throw std::logic_error{"Operand of single-qubit op and user of " + "qubit is not in current outQubits"}; } - return false; - }; + std::uint8_t qubitId = std::distance(outQubits.begin(), it); + *it = nextGate->getResult(0); - while (true) { - assert(qubits[0].hasOneUse()); - assert(qubits[1].hasOneUse()); - bool isSeriesOngoing = findNextInSeries(*qubits[0].getUsers().begin()) || - findNextInSeries(*qubits[1].getUsers().begin()); - if (!isSeriesOngoing) { - return result; + gates.push_back({nextGate, {qubitId}}); + return true; + } + + /** + * @return true if series continues, otherwise false + */ + bool appendTwoQubitGate(UnitaryInterface nextGate) { + auto opInQubits = nextGate.getAllInQubits(); + auto&& firstOperand = opInQubits[0]; + auto&& secondOperand = opInQubits[1]; + auto* firstQubitIt = llvm::find(outQubits, firstOperand); + auto* secondQubitIt = llvm::find(outQubits, secondOperand); + if (firstQubitIt == outQubits.end() || secondQubitIt == outQubits.end()) { + // another qubit is involved, series is finished (except there only + // has been one qubit so far) + auto* it = llvm::find(outQubits, mlir::Value{}); + if (it == outQubits.end()) { + return false; + } + auto it2 = llvm::find(opInQubits, firstQubitIt != outQubits.end() + ? *firstQubitIt + : *secondQubitIt); + inQubits[std::distance(outQubits.begin(), it)] = + opInQubits[1 - std::distance(opInQubits.begin(), it2)]; + firstQubitIt = (firstQubitIt != outQubits.end()) ? firstQubitIt : it; + secondQubitIt = (secondQubitIt != outQubits.end()) ? secondQubitIt : it; } + *firstQubitIt = nextGate->getResult(0); + std::uint8_t firstQubitId = + std::distance(outQubits.begin(), firstQubitIt); + *secondQubitIt = nextGate->getResult(1); + std::uint8_t secondQubitId = + std::distance(outQubits.begin(), secondQubitIt); + + gates.push_back({nextGate, {firstQubitId, secondQubitId}}); + return true; } - } + }; /** * @brief Creates a new rotation gate with no controls. @@ -155,8 +223,8 @@ struct GateDecompositionPattern final static OpType createGate(mlir::PatternRewriter& rewriter, mlir::Location location, mlir::ValueRange inQubits, mlir::ValueRange ctrlQubits, - std::vector parameters) { - mlir::SmallVector parameterValues; + llvm::SmallVector parameters) { + mlir::SmallVector parameterValues; for (auto&& parameter : parameters) { auto parameterValue = rewriter.create( location, rewriter.getF64Type(), rewriter.getF64FloatAttr(parameter)); @@ -172,8 +240,8 @@ struct GateDecompositionPattern final struct QubitGateSequence { struct Gate { qc::OpType type{qc::I}; - std::vector parameter; - std::vector qubit_id = {0}; + llvm::SmallVector parameter; + llvm::SmallVector qubit_id = {0}; }; std::vector gates; fp globalPhase; @@ -182,94 +250,77 @@ struct GateDecompositionPattern final using TwoQubitGateSequence = QubitGateSequence; static void applySeries(mlir::PatternRewriter& rewriter, - llvm::SmallVector& series, + TwoQubitSeries& series, const TwoQubitGateSequence& sequence) { - auto location = series[0]->getLoc(); - std::vector inQubits(2); - auto&& firstInQubist = series[0].getAllInQubits(); - if (firstInQubist.size() == 2) { - inQubits[0] = firstInQubist[0]; - inQubits[1] = firstInQubist[1]; - } else { - // TODO - } + auto location = series.gates.back().op->getLoc(); if (sequence.globalPhase != 0.0) { createOneParameterGate(rewriter, location, sequence.globalPhase, {}); } + auto inQubits = series.inQubits; + auto updateInQubits = + [&inQubits](const TwoQubitGateSequence::Gate& gateDescription, + auto&& newGate) { + auto results = newGate.getAllOutQubits(); + if (gateDescription.qubit_id.size() == 2) { + inQubits[gateDescription.qubit_id[0]] = results[0]; + inQubits[gateDescription.qubit_id[1]] = results[1]; + } else if (gateDescription.qubit_id.size() == 1) { + inQubits[gateDescription.qubit_id[0]] = results[0]; + } else { + throw std::logic_error{"Invalid number of qubit IDs!"}; + } + }; + + std::cerr << "SERIES: "; + for (auto&& gate : series.gates) { + std::cerr << gate.op->getName().stripDialect().str() << ", "; + } + std::cerr << '\n'; std::cerr << "GATE SEQUENCE!: " << std::flush; - UnitaryInterface lastGate = series[0]; for (auto&& gate : sequence.gates) { + std::cerr << qc::toString(gate.type) << ", "; if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubit_id.size() > 1) { inCtrlQubits.push_back(inQubits[gate.qubit_id[1]]); } - lastGate = createGate(rewriter, location, {inQubits[0]}, - inCtrlQubits, gate.parameter); - if (gate.qubit_id.size() == 2) { - inQubits = lastGate.getAllOutQubits(); - } else if (gate.qubit_id.size() == 1) { - inQubits[gate.qubit_id[0]] = - lastGate.getAllOutQubits()[0]; - } else { - throw std::logic_error{"Invalid number of qubit IDs!"}; - } - } - if (gate.type == qc::RX) { + auto newGate = createGate(rewriter, location, {inQubits[0]}, + inCtrlQubits, gate.parameter); + updateInQubits(gate, newGate); + } else if (gate.type == qc::RX) { mlir::SmallVector qubits; for (auto&& x : gate.qubit_id) { qubits.push_back(inQubits[x]); } - lastGate = + auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - if (gate.qubit_id.size() == 2) { - inQubits = lastGate.getAllOutQubits(); - } else if (gate.qubit_id.size() == 1) { - inQubits[gate.qubit_id[0]] = - lastGate.getAllOutQubits()[0]; - } else { - throw std::logic_error{"Invalid number of qubit IDs!"}; - } - } - if (gate.type == qc::RY) { + updateInQubits(gate, newGate); + } else if (gate.type == qc::RY) { mlir::SmallVector qubits; for (auto&& x : gate.qubit_id) { qubits.push_back(inQubits[x]); } - lastGate = + auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - if (gate.qubit_id.size() == 2) { - inQubits = lastGate.getAllOutQubits(); - } else if (gate.qubit_id.size() == 1) { - inQubits[gate.qubit_id[0]] = - lastGate.getAllOutQubits()[0]; - } else { - throw std::logic_error{"Invalid number of qubit IDs!"}; - } - } - if (gate.type == qc::RZ) { + updateInQubits(gate, newGate); + } else if (gate.type == qc::RZ) { mlir::SmallVector qubits; for (auto&& x : gate.qubit_id) { qubits.push_back(inQubits[x]); } - lastGate = + auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - if (gate.qubit_id.size() == 2) { - inQubits = lastGate.getAllOutQubits(); - } else if (gate.qubit_id.size() == 1) { - inQubits[gate.qubit_id[0]] = - lastGate.getAllOutQubits()[0]; - } else { - throw std::logic_error{"Invalid number of qubit IDs!"}; - } + updateInQubits(gate, newGate); + } else { + throw std::runtime_error{"Unknown gate type!"}; } } - rewriter.replaceAllOpUsesWith(series.back(), inQubits); - for (auto&& op : llvm::reverse(series)) { - rewriter.eraseOp(op); + rewriter.replaceAllUsesWith(series.outQubits, inQubits); + for (auto&& gate : llvm::reverse(series.gates)) { + rewriter.eraseOp(gate.op); } } @@ -344,11 +395,13 @@ struct GateDecompositionPattern final auto [U, S] = self_adjoint_evd(A); // TODO: not in original code - if (helpers::determinant(U) + 1.0 < std::numeric_limits::epsilon()) { - // if determinant of eigenvector matrix is -1.0, multiply first eigenvector by -1.0 - for (int i = 0; i < 4; ++i) { - U[i * 4 + 0] *= -1.0; - } + if (helpers::determinant(U) + 1.0 < 1e-5) { + std::cerr << "CORRECTION!\n"; + // if determinant of eigenvector matrix is -1.0, multiply first + // eigenvector by -1.0 + for (int i = 0; i < 4; ++i) { + U[i * 4 + 0] *= -1.0; + } } return std::make_pair(U, S); @@ -356,11 +409,11 @@ struct GateDecompositionPattern final static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { - using helpers::dot; - using helpers::kroneckerProduct; - using helpers::transpose_conjugate; - using helpers::determinant; - helpers::print(special_unitary, "SPECIAL_UNITARY"); + using helpers::determinant; + using helpers::dot; + using helpers::kroneckerProduct; + using helpers::transpose_conjugate; + helpers::print(special_unitary, "SPECIAL_UNITARY"); // first quadrant matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; @@ -371,17 +424,21 @@ struct GateDecompositionPattern final special_unitary[3 * 4 + 0], special_unitary[3 * 4 + 1]}; det_r = determinant(r); } + std::cerr << "DET_R: " << det_r << '\n'; if (std::abs(det_r) < 0.1) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; } llvm::transform(r, r.begin(), [&](auto&& x) { return x / std::sqrt(det_r); }); + helpers::print(r, "R"); // transpose with complex conjugate of each element matrix2x2 r_t_conj = transpose_conjugate(r); auto temp = kroneckerProduct(identityGate, r_t_conj); + helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 1)"); temp = dot(special_unitary, temp); + helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 2)"); // [[a, b, c, d], // [e, f, g, h], => [[a, c], @@ -403,7 +460,7 @@ struct GateDecompositionPattern final static matrix4x4 magic_basis_transform(const matrix4x4& unitary, MagicBasisTransform direction) { - using helpers::dot; + using helpers::dot; constexpr matrix4x4 B_NON_NORMALIZED = { C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, @@ -456,6 +513,38 @@ struct GateDecompositionPattern final return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; } + static matrix4x4 rxxMatrix(const fp theta) { + const auto cosTheta = std::cos(theta / 2.); + const auto sinTheta = std::sin(theta / 2.); + + return {{cosTheta, + C_ZERO, + C_ZERO, + {0., -sinTheta}, + C_ZERO, + cosTheta, + {0., -sinTheta}, + C_ZERO, + C_ZERO, + {0., -sinTheta}, + cosTheta, + C_ZERO, + {0., -sinTheta}, + C_ZERO, + C_ZERO, + cosTheta}}; + } + + static matrix4x4 rzzMatrix(const fp theta) { + const auto cosTheta = std::cos(theta / 2.); + const auto sinTheta = std::sin(theta / 2.); + + return {qfp{cosTheta, -sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + {cosTheta, sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + {cosTheta, sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, + {cosTheta, -sinTheta}}; + } + static std::array angles_from_unitary(const matrix2x2& matrix, EulerBasis basis) { if (basis == EulerBasis::XYX) { @@ -524,7 +613,7 @@ struct GateDecompositionPattern final return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; } if (gate.type == qc::RZ) { - return rz_matrix(gate.parameter.at(0)); + return rz_matrix(gate.parameter[0]); } if (gate.type == qc::X) { return {0, 1, 1, 0}; @@ -546,19 +635,28 @@ struct GateDecompositionPattern final if (gate.qubit_id[0] == 1) { return kroneckerProduct(getSingleQubitMatrix(gate), identityGate); } - throw std::logic_error{"Invalid qubit ID in compute_unitary"}; + throw std::logic_error{"Invalid qubit ID in getTwoQubitMatrix"}; } if (gate.qubit_id.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubit_id == std::vector{0, 1}) { + if (gate.qubit_id == llvm::SmallVector{0, 1}) { return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; } - if (gate.qubit_id == std::vector{1, 0}) { + if (gate.qubit_id == llvm::SmallVector{1, 0}) { return {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; } } - throw std::invalid_argument{"unsupported gate type for two qubit matrix"}; + if (gate.type == qc::RZ) { + // TODO: check qubit order + return rzzMatrix(gate.parameter[0]); + } + if (gate.type == qc::RX) { + // TODO: check qubit order + return rxxMatrix(gate.parameter[0]); + } + throw std::invalid_argument{ + "unsupported gate type for two qubit matrix "}; } throw std::logic_error{"Invalid number of qubit IDs in compute_unitary"}; } @@ -601,27 +699,28 @@ struct GateDecompositionPattern final helpers::print(m2); // arma::Mat U(4, 4); - // for (int i = 0; i < 4; ++i) { - // for (int j = 0; j < 4; ++j) { + // for (int i = 0; i < 4; ++i) { + // for (int j = 0; j < 4; ++j) { // U.at(j, i) = u_p[j * 4 + i]; - // } - // } + // } + // } // auto x = U.st() * U; - // std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << std::endl; + // std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << + // std::endl; // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. // - // We can't use raw `eig` directly because it isn't guaranteed to give us - // real or orthogonal eigenvectors. Instead, since `M2` is + // We can't use raw `eig` directly because it isn't guaranteed to give + // us real or orthogonal eigenvectors. Instead, since `M2` is // complex-symmetric, // M2 = A + iB // for real-symmetric `A` and `B`, and as // M2^+ @ M2 = A^2 + B^2 + i [A, B] = 1 // we must have `A` and `B` commute, and consequently they are // simultaneously diagonalizable. Mixing them together _should_ account - // for any degeneracy problems, but it's not guaranteed, so we repeat it a - // little bit. The fixed seed is to make failures deterministic; the + // for any degeneracy problems, but it's not guaranteed, so we repeat it + // a little bit. The fixed seed is to make failures deterministic; the // value is not important. auto state = std::mt19937{2023}; std::normal_distribution dist; @@ -654,10 +753,8 @@ struct GateDecompositionPattern final [](auto&& x) { return qfp(x, 0.0); }); auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); - llvm::errs() << "===== D_INNER =====\n"; - helpers::print(d_inner); - llvm::errs() << "===== P_INNER =====\n"; - helpers::print(p_inner); + helpers::print(d_inner, "D_INNER"); + helpers::print(p_inner, "P_INNER"); matrix4x4 diag_d{}; // zero initialization diag_d[0 * 4 + 0] = d_inner[0]; diag_d[1 * 4 + 1] = d_inner[1]; @@ -665,8 +762,7 @@ struct GateDecompositionPattern final diag_d[3 * 4 + 3] = d_inner[3]; auto compare = dot(dot(p_inner, diag_d), transpose(p_inner)); - llvm::errs() << "===== COMPARE =====\n"; - helpers::print(compare); + helpers::print(compare, "COMPARE"); found = llvm::all_of_zip(compare, m2, [](auto&& a, auto&& b) { return std::abs(a - b) <= 1.0e-13; }); @@ -696,7 +792,9 @@ struct GateDecompositionPattern final auto tmp = remEuclid(x, qc::PI_2); return std::min(tmp, qc::PI_2 - tmp); }); - std::array order{2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector order in eigen decomposition algorithm? + std::array order{ + 2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector + // order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); helpers::print(order, "ORDER (1)"); @@ -950,8 +1048,9 @@ struct GateDecompositionPattern final // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim // \text{SWAP}^\alpha` // - // (a non-equivalent root of SWAP from the TwoQubitWeylPartialSWAPEquiv - // similar to how :math:`x = (\pm \sqrt(x))^2`) + // (a non-equivalent root of SWAP from the + // TwoQubitWeylPartialSWAPEquiv similar to how :math:`x = (\pm + // \sqrt(x))^2`) // // This gate binds 3 parameters, we make it canonical by setting: // @@ -1158,7 +1257,7 @@ struct GateDecompositionPattern final struct TwoQubitBasisDecomposer { qc::OpType gate; - std::vector gate_params; + llvm::SmallVector gate_params; fp basis_fidelity; EulerBasis euler_basis; std::optional pulse_optimize; @@ -1185,13 +1284,14 @@ struct GateDecompositionPattern final matrix2x2 q2r; public: - static TwoQubitBasisDecomposer - new_inner(qc::OpType gate = qc::X, // CX - const std::vector& gate_params = {}, - matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, - 0, 0}, // CX matrix - fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, - std::optional pulse_optimize = std::nullopt) { + static TwoQubitBasisDecomposer new_inner( + qc::OpType gate = qc::X, // CX + const llvm::SmallVector& gate_params = {}, + // matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, + matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, + 0}, // CX matrix + fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, + std::optional pulse_optimize = std::nullopt) { using helpers::dot; using helpers::transpose_conjugate; @@ -1296,7 +1396,8 @@ struct GateDecompositionPattern final auto k1rd = transpose_conjugate(basis_decomposer.K1r); auto k2ld = transpose_conjugate(basis_decomposer.K2l); auto k2rd = transpose_conjugate(basis_decomposer.K2r); - // Pre-build the fixed parts of the matrices used in 3-part decomposition + // Pre-build the fixed parts of the matrices used in 3-part + // decomposition auto u0l = dot(k31l, k1ld); auto u0r = dot(k31r, k1rd); auto u1l = dot(dot(k2ld, k32l_k21l), k1ld); @@ -1364,11 +1465,11 @@ struct GateDecompositionPattern final unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); auto traces = this->traces(target_decomposed); auto get_default_nbasis = [&]() { - auto minValue = std::numeric_limits::max(); + auto minValue = std::numeric_limits::min(); auto minIndex = -1; for (std::size_t i = 0; i < traces.size(); ++i) { auto value = trace_to_fid(traces[i]) * std::pow(basis_fidelity, i); - if (value < minValue) { + if (value > minValue) { minIndex = i; minValue = value; } @@ -1392,11 +1493,23 @@ struct GateDecompositionPattern final throw std::logic_error{"Invalid basis to use"}; }; auto decomposition = choose_decomposition(); + std::cerr << "NBasis: " << (int)best_nbasis + << "; basis_fid: " << basis_fidelity + << "; Traces: " << traces[0] << ", " << traces[1] << ", " + << traces[2] << ", " << traces[3]; + std::cerr << "\nDecomposition:\n"; + for (auto x : decomposition) { + helpers::print(x, "", true); + } if (pulse_optimize.value_or(true)) { - return pulse_optimal_chooser(best_nbasis, decomposition, - target_decomposed); + if (auto result = pulse_optimal_chooser(best_nbasis, decomposition, + target_decomposed)) { + return result; + } } - std::vector target_1q_basis_list; + std::vector + target_1q_basis_list; // TODO: simplify because list only has one + // element? target_1q_basis_list.push_back(euler_basis); llvm::SmallVector, 8> euler_decompositions; @@ -1406,12 +1519,12 @@ struct GateDecompositionPattern final euler_decompositions.push_back(euler_decomp); } TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; - // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q gate - // We might overallocate a bit if the euler basis is different but + // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q + // gate We might overallocate a bit if the euler basis is different but // the worst case is just 16 extra elements with just a String and 2 - // smallvecs each. This is only transient though as the circuit sequences - // aren't long lived and are just used to create a QuantumCircuit or - // DAGCircuit when we return to Python space. + // smallvecs each. This is only transient though as the circuit + // sequences aren't long lived and are just used to create a + // QuantumCircuit or DAGCircuit when we return to Python space. constexpr auto TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY = 21; gates.gates.reserve(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); gates.globalPhase -= best_nbasis * basis_decomposer.global_phase; @@ -1515,8 +1628,8 @@ struct GateDecompositionPattern final if (gate != qc::X) { // CX if (pulse_optimize.has_value()) { - throw std::runtime_error{ - "pulse_optimizer currently only works with CNOT entangling gate"}; + throw std::runtime_error{"pulse_optimizer currently only works " + "with CNOT entangling gate"}; } return std::nullopt; } @@ -1542,8 +1655,8 @@ struct GateDecompositionPattern final /// This first decomposes each unitary from the KAK decomposition into ZXZ /// on the source qubit of the CNOTs and XZX on the targets in order to /// commute operators to beginning and end of decomposition. The beginning - /// and ending single qubit gates are then collapsed and re-decomposed with - /// the single qubit decomposer. This last step could be avoided if + /// and ending single qubit gates are then collapsed and re-decomposed + /// with the single qubit decomposer. This last step could be avoided if /// performance is a concern. TwoQubitGateSequence get_sx_vz_2cx_efficient_euler( const std::vector& decomposition, @@ -1604,12 +1717,13 @@ struct GateDecompositionPattern final /// gates assuming three CNOT gates are needed. /// /// This first decomposes each unitary from the KAK decomposition into ZXZ - /// on the source qubit of the CNOTs and XZX on the targets in order commute - /// operators to beginning and end of decomposition. Inserting Hadamards - /// reverses the direction of the CNOTs and transforms a variable Rx -> - /// variable virtual Rz. The beginning and ending single qubit gates are - /// then collapsed and re-decomposed with the single qubit decomposer. This - /// last step could be avoided if performance is a concern. + /// on the source qubit of the CNOTs and XZX on the targets in order + /// commute operators to beginning and end of decomposition. Inserting + /// Hadamards reverses the direction of the CNOTs and transforms a + /// variable Rx -> variable virtual Rz. The beginning and ending single + /// qubit gates are then collapsed and re-decomposed with the single qubit + /// decomposer. This last step could be avoided if performance is a + /// concern. std::optional get_sx_vz_3cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { @@ -1814,7 +1928,8 @@ struct GateDecompositionPattern final matrix2x2 unitary_mat, const std::vector& target_basis_list, std::size_t qubit, const std::vector>& - error_map, // TODO: remove error_map+qubit for platform independence + error_map, // TODO: remove error_map+qubit for platform + // independence bool simplify, std::optional atol) { auto calculateError = [](const OneQubitGateSequence& sequence) { return sequence.gates.size(); @@ -1845,8 +1960,8 @@ struct GateDecompositionPattern final * directory of this source tree or at * http://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 + * 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 @@ -1854,7 +1969,7 @@ struct GateDecompositionPattern final qc::OpType kGate, qc::OpType aGate, bool simplify, std::optional atol) { fp angleZeroEpsilon = atol.value_or(1e-12); - if (simplify) { + if (!simplify) { angleZeroEpsilon = -1.0; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 35d6f02d4..632a8fd62 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -40,42 +40,48 @@ constexpr qfp M_IM{0., -1.}; namespace mqt::ir::opt::helpers { // TODO: remove -template void print(std::array, N> matrix, std::string s = "") { +template void print(std::array, N> matrix, std::string s = "", bool force = false) { + if (!force) return; int i{}; + int n = std::sqrt(N); if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } for (auto&& a : matrix) { std::cerr << std::setprecision(17) << a.real() << 'i' << a.imag() << ' '; - if (++i % 4 == 0) { + if (++i % n == 0) { llvm::errs() << '\n'; } } llvm::errs() << '\n'; } -template void print(std::array matrix, std::string s = "") { +template void print(std::array matrix, std::string s = "", bool force = false) { + if (!force) return; int i{}; + int n = std::sqrt(N); if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } for (auto&& a : matrix) { std::cerr << std::setprecision(17) << a << ' '; - if (++i % 4 == 0) { + if (++i % n == 0) { llvm::errs() << '\n'; } } llvm::errs() << '\n'; } -template void print(std::array matrix, std::string s = "") { +template void print(std::array matrix, std::string s = "", bool force = false) { + if (!force) return; int i{}; + int n = std::sqrt(N); if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } for (auto&& a : matrix) { std::cerr << a << ' '; - if (++i % 4 == 0) { + if (++i % n == 0) { llvm::errs() << '\n'; } } @@ -83,7 +89,7 @@ template void print(std::array matrix, std::stri } inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { - std::array, 16> result; + std::array result; for (std::size_t i = 0; i < result.size(); ++i) { result[i] = matrix[i / 4][i % 4]; } @@ -172,8 +178,8 @@ inline std::optional mlirValueToFp(mlir::Value value) { return std::nullopt; } -[[nodiscard]] inline std::vector getParameters(UnitaryInterface op) { - std::vector parameters; +[[nodiscard]] inline llvm::SmallVector getParameters(UnitaryInterface op) { + llvm::SmallVector parameters; for (auto&& param : op.getParams()) { if (auto value = helpers::mlirValueToFp(param)) { parameters.push_back(*value); diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index b1a37228c..177126781 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -17,7 +17,7 @@ module { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK-NOT: %[[ANY:.*]] = mqtopt.x + // CHECK-NOT: mqtopt.[[ANY:.*]] // CHECK: mqtopt.deallocQubit %[[Q0_0]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] @@ -71,11 +71,10 @@ module { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK-NOT: %[[ANY:.*]] = mqtopt.x + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK: mqtopt.deallocQubit %[[Q0_0]] - // CHECK: mqtopt.deallocQubit %[[Q1_0]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -90,3 +89,78 @@ module { return } } + +module { + // CHECK-LABEL: func.func @testSeriesOneQubitOpInbetween + func.func @testSeriesOneQubitOpInbetween() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2 = mqtopt.x() %q0_1: !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_2 + + return + } +} + +module { + // CHECK-LABEL: func.func @testSeriesStartingOneQubitOp + func.func @testSeriesStartingOneQubitOp() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3 = mqtopt.x() %q0_1: !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_2 + + return + } +} + +module { + // CHECK-LABEL: func.func @testSeriesEndingOneQubitOp + func.func @testSeriesEndingOneQubitOp() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1 = mqtopt.x() %q0_0: !mqtopt.Qubit + %q0_2, %q1_1 = mqtopt.x() %q0_1 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_2 + + return + } +} From 4435948513e93d3a600e8ba53d07280f196ee53c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 21:18:29 +0100 Subject: [PATCH 028/100] fix test case --- .../MQTOpt/Transforms/gate-decomposition.mlir | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 177126781..490cffd6f 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -129,9 +129,9 @@ module { %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit - %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_3 = mqtopt.x() %q0_1: !mqtopt.Qubit + %q0_1 = mqtopt.x() %q0_0: !mqtopt.Qubit + %q0_2, %q1_1 = mqtopt.x() %q0_1 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit mqtopt.deallocQubit %q0_3 mqtopt.deallocQubit %q1_2 @@ -154,9 +154,9 @@ module { %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit - %q0_1 = mqtopt.x() %q0_0: !mqtopt.Qubit - %q0_2, %q1_1 = mqtopt.x() %q0_1 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2, %q1_2 = mqtopt.x() %q0_1 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3 = mqtopt.x() %q0_2: !mqtopt.Qubit mqtopt.deallocQubit %q0_3 mqtopt.deallocQubit %q1_2 From 0b12cb3e2ff5a8c86fe5d771499f4f6b7d956e13 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 21:18:42 +0100 Subject: [PATCH 029/100] minor fix --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 21060ae35..52457e92c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -59,7 +59,7 @@ struct GateDecompositionPattern final auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate.op), .parameter = helpers::getParameters(gate.op), - .qubit_id = gate.qubit_id}); + .qubit_id = gate.qubitIds}); unitaryMatrix = helpers::multiply(unitaryMatrix, gateMatrix); } @@ -88,7 +88,7 @@ struct GateDecompositionPattern final struct Gate { UnitaryInterface op; - llvm::SmallVector qubit_id; + llvm::SmallVector qubitIds; }; llvm::SmallVector gates; @@ -102,12 +102,10 @@ struct GateDecompositionPattern final } if (helpers::isSingleQubitOperation(userUnitary)) { - result.appendSingleQubitGate(userUnitary); - return true; + return result.appendSingleQubitGate(userUnitary); } if (helpers::isTwoQubitOperation(userUnitary)) { - result.appendTwoQubitGate(userUnitary); - return true; + return result.appendTwoQubitGate(userUnitary); } return false; }; @@ -130,7 +128,7 @@ struct GateDecompositionPattern final } private: - TwoQubitSeries(UnitaryInterface initialOperation) { + explicit TwoQubitSeries(UnitaryInterface initialOperation) { auto&& in = initialOperation.getAllInQubits(); auto&& out = initialOperation->getResults(); if (helpers::isSingleQubitOperation(initialOperation)) { @@ -303,7 +301,7 @@ struct GateDecompositionPattern final qubits.push_back(inQubits[x]); } auto newGate = - createGate(rewriter, location, qubits, {}, gate.parameter); + createGate(rewriter, location, qubits, {}, gate.parameter); updateInQubits(gate, newGate); } else if (gate.type == qc::RZ) { mlir::SmallVector qubits; From e3dbdb32d46ed39790c14db685654639bd5ce182 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 23:14:25 +0100 Subject: [PATCH 030/100] fix test case --- .../Dialect/MQTOpt/Transforms/gate-decomposition.mlir | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 490cffd6f..166936cfb 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -96,10 +96,12 @@ module { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: mqtopt.gphase(%[[C0:.*]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit From 3974582dab37ceea891c0e57a3e938589342ae87 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 30 Oct 2025 23:28:22 +0100 Subject: [PATCH 031/100] add new test, fix tests --- .../MQTOpt/Transforms/gate-decomposition.mlir | 72 +++++++++++++++++-- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 166936cfb..298a9c57f 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -9,7 +9,7 @@ // RUN: quantum-opt %s -split-input-file --gate-decomposition | FileCheck %s // ----- -// This test checks if two-qubit consecutive controlled self-inverses are canceled correctly. +// This test checks if an even number of CNOT gates will be cancelled out. module { // CHECK-LABEL: func.func @testEvenNegationSeries @@ -65,6 +65,9 @@ module { } } +// ----- +// This test checks if an odd number of CNOT gates will be reduced to a single CNOT. + module { // CHECK-LABEL: func.func @testOddNegationSeries func.func @testOddNegationSeries() { @@ -90,6 +93,9 @@ module { } } +// ----- +// This test checks if a two-qubit series containing a single-qubit gate is decomposed correctly. + module { // CHECK-LABEL: func.func @testSeriesOneQubitOpInbetween func.func @testSeriesOneQubitOpInbetween() { @@ -117,16 +123,21 @@ module { } } +// ----- +// This test checks if a two-qubit series starting on a single-qubit gate is decomposed correctly. + module { // CHECK-LABEL: func.func @testSeriesStartingOneQubitOp func.func @testSeriesStartingOneQubitOp() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: mqtopt.gphase(%[[C0:.*]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -142,16 +153,21 @@ module { } } +// ----- +// This test checks if a two-qubit series ending on a single-qubit gate is decomposed correctly. + module { // CHECK-LABEL: func.func @testSeriesEndingOneQubitOp func.func @testSeriesEndingOneQubitOp() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] + // CHECK: mqtopt.gphase(%[[C0:.*]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -166,3 +182,45 @@ module { return } } + +// ----- +// This test checks if an interrupted series is ignored correctly. + +module { + // CHECK-LABEL: func.func @testInterruptedSeries + func.func @testInterruptedSeries() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q1_2:.*]], %[[Q0_2:.*]] = mqtopt.x() %[[Q1_1]] ctrl %[[Q0_1]] + // CHECK: %[[Q0_3:.*]], %[[Q2_1:.*]] = mqtopt.x() %[[Q0_2]] ctrl %[[Q2_0]] + // CHECK: %[[Q1_3:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_2]] ctrl %[[Q0_3]] + // CHECK: %[[Q0_5:.*]], %[[Q1_4:.*]] = mqtopt.x() %[[Q0_4]] ctrl %[[Q1_3]] + + // CHECK-NOT: mqtopt.ry + // CHECK-NOT: mqtopt.rz + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.x() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q2_1 = mqtopt.x() %q0_2 ctrl %q2_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_3, %q0_4 = mqtopt.x() %q1_2 ctrl %q0_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_5 + mqtopt.deallocQubit %q1_4 + mqtopt.deallocQubit %q2_1 + + return + } +} From 458d1874a5a15ed6b83c11a813292d352642cdb3 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 16:29:29 +0100 Subject: [PATCH 032/100] iniital complexSeries test --- .../MQTOpt/Transforms/gate-decomposition.mlir | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 298a9c57f..1ea8f7235 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -224,3 +224,39 @@ module { return } } + +module { + // CHECK-LABEL: func.func @testComplexSeries + func.func @testComplexSeries() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + + %cst0 = arith.constant 2.5 : f64 + %cst1 = arith.constant 1.2 : f64 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1 = mqtopt.h() %q0_0: !mqtopt.Qubit + %q1_1, %q0_2 = mqtopt.x() %q1_0 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.rzz(%cst0) %q0_2, %q1_1: !mqtopt.Qubit, !mqtopt.Qubit + %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit + %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit + %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_6 = mqtopt.rz(%cst0) %q0_5: !mqtopt.Qubit + + mqtopt.deallocQubit %q0_6 + mqtopt.deallocQubit %q1_4 + mqtopt.deallocQubit %q2_0 + + return + } +} From 4cb24a02456a7debc697230827fc4ef25c57ade1 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 17:31:25 +0100 Subject: [PATCH 033/100] use Eigen for multiply() and determinant() --- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 92 +++++++------------- 1 file changed, 31 insertions(+), 61 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 632a8fd62..b52cf42fe 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -16,6 +16,7 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include +#include #include #include @@ -271,15 +272,31 @@ template template [[nodiscard]] inline auto multiply(const std::array& lhs, const std::array& rhs) { + const int n = std::sqrt(N); + Eigen::Matrix lhsEigen; + Eigen::Matrix rhsEigen; + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + lhsEigen(j, i) = lhs[j * n + i]; + rhsEigen(j, i) = rhs[j * n + i]; + } + } + auto r = lhsEigen * rhsEigen; std::array result{}; - const int n = std::sqrt(lhs.size()); - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - for (int k = 0; k < n; k++) { - result[i * n + j] += lhs[i * n + k] * rhs[k * n + j]; - } + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + result[j * n + i] = r(j, i); } } + // std::array result{}; + // const int n = std::sqrt(N); + // for (int i = 0; i < n; i++) { + // for (int j = 0; j < n; j++) { + // for (int k = 0; k < n; k++) { + // result[i * n + j] += lhs[i * n + k] * rhs[k * n + j]; + // } + // } + // } return result; } @@ -472,63 +489,16 @@ inline auto transpose_conjugate(Container&& matrix) { return conjugate(result); } -template -inline T determinant(const std::array& mat) { - return mat[0] * mat[3] - mat[1] * mat[2]; -} - -template -inline T determinant(const std::array& mat) { - return mat[0] * (mat[4] * mat[8] - mat[5] * mat[7]) - - mat[1] * (mat[3] * mat[8] - mat[5] * mat[6]) + - mat[2] * (mat[3] * mat[7] - mat[4] * mat[6]); -} - -inline std::array get3x3Submatrix(const matrix4x4& mat, - int rowToBeRemoved, - int columnToBeRemoved) { - std::array, 9> result; - int subIndex = 0; - for (int i = 0; i < 4; ++i) { - if (i != rowToBeRemoved) { - for (int j = 0; j < 4; ++j) { - if (j != columnToBeRemoved) { - result[subIndex++] = mat[i * 4 + j]; - } - } +template +inline T determinant(const std::array& mat) { + const int n = std::sqrt(N); + Eigen::Matrix matEigen; + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + matEigen(j, i) = mat[j * n + i]; } } - return result; -} - -template -inline T determinant(const std::array& mat) { - auto [l, u, rowPermutations] = helpers::LUdecomposition(mat); - T det = 1.0; - for (int i = 0; i < 4; ++i) { - det *= l[i * 4 + i]; - } - - if (rowPermutations % 2 != 0) { - det = -det; - } - return det; - - // auto det = -C_ZERO; - // for (int column = 0; column < 4; ++column) { - // auto submatrix = get3x3Submatrix(mat, 0, column); - // auto subDet = determinant(submatrix); - // auto tmp = mat[0 * 4 + column] * subDet; - // if (column % 2 == 0 && - // tmp != - // C_ZERO) { // TODO: better way to get negative 0.0 in - // determinant? - // det += tmp; - // } else if (tmp != -C_ZERO) { - // det -= tmp; - // } - // } - // return det; + return matEigen.determinant(); } template From 4cbc85ea6c3d4c1c8d758028e08d28d92e6dd94a Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 23:06:06 +0100 Subject: [PATCH 034/100] fix gate matrix multiplication --- mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 52457e92c..a85bf6c7c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -60,7 +60,7 @@ struct GateDecompositionPattern final getTwoQubitMatrix({.type = helpers::getQcType(gate.op), .parameter = helpers::getParameters(gate.op), .qubit_id = gate.qubitIds}); - unitaryMatrix = helpers::multiply(unitaryMatrix, gateMatrix); + unitaryMatrix = helpers::multiply(gateMatrix, unitaryMatrix); } auto decomposer = TwoQubitBasisDecomposer::new_inner(); From 374454b0dfe1ff31d697b938b25b955bf2c96aaa Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 23:06:33 +0100 Subject: [PATCH 035/100] add simple (too simple) series complexity --- .../Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index a85bf6c7c..e3e382bab 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -70,7 +70,7 @@ struct GateDecompositionPattern final llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } - if (sequence->gates.size() >= series.gates.size()) { + if (sequence->gates.size() >= series.complexity) { // TODO: add more sophisticated metric to determine complexity of // series/sequence llvm::errs() << "SEQUENCE LONGER THAN INPUT!\n"; @@ -83,6 +83,7 @@ struct GateDecompositionPattern final } struct TwoQubitSeries { + std::size_t complexity{0}; std::array inQubits; std::array outQubits; @@ -135,10 +136,12 @@ struct GateDecompositionPattern final inQubits = {in[0], mlir::Value{}}; outQubits = {out[0], mlir::Value{}}; gates.push_back({initialOperation, {0}}); + complexity += 1; } else if (helpers::isTwoQubitOperation(initialOperation)) { inQubits = {in[0], in[1]}; outQubits = {out[0], out[1]}; gates.push_back({initialOperation, {0, 1}}); + complexity += 2; } } @@ -156,6 +159,7 @@ struct GateDecompositionPattern final *it = nextGate->getResult(0); gates.push_back({nextGate, {qubitId}}); + complexity += 1; return true; } @@ -191,6 +195,7 @@ struct GateDecompositionPattern final std::distance(outQubits.begin(), secondQubitIt); gates.push_back({nextGate, {firstQubitId, secondQubitId}}); + complexity += 2; return true; } }; From 26b11a24b2cf1f57fa2ffbf8a748829f4f9118e6 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 23:07:22 +0100 Subject: [PATCH 036/100] fix insertion when operations which are not part of the series are involved --- .../Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e3e382bab..3d6959430 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -255,7 +255,10 @@ struct GateDecompositionPattern final static void applySeries(mlir::PatternRewriter& rewriter, TwoQubitSeries& series, const TwoQubitGateSequence& sequence) { - auto location = series.gates.back().op->getLoc(); + auto& lastSeriesOp = series.gates.back().op; + auto location = lastSeriesOp->getLoc(); + rewriter.setInsertionPointAfter(lastSeriesOp); + if (sequence.globalPhase != 0.0) { createOneParameterGate(rewriter, location, sequence.globalPhase, {}); From 2b648a9589ce660769ce409a6c6d0bb8a8cabd63 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 4 Nov 2025 23:08:13 +0100 Subject: [PATCH 037/100] add gate matrices --- .../Transforms/GateDecompositionPattern.cpp | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 3d6959430..9cb918fb5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -618,14 +618,29 @@ struct GateDecompositionPattern final if (gate.type == qc::SX) { return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; } + if (gate.type == qc::RX) { + return rx_matrix(gate.parameter[0]); + } + if (gate.type == qc::RY) { + return ry_matrix(gate.parameter[0]); + } if (gate.type == qc::RZ) { return rz_matrix(gate.parameter[0]); } if (gate.type == qc::X) { return {0, 1, 1, 0}; } + if (gate.type == qc::I) { + return identityGate; + } + if (gate.type == qc::H) { + static constexpr fp SQRT2_2 = static_cast( + 0.707106781186547524400844362104849039284835937688474036588L); + return {SQRT2_2, SQRT2_2, SQRT2_2, -SQRT2_2}; + } throw std::invalid_argument{ - "unsupported gate type for single qubit matrix"}; + "unsupported gate type for single qubit matrix (" + + qc::toString(gate.type) + ")"}; } static matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { @@ -654,13 +669,19 @@ struct GateDecompositionPattern final } } if (gate.type == qc::RZ) { + throw std::invalid_argument{"RZ for two-qubit gate matrix"}; // TODO: check qubit order return rzzMatrix(gate.parameter[0]); } if (gate.type == qc::RX) { + throw std::invalid_argument{"RX for two-qubit gate matrix"}; // TODO: check qubit order return rxxMatrix(gate.parameter[0]); } + if (gate.type == qc::RZZ) { + // TODO: check qubit order + return rzzMatrix(gate.parameter[0]); + } throw std::invalid_argument{ "unsupported gate type for two qubit matrix "}; } @@ -1123,7 +1144,7 @@ struct GateDecompositionPattern final angles_from_unitary(general.K2l, EulerBasis::ZYZ); auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = angles_from_unitary(general.K2r, EulerBasis::ZYZ); - TwoQubitWeylDecomposition{ + return TwoQubitWeylDecomposition{ qc::PI_4, qc::PI_4, c, From f5593348ddeb1481a7f176528b315c082c734f63 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 6 Nov 2025 14:10:06 +0100 Subject: [PATCH 038/100] sequence complexity() --- .../Transforms/GateDecompositionPattern.cpp | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 9cb918fb5..64b5b8071 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -15,9 +15,7 @@ #include #include -#include #include -#include #include #include #include @@ -70,7 +68,7 @@ struct GateDecompositionPattern final llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } - if (sequence->gates.size() >= series.complexity) { + if (sequence->complexity() >= series.complexity) { // TODO: add more sophisticated metric to determine complexity of // series/sequence llvm::errs() << "SEQUENCE LONGER THAN INPUT!\n"; @@ -247,6 +245,13 @@ struct GateDecompositionPattern final llvm::SmallVector qubit_id = {0}; }; std::vector gates; + std::size_t complexity() { + std::size_t c{}; + for (auto&& gate : gates) { + c += gate.qubit_id.size(); + } + return c; + } fp globalPhase; }; using OneQubitGateSequence = QubitGateSequence; @@ -401,7 +406,7 @@ struct GateDecompositionPattern final auto [U, S] = self_adjoint_evd(A); // TODO: not in original code - if (helpers::determinant(U) + 1.0 < 1e-5) { + if (std::abs(helpers::determinant(U) + 1.0) < 1e-5) { std::cerr << "CORRECTION!\n"; // if determinant of eigenvector matrix is -1.0, multiply first // eigenvector by -1.0 @@ -515,8 +520,12 @@ struct GateDecompositionPattern final } static matrix2x2 rz_matrix(fp theta) { - auto ilam2 = qfp(0., 0.5 * theta); - return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; + return {qfp{std::cos(theta / 2.), -std::sin(theta / 2.)}, + 0, + 0, + {std::cos(theta / 2.), std::sin(theta / 2.)}}; + // auto ilam2 = qfp(0., 0.5 * theta); + // return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; } static matrix4x4 rxxMatrix(const fp theta) { @@ -1284,6 +1293,7 @@ struct GateDecompositionPattern final struct TwoQubitBasisDecomposer { qc::OpType gate; + std::array gate_qubit_ids; llvm::SmallVector gate_params; fp basis_fidelity; EulerBasis euler_basis; @@ -1313,6 +1323,7 @@ struct GateDecompositionPattern final public: static TwoQubitBasisDecomposer new_inner( qc::OpType gate = qc::X, // CX + std::array gate_qubit_ids = {1, 0}, const llvm::SmallVector& gate_params = {}, // matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, @@ -1449,6 +1460,7 @@ struct GateDecompositionPattern final return TwoQubitBasisDecomposer{ gate, + gate_qubit_ids, gate_params, basis_fidelity, euler_basis, From 85d204caffa797c550b39528e449b5c6b8a283ed Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 6 Nov 2025 14:10:26 +0100 Subject: [PATCH 039/100] print debugging :) --- .../Transforms/GateDecompositionPattern.cpp | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 64b5b8071..da5b2cc5b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -40,6 +40,10 @@ struct GateDecompositionPattern final mlir::PatternRewriter& rewriter) const override { auto series = TwoQubitSeries::getTwoQubitSeries(op); llvm::errs() << "SERIES SIZE: " << series.gates.size() << '\n'; + for (auto&& gate : series.gates) { + std::cerr << gate.op->getName().stripDialect().str() << ", "; + } + std::cerr << '\n'; if (series.gates.size() < 3) { // too short @@ -53,13 +57,16 @@ struct GateDecompositionPattern final matrix4x4 unitaryMatrix = helpers::kroneckerProduct(identityGate, identityGate); + int i{}; for (auto&& gate : series.gates) { auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate.op), .parameter = helpers::getParameters(gate.op), .qubit_id = gate.qubitIds}); unitaryMatrix = helpers::multiply(gateMatrix, unitaryMatrix); + helpers::print(gateMatrix, "GATE MATRIX " + std::to_string(i++), true); } + helpers::print(unitaryMatrix, "UNITARY MATRIX", true); auto decomposer = TwoQubitBasisDecomposer::new_inner(); auto sequence = decomposer.twoQubitDecompose( @@ -71,7 +78,8 @@ struct GateDecompositionPattern final if (sequence->complexity() >= series.complexity) { // TODO: add more sophisticated metric to determine complexity of // series/sequence - llvm::errs() << "SEQUENCE LONGER THAN INPUT!\n"; + llvm::errs() << "SEQUENCE LONGER THAN INPUT (" << sequence->gates.size() + << ")\n"; return mlir::failure(); } @@ -721,28 +729,18 @@ struct GateDecompositionPattern final using helpers::transpose; auto& u = unitary_matrix; auto det_u = determinant(u); + std::cerr << "DET_U: " << det_u << '\n'; auto det_pow = std::pow(det_u, static_cast(-0.25)); llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); - llvm::errs() << "===== U =====\n"; - helpers::print(u); + helpers::print(u, "U", true); auto global_phase = std::arg(det_u) / 4.; auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); - llvm::errs() << "===== U_P =====\n"; - helpers::print(u_p); + helpers::print(u_p, "U_P", true); auto m2 = dot(transpose(u_p), u_p); auto default_euler_basis = EulerBasis::ZYZ; - llvm::errs() << "===== M2 =====\n"; - helpers::print(m2); - - // arma::Mat U(4, 4); - // for (int i = 0; i < 4; ++i) { - // for (int j = 0; j < 4; ++j) { - // U.at(j, i) = u_p[j * 4 + i]; - // } - // } - // auto x = U.st() * U; - // std::cerr << "ARMA\n" << U.t() << "\n\n" << U << "\n\n" << x << - // std::endl; + helpers::print(m2, "M2", true); + + std::cerr << "DET_U after division: " << determinant(u) << '\n'; // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -789,8 +787,8 @@ struct GateDecompositionPattern final [](auto&& x) { return qfp(x, 0.0); }); auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); - helpers::print(d_inner, "D_INNER"); - helpers::print(p_inner, "P_INNER"); + helpers::print(d_inner, "D_INNER", true); + helpers::print(p_inner, "P_INNER", true); matrix4x4 diag_d{}; // zero initialization diag_d[0 * 4 + 0] = d_inner[0]; diag_d[1 * 4 + 1] = d_inner[1]; From 1036007854e2f71df5c6524087214e7fa91f4d3c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 6 Nov 2025 14:10:52 +0100 Subject: [PATCH 040/100] longer complex test to pass sequence complexity check --- .../MQTOpt/Transforms/gate-decomposition.mlir | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 1ea8f7235..63b676b13 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -240,6 +240,7 @@ module { %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 + %cst2 = arith.constant 0.5 : f64 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit @@ -251,10 +252,19 @@ module { %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_6 = mqtopt.rz(%cst0) %q0_5: !mqtopt.Qubit - - mqtopt.deallocQubit %q0_6 - mqtopt.deallocQubit %q1_4 + %q0_6 = mqtopt.rz(%cst2) %q0_5: !mqtopt.Qubit + // make series longer to enforce decomposition + %q0_7 = mqtopt.i() %q0_6: !mqtopt.Qubit + %q0_8 = mqtopt.i() %q0_7: !mqtopt.Qubit + %q0_9 = mqtopt.i() %q0_8: !mqtopt.Qubit + %q1_5 = mqtopt.i() %q1_4: !mqtopt.Qubit + %q1_6 = mqtopt.i() %q1_5: !mqtopt.Qubit + %q1_7 = mqtopt.i() %q1_6: !mqtopt.Qubit + %q1_8 = mqtopt.i() %q1_7: !mqtopt.Qubit + %q1_9 = mqtopt.i() %q1_8: !mqtopt.Qubit + + mqtopt.deallocQubit %q0_9 + mqtopt.deallocQubit %q1_9 mqtopt.deallocQubit %q2_0 return From 1d654d19e40fe3113ccb4ad09d8b4d20f5034e35 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 10 Nov 2025 15:13:22 +0100 Subject: [PATCH 041/100] use CXGate() definition for decomposer --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index da5b2cc5b..6b6ce298e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -120,6 +120,8 @@ struct GateDecompositionPattern final bool isFirstQubitOngoing = result.outQubits[0] != mlir::Value{}; bool isSecondQubitOngoing = result.outQubits[1] != mlir::Value{}; while (isFirstQubitOngoing || isSecondQubitOngoing) { + // TODO: can cause issues; instead: per iteration, collect all + // single-qubit operations, then take one two-qubit operation; repeat if (result.outQubits[0]) { assert(result.outQubits[0].hasOneUse()); isFirstQubitOngoing = @@ -1321,10 +1323,11 @@ struct GateDecompositionPattern final public: static TwoQubitBasisDecomposer new_inner( qc::OpType gate = qc::X, // CX - std::array gate_qubit_ids = {1, 0}, + std::array gate_qubit_ids = {0, 1}, const llvm::SmallVector& gate_params = {}, - // matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, - matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, + matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, + // matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, + // 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}, // CX matrix fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, std::optional pulse_optimize = std::nullopt) { From bba842cc5355d5d192db337c76949d15d5a07f86 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 10 Nov 2025 15:13:44 +0100 Subject: [PATCH 042/100] add cnot test other direction --- .../MQTOpt/Transforms/gate-decomposition.mlir | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 63b676b13..402c79105 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -270,3 +270,50 @@ module { return } } + +module { + // CHECK-LABEL: func.func @testCNotOtherDirection + func.func @testCNotOtherDirection() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + + %cst0 = arith.constant 2.5 : f64 + %cst1 = arith.constant 1.2 : f64 + %cst2 = arith.constant 0.5 : f64 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_1 = mqtopt.i() %q0_0: !mqtopt.Qubit + %q1_1 = mqtopt.i() %q1_0: !mqtopt.Qubit + %q0_2 = mqtopt.i() %q0_1: !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_3 = mqtopt.i() %q1_2: !mqtopt.Qubit + %q0_4 = mqtopt.i() %q0_3: !mqtopt.Qubit + %q1_4 = mqtopt.i() %q1_3: !mqtopt.Qubit + %q0_5 = mqtopt.i() %q0_4: !mqtopt.Qubit + %q0_6 = mqtopt.i() %q0_5: !mqtopt.Qubit + %q0_7 = mqtopt.i() %q0_6: !mqtopt.Qubit + %q0_8 = mqtopt.i() %q0_7: !mqtopt.Qubit + %q0_9 = mqtopt.i() %q0_8: !mqtopt.Qubit + %q1_5 = mqtopt.i() %q1_4: !mqtopt.Qubit + %q1_6 = mqtopt.i() %q1_5: !mqtopt.Qubit + %q1_7 = mqtopt.i() %q1_6: !mqtopt.Qubit + %q1_8 = mqtopt.i() %q1_7: !mqtopt.Qubit + %q1_9 = mqtopt.i() %q1_8: !mqtopt.Qubit + + mqtopt.deallocQubit %q0_9 + mqtopt.deallocQubit %q1_9 + mqtopt.deallocQubit %q2_0 + + return + } +} From bda98d0829a9d70311b380811d8945f54f56e16d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 10 Nov 2025 15:13:56 +0100 Subject: [PATCH 043/100] tmp (to be deleted) --- .../Transforms/GateDecompositionPattern.cpp | 15 +++++++++------ mlir/lib/Dialect/MQTOpt/Transforms/g.h | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 6b6ce298e..8f765901f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -44,6 +44,10 @@ struct GateDecompositionPattern final std::cerr << gate.op->getName().stripDialect().str() << ", "; } std::cerr << '\n'; + static int a{}; + if (a++ > 0) { + return mlir::failure(); + } if (series.gates.size() < 3) { // too short @@ -815,14 +819,14 @@ struct GateDecompositionPattern final std::array d_real; llvm::transform(d, d_real.begin(), [](auto&& x) { return -std::arg(x) / 2.0; }); - helpers::print(d_real, "D_REAL"); + helpers::print(d_real, "D_REAL", true); d_real[3] = -d_real[0] - d_real[1] - d_real[2]; std::array cs; for (std::size_t i = 0; i < cs.size(); ++i) { assert(i < d_real.size()); cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::TAU); } - helpers::print(cs, "CS"); + helpers::print(cs, "CS", true); decltype(cs) cstemp; llvm::transform(cs, cstemp.begin(), [](auto&& x) { auto tmp = remEuclid(x, qc::PI_2); @@ -833,15 +837,14 @@ struct GateDecompositionPattern final // order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - helpers::print(order, "ORDER (1)"); std::tie(order[0], order[1], order[2]) = std::tuple{order[1], order[2], order[0]}; - helpers::print(order, "ORDER (2)"); + helpers::print(order, "ORDER", true); std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; std::tie(d_real[0], d_real[1], d_real[2]) = std::tuple{d_real[order[0]], d_real[order[1]], d_real[order[2]]}; - helpers::print(d_real, "D_REAL (sorted)"); + helpers::print(d_real, "D_REAL (sorted)", true); // swap columns of p according to order constexpr auto P_ROW_LENGTH = 4; @@ -867,7 +870,7 @@ struct GateDecompositionPattern final temp[2 * 4 + 2] = std::exp(IM * d_real[2]); temp[3 * 4 + 3] = std::exp(IM * d_real[3]); helpers::print(temp, "TEMP"); - helpers::print(p, "P"); + helpers::print(p, "P", true); auto k1 = magic_basis_transform(dot(dot(u_p, p), temp), MagicBasisTransform::Into); auto k2 = magic_basis_transform(transpose(p), MagicBasisTransform::Into); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/g.h b/mlir/lib/Dialect/MQTOpt/Transforms/g.h index d2927e943..8eb6c4054 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/g.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/g.h @@ -14,7 +14,7 @@ auto self_adjoint_evd(rmatrix4x4 A) { } } std::cerr << "=EigIN==\n" << a << "\n========\n" << std::endl; - s.computeDirect(a); + s.compute(a); // TODO: computeDirect is faster auto vecs = s.eigenvectors(); auto vals = s.eigenvalues(); rmatrix4x4 rvecs; From 246879c230b1fa0a632710f87f0b99b736a83503 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 10 Nov 2025 21:31:36 +0100 Subject: [PATCH 044/100] re-write using Eigen --- .../Transforms/GateDecompositionPattern.cpp | 659 ++++++------ mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 400 +------ mlir/lib/Dialect/MQTOpt/Transforms/a.h | 82 -- mlir/lib/Dialect/MQTOpt/Transforms/b.h | 469 --------- mlir/lib/Dialect/MQTOpt/Transforms/c.h | 142 --- mlir/lib/Dialect/MQTOpt/Transforms/d.h | 31 - mlir/lib/Dialect/MQTOpt/Transforms/e.h | 976 ------------------ mlir/lib/Dialect/MQTOpt/Transforms/f.h | 97 -- mlir/lib/Dialect/MQTOpt/Transforms/g.h | 32 - 9 files changed, 337 insertions(+), 2551 deletions(-) delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/a.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/b.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/c.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/d.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/e.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/f.h delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/g.h diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 8f765901f..69f5f1a25 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -9,12 +9,12 @@ */ #include "Helpers.h" -#include "g.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include #include +#include #include #include #include @@ -67,7 +67,7 @@ struct GateDecompositionPattern final getTwoQubitMatrix({.type = helpers::getQcType(gate.op), .parameter = helpers::getParameters(gate.op), .qubit_id = gate.qubitIds}); - unitaryMatrix = helpers::multiply(gateMatrix, unitaryMatrix); + unitaryMatrix = gateMatrix * unitaryMatrix; helpers::print(gateMatrix, "GATE MATRIX " + std::to_string(i++), true); } helpers::print(unitaryMatrix, "UNITARY MATRIX", true); @@ -99,7 +99,7 @@ struct GateDecompositionPattern final struct Gate { UnitaryInterface op; - llvm::SmallVector qubitIds; + llvm::SmallVector qubitIds; }; llvm::SmallVector gates; @@ -167,7 +167,7 @@ struct GateDecompositionPattern final throw std::logic_error{"Operand of single-qubit op and user of " "qubit is not in current outQubits"}; } - std::uint8_t qubitId = std::distance(outQubits.begin(), it); + std::size_t qubitId = std::distance(outQubits.begin(), it); *it = nextGate->getResult(0); gates.push_back({nextGate, {qubitId}}); @@ -200,10 +200,9 @@ struct GateDecompositionPattern final secondQubitIt = (secondQubitIt != outQubits.end()) ? secondQubitIt : it; } *firstQubitIt = nextGate->getResult(0); - std::uint8_t firstQubitId = - std::distance(outQubits.begin(), firstQubitIt); + std::size_t firstQubitId = std::distance(outQubits.begin(), firstQubitIt); *secondQubitIt = nextGate->getResult(1); - std::uint8_t secondQubitId = + std::size_t secondQubitId = std::distance(outQubits.begin(), secondQubitIt); gates.push_back({nextGate, {firstQubitId, secondQubitId}}); @@ -256,7 +255,7 @@ struct GateDecompositionPattern final struct Gate { qc::OpType type{qc::I}; llvm::SmallVector parameter; - llvm::SmallVector qubit_id = {0}; + llvm::SmallVector qubit_id = {0}; }; std::vector gates; std::size_t complexity() { @@ -385,9 +384,8 @@ struct GateDecompositionPattern final }; static constexpr auto sqrt2 = static_cast(1.4142135623730950488L); - static constexpr matrix2x2 identityGate = {1, 0, 0, 1}; - static constexpr matrix2x2 hGate = {1.0 / sqrt2, 1.0 / sqrt2, 1.0 / sqrt2, - -1.0 / sqrt2}; + static const matrix2x2 identityGate; + static const matrix2x2 hGate; static fp remEuclid(fp a, fp b) { auto r = std::fmod(a, b); @@ -417,15 +415,15 @@ struct GateDecompositionPattern final // rmatrix4x4 U; // jacobi_eigen_decomposition(A, U, S); - auto [U, S] = self_adjoint_evd(A); + auto [U, S] = helpers::self_adjoint_evd(A); // TODO: not in original code - if (std::abs(helpers::determinant(U) + 1.0) < 1e-5) { + if (std::abs(U.determinant() + 1.0) < 1e-5) { std::cerr << "CORRECTION!\n"; // if determinant of eigenvector matrix is -1.0, multiply first // eigenvector by -1.0 for (int i = 0; i < 4; ++i) { - U[i * 4 + 0] *= -1.0; + U(i, 0) *= -1.0; } } @@ -434,50 +432,43 @@ struct GateDecompositionPattern final static std::tuple decompose_two_qubit_product_gate(matrix4x4 special_unitary) { - using helpers::determinant; - using helpers::dot; - using helpers::kroneckerProduct; - using helpers::transpose_conjugate; helpers::print(special_unitary, "SPECIAL_UNITARY"); // first quadrant - matrix2x2 r = {special_unitary[0 * 4 + 0], special_unitary[0 * 4 + 1], - special_unitary[1 * 4 + 0], special_unitary[1 * 4 + 1]}; - auto det_r = determinant(r); + matrix2x2 r{{special_unitary(0, 0), special_unitary(0, 1)}, + {special_unitary(1, 0), special_unitary(1, 1)}}; + auto det_r = r.determinant(); if (std::abs(det_r) < 0.1) { // third quadrant - r = {special_unitary[2 * 4 + 0], special_unitary[2 * 4 + 1], - special_unitary[3 * 4 + 0], special_unitary[3 * 4 + 1]}; - det_r = determinant(r); + r = matrix2x2{{special_unitary(2, 0), special_unitary(2, 1)}, + {special_unitary(3, 0), special_unitary(3, 1)}}; + det_r = r.determinant(); } std::cerr << "DET_R: " << det_r << '\n'; if (std::abs(det_r) < 0.1) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; } - llvm::transform(r, r.begin(), - [&](auto&& x) { return x / std::sqrt(det_r); }); + r /= std::sqrt(det_r); helpers::print(r, "R"); // transpose with complex conjugate of each element - matrix2x2 r_t_conj = transpose_conjugate(r); + matrix2x2 r_t_conj = r.transpose().conjugate(); - auto temp = kroneckerProduct(identityGate, r_t_conj); + auto temp = helpers::kroneckerProduct(identityGate, r_t_conj); helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 1)"); - temp = dot(special_unitary, temp); + temp = special_unitary * temp; helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 2)"); // [[a, b, c, d], // [e, f, g, h], => [[a, c], // [i, j, k, l], [i, k]] // [m, n, o, p]] - matrix2x2 l = {temp[0 * 4 + 0], temp[0 * 4 + 2], temp[2 * 4 + 0], - temp[2 * 4 + 2]}; - auto det_l = determinant(l); + matrix2x2 l{{temp(0, 0), temp(0, 2)}, {temp(2, 0), temp(2, 2)}}; + auto det_l = l.determinant(); if (std::abs(det_l) < 0.9) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9"}; } - llvm::transform(l, l.begin(), - [&](auto&& x) { return x / std::sqrt(det_l); }); + l /= std::sqrt(det_l); auto phase = std::arg(det_l) / 2.; return {l, r, phase}; @@ -485,24 +476,25 @@ struct GateDecompositionPattern final static matrix4x4 magic_basis_transform(const matrix4x4& unitary, MagicBasisTransform direction) { - using helpers::dot; - constexpr matrix4x4 B_NON_NORMALIZED = { - C_ONE, IM, C_ZERO, C_ZERO, C_ZERO, C_ZERO, IM, C_ONE, - C_ZERO, C_ZERO, IM, C_M_ONE, C_ONE, M_IM, C_ZERO, C_ZERO, + const matrix4x4 B_NON_NORMALIZED{ + {C_ONE, IM, C_ZERO, C_ZERO}, + {C_ZERO, C_ZERO, IM, C_ONE}, + {C_ZERO, C_ZERO, IM, C_M_ONE}, + {C_ONE, M_IM, C_ZERO, C_ZERO}, }; - constexpr matrix4x4 B_NON_NORMALIZED_DAGGER = { - qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.), - qfp(0., -0.5), C_ZERO, C_ZERO, qfp(0., 0.5), - C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO, - C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO, + const matrix4x4 B_NON_NORMALIZED_DAGGER{ + {qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.)}, + {qfp(0., -0.5), C_ZERO, C_ZERO, qfp(0., 0.5)}, + {C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO}, + {C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO}, }; helpers::print(unitary, "UNITARY in MAGIC BASIS TRANSFORM"); if (direction == MagicBasisTransform::OutOf) { - return dot(dot(B_NON_NORMALIZED_DAGGER, unitary), B_NON_NORMALIZED); + return B_NON_NORMALIZED_DAGGER * unitary * B_NON_NORMALIZED; } if (direction == MagicBasisTransform::Into) { - return dot(dot(B_NON_NORMALIZED, unitary), B_NON_NORMALIZED_DAGGER); + return B_NON_NORMALIZED * unitary * B_NON_NORMALIZED_DAGGER; } throw std::logic_error{"Unknown MagicBasisTransform direction!"}; } @@ -523,21 +515,19 @@ struct GateDecompositionPattern final auto half_theta = theta / 2.; auto cos = qfp(std::cos(half_theta), 0.); auto isin = qfp(0., -std::sin(half_theta)); - return {cos, isin, isin, cos}; + return matrix2x2{{cos, isin}, {isin, cos}}; } static matrix2x2 ry_matrix(fp theta) { auto half_theta = theta / 2.; auto cos = qfp(std::cos(half_theta), 0.); auto sin = qfp(std::sin(half_theta), 0.); - return {cos, -sin, sin, cos}; + return matrix2x2{{cos, -sin}, {sin, cos}}; } static matrix2x2 rz_matrix(fp theta) { - return {qfp{std::cos(theta / 2.), -std::sin(theta / 2.)}, - 0, - 0, - {std::cos(theta / 2.), std::sin(theta / 2.)}}; + return matrix2x2{{qfp{std::cos(theta / 2.), -std::sin(theta / 2.)}, 0}, + {0, qfp{std::cos(theta / 2.), std::sin(theta / 2.)}}}; // auto ilam2 = qfp(0., 0.5 * theta); // return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; } @@ -546,32 +536,20 @@ struct GateDecompositionPattern final const auto cosTheta = std::cos(theta / 2.); const auto sinTheta = std::sin(theta / 2.); - return {{cosTheta, - C_ZERO, - C_ZERO, - {0., -sinTheta}, - C_ZERO, - cosTheta, - {0., -sinTheta}, - C_ZERO, - C_ZERO, - {0., -sinTheta}, - cosTheta, - C_ZERO, - {0., -sinTheta}, - C_ZERO, - C_ZERO, - cosTheta}}; + return matrix4x4{{cosTheta, C_ZERO, C_ZERO, {0., -sinTheta}}, + {C_ZERO, cosTheta, {0., -sinTheta}, C_ZERO}, + {C_ZERO, {0., -sinTheta}, cosTheta, C_ZERO}, + {{0., -sinTheta}, C_ZERO, C_ZERO, cosTheta}}; } static matrix4x4 rzzMatrix(const fp theta) { const auto cosTheta = std::cos(theta / 2.); const auto sinTheta = std::sin(theta / 2.); - return {qfp{cosTheta, -sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - {cosTheta, sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - {cosTheta, sinTheta}, C_ZERO, C_ZERO, C_ZERO, C_ZERO, - {cosTheta, -sinTheta}}; + return matrix4x4{{qfp{cosTheta, -sinTheta}, C_ZERO, C_ZERO, C_ZERO}, + {C_ZERO, {cosTheta, sinTheta}, C_ZERO, C_ZERO}, + {C_ZERO, C_ZERO, {cosTheta, sinTheta}, C_ZERO}, + {C_ZERO, C_ZERO, C_ZERO, {cosTheta, -sinTheta}}}; } static std::array angles_from_unitary(const matrix2x2& matrix, @@ -589,18 +567,12 @@ struct GateDecompositionPattern final } static std::array params_zyz_inner(const matrix2x2& matrix) { - auto getIndex = [](auto x, auto y) { return (y * 2) + x; }; - auto determinant = [getIndex](auto&& matrix) { - return (matrix.at(getIndex(0, 0)) * matrix.at(getIndex(1, 1))) - - (matrix.at(getIndex(1, 0)) * matrix.at(getIndex(0, 1))); - }; - - auto detArg = std::arg(determinant(matrix)); + auto detArg = std::arg(matrix.determinant()); auto phase = 0.5 * detArg; - auto theta = 2. * std::atan2(std::abs(matrix.at(getIndex(1, 0))), - std::abs(matrix.at(getIndex(0, 0)))); - auto ang1 = std::arg(matrix.at(getIndex(1, 1))); - auto ang2 = std::arg(matrix.at(getIndex(1, 0))); + auto theta = + 2. * std::atan2(std::abs(matrix(1, 0)), std::abs(matrix(0, 0))); + auto ang1 = std::arg(matrix(1, 1)); + auto ang2 = std::arg(matrix(1, 0)); auto phi = ang1 + ang2 - detArg; auto lam = ang1 - ang2; return {theta, phi, lam, phase}; @@ -612,15 +584,15 @@ struct GateDecompositionPattern final } static std::array params_xyx_inner(const matrix2x2& matrix) { - auto mat_zyz = std::array{ - static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1) + - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1) + - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) + matrix.at(0 * 2 + 1) - - matrix.at(1 * 2 + 0) - matrix.at(1 * 2 + 1)), - static_cast(0.5) * (matrix.at(0 * 2 + 0) - matrix.at(0 * 2 + 1) - - matrix.at(1 * 2 + 0) + matrix.at(1 * 2 + 1)), + auto mat_zyz = matrix2x2{ + {static_cast(0.5) * + (matrix(0, 0) + matrix(0, 1) + matrix(1, 0) + matrix(1, 1)), + static_cast(0.5) * + (matrix(0, 0) - matrix(0, 1) + matrix(1, 0) - matrix(1, 1))}, + {static_cast(0.5) * + (matrix(0, 0) + matrix(0, 1) - matrix(1, 0) - matrix(1, 1)), + static_cast(0.5) * + (matrix(0, 0) - matrix(0, 1) - matrix(1, 0) + matrix(1, 1))}, }; auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); auto new_phi = mod2pi(phi + qc::PI, 0.); @@ -633,13 +605,14 @@ struct GateDecompositionPattern final }; } - static constexpr std::array IPZ = {IM, C_ZERO, C_ZERO, M_IM}; - static constexpr std::array IPY = {C_ZERO, C_ONE, C_M_ONE, C_ZERO}; - static constexpr std::array IPX = {C_ZERO, IM, IM, C_ZERO}; + static const matrix2x2 IPZ; + static const matrix2x2 IPY; + static const matrix2x2 IPX; static matrix2x2 getSingleQubitMatrix(const QubitGateSequence::Gate& gate) { if (gate.type == qc::SX) { - return {qfp{0.5, 0.5}, qfp{0.5, -0.5}, qfp{0.5, -0.5}, qfp{0.5, 0.5}}; + return matrix2x2{{qfp{0.5, 0.5}, qfp{0.5, -0.5}}, + {qfp{0.5, -0.5}, qfp{0.5, 0.5}}}; } if (gate.type == qc::RX) { return rx_matrix(gate.parameter[0]); @@ -651,7 +624,7 @@ struct GateDecompositionPattern final return rz_matrix(gate.parameter[0]); } if (gate.type == qc::X) { - return {0, 1, 1, 0}; + return matrix2x2{{0, 1}, {1, 0}}; } if (gate.type == qc::I) { return identityGate; @@ -659,7 +632,7 @@ struct GateDecompositionPattern final if (gate.type == qc::H) { static constexpr fp SQRT2_2 = static_cast( 0.707106781186547524400844362104849039284835937688474036588L); - return {SQRT2_2, SQRT2_2, SQRT2_2, -SQRT2_2}; + return matrix2x2{{SQRT2_2, SQRT2_2}, {SQRT2_2, -SQRT2_2}}; } throw std::invalid_argument{ "unsupported gate type for single qubit matrix (" + @@ -684,11 +657,13 @@ struct GateDecompositionPattern final if (gate.qubit_id.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubit_id == llvm::SmallVector{0, 1}) { - return {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0}; + if (gate.qubit_id == llvm::SmallVector{1, 0}) { + return matrix4x4{ + {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubit_id == llvm::SmallVector{1, 0}) { - return {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}; + if (gate.qubit_id == llvm::SmallVector{0, 1}) { + return matrix4x4{ + {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } } if (gate.type == qc::RZ) { @@ -716,10 +691,10 @@ struct GateDecompositionPattern final fp b; fp c; fp global_phase; - std::array K1l; - std::array K2l; - std::array K1r; - std::array K2r; + matrix2x2 K1l; + matrix2x2 K2l; + matrix2x2 K1r; + matrix2x2 K2r; Specialization specialization; EulerBasis default_euler_basis; std::optional requested_fidelity; @@ -729,24 +704,20 @@ struct GateDecompositionPattern final static TwoQubitWeylDecomposition new_inner(matrix4x4 unitary_matrix, std::optional fidelity, std::optional _specialization) { - using helpers::determinant; - using helpers::diagonal; - using helpers::dot; - using helpers::transpose; auto& u = unitary_matrix; - auto det_u = determinant(u); + auto det_u = u.determinant(); std::cerr << "DET_U: " << det_u << '\n'; auto det_pow = std::pow(det_u, static_cast(-0.25)); - llvm::transform(u, u.begin(), [&](auto&& x) { return x * det_pow; }); + u *= det_pow; helpers::print(u, "U", true); auto global_phase = std::arg(det_u) / 4.; auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); helpers::print(u_p, "U_P", true); - auto m2 = dot(transpose(u_p), u_p); + matrix4x4 m2 = u_p.transpose() * u_p; auto default_euler_basis = EulerBasis::ZYZ; helpers::print(m2, "M2", true); - std::cerr << "DET_U after division: " << determinant(u) << '\n'; + std::cerr << "DET_U after division: " << u.determinant() << '\n'; // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -765,8 +736,8 @@ struct GateDecompositionPattern final auto state = std::mt19937{2023}; std::normal_distribution dist; auto found = false; - diagonal4x4 d{{C_ZERO}}; - matrix4x4 p{{C_ZERO}}; + diagonal4x4 d = diagonal4x4::Zero(); + matrix4x4 p = matrix4x4::Zero(); for (int i = 0; i < 100; ++i) { fp rand_a; @@ -783,29 +754,18 @@ struct GateDecompositionPattern final rand_a = dist(state); rand_b = dist(state); } - std::array m2_real; - llvm::transform(m2, m2_real.begin(), [&](const qfp& val) { - return rand_a * val.real() + rand_b * val.imag(); - }); + rmatrix4x4 m2_real = rand_a * m2.real() + rand_b * m2.imag(); rmatrix4x4 p_inner_real = self_adjoint_eigen_lower(m2_real).first; - matrix4x4 p_inner; - llvm::transform(p_inner_real, p_inner.begin(), - [](auto&& x) { return qfp(x, 0.0); }); - auto d_inner = diagonal(dot(dot(transpose(p_inner), m2), p_inner)); + matrix4x4 p_inner = p_inner_real; + diagonal4x4 d_inner = (p_inner.transpose() * m2 * p_inner).diagonal(); helpers::print(d_inner, "D_INNER", true); helpers::print(p_inner, "P_INNER", true); - matrix4x4 diag_d{}; // zero initialization - diag_d[0 * 4 + 0] = d_inner[0]; - diag_d[1 * 4 + 1] = d_inner[1]; - diag_d[2 * 4 + 2] = d_inner[2]; - diag_d[3 * 4 + 3] = d_inner[3]; + matrix4x4 diag_d = d_inner.asDiagonal(); - auto compare = dot(dot(p_inner, diag_d), transpose(p_inner)); + matrix4x4 compare = p_inner * diag_d * p_inner.transpose(); helpers::print(compare, "COMPARE"); - found = llvm::all_of_zip(compare, m2, [](auto&& a, auto&& b) { - return std::abs(a - b) <= 1.0e-13; - }); + found = (compare - m2).cwiseAbs().cwiseLessOrEqual(1.0e-13).all(); if (found) { p = p_inner; d = d_inner; @@ -816,15 +776,13 @@ struct GateDecompositionPattern final throw std::runtime_error{ "TwoQubitWeylDecomposition: failed to diagonalize M2."}; } - std::array d_real; - llvm::transform(d, d_real.begin(), - [](auto&& x) { return -std::arg(x) / 2.0; }); + rdiagonal4x4 d_real = -1.0 * d.cwiseArg() / 2.0; helpers::print(d_real, "D_REAL", true); - d_real[3] = -d_real[0] - d_real[1] - d_real[2]; + d_real(3) = -d_real(0) - d_real(1) - d_real(2); std::array cs; for (std::size_t i = 0; i < cs.size(); ++i) { assert(i < d_real.size()); - cs[i] = remEuclid((d_real[i] + d_real[3]) / 2.0, qc::TAU); + cs[i] = remEuclid((d_real(i) + d_real(3)) / 2.0, qc::TAU); } helpers::print(cs, "CS", true); decltype(cs) cstemp; @@ -842,38 +800,29 @@ struct GateDecompositionPattern final helpers::print(order, "ORDER", true); std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - std::tie(d_real[0], d_real[1], d_real[2]) = - std::tuple{d_real[order[0]], d_real[order[1]], d_real[order[2]]}; + std::tie(d_real(0), d_real(1), d_real(2)) = + std::tuple{d_real(order[0]), d_real(order[1]), d_real(order[2])}; helpers::print(d_real, "D_REAL (sorted)", true); // swap columns of p according to order - constexpr auto P_ROW_LENGTH = 4; auto p_orig = p; for (std::size_t i = 0; i < order.size(); ++i) { - for (std::size_t row = 0; row < P_ROW_LENGTH; ++row) { - std::swap(p[row * P_ROW_LENGTH + i], - p_orig[row * P_ROW_LENGTH + order[i]]); - } + p.col(i) = p_orig.col(order[i]); } - if (determinant(p).real() < 0.0) { - // negate last column - for (int i = 0; i < P_ROW_LENGTH; ++i) { - auto& x = p[i * P_ROW_LENGTH + P_ROW_LENGTH - 1]; - x = -x; - } + if (p.determinant().real() < 0.0) { + auto lastColumnIndex = p.cols() - 1; + p.col(lastColumnIndex) = -p.col(lastColumnIndex); } - matrix4x4 temp{}; - temp[0 * 4 + 0] = std::exp(IM * d_real[0]); - temp[1 * 4 + 1] = std::exp(IM * d_real[1]); - temp[2 * 4 + 2] = std::exp(IM * d_real[2]); - temp[3 * 4 + 3] = std::exp(IM * d_real[3]); + matrix4x4 temp = d_real.asDiagonal(); + temp *= IM; + temp = temp.exp(); helpers::print(temp, "TEMP"); helpers::print(p, "P", true); - auto k1 = magic_basis_transform(dot(dot(u_p, p), temp), - MagicBasisTransform::Into); - auto k2 = magic_basis_transform(transpose(p), MagicBasisTransform::Into); + auto k1 = + magic_basis_transform(u_p * p * temp, MagicBasisTransform::Into); + auto k2 = magic_basis_transform(p.transpose(), MagicBasisTransform::Into); auto [K1l, K1r, phase_l] = decompose_two_qubit_product_gate(k1); auto [K2l, K2r, phase_r] = decompose_two_qubit_product_gate(k2); @@ -882,28 +831,28 @@ struct GateDecompositionPattern final // Flip into Weyl chamber if (cs[0] > qc::PI_2) { cs[0] -= 3.0 * qc::PI_2; - K1l = dot(K1l, IPY); - K1r = dot(K1r, IPY); + K1l = K1l * IPY; + K1r = K1r * IPY; global_phase += qc::PI_2; } if (cs[1] > qc::PI_2) { cs[1] -= 3.0 * qc::PI_2; - K1l = dot(K1l, IPX); - K1r = dot(K1r, IPX); + K1l = K1l * IPX; + K1r = K1r * IPX; global_phase += qc::PI_2; } auto conjs = 0; if (cs[0] > qc::PI_4) { cs[0] = qc::PI_2 - cs[0]; - K1l = dot(K1l, IPY); - K2r = dot(IPY, K2r); + K1l = K1l * IPY; + K2r = IPY * K2r; conjs += 1; global_phase -= qc::PI_2; } if (cs[1] > qc::PI_4) { cs[1] = qc::PI_2 - cs[1]; - K1l = dot(K1l, IPX); - K2r = dot(IPX, K2r); + K1l = K1l * IPX; + K2r = IPX * K2r; conjs += 1; global_phase += qc::PI_2; if (conjs == 1) { @@ -912,8 +861,8 @@ struct GateDecompositionPattern final } if (cs[2] > qc::PI_2) { cs[2] -= 3.0 * qc::PI_2; - K1l = dot(K1l, IPZ); - K1r = dot(K1r, IPZ); + K1l = K1l * IPZ; + K1r = K1r * IPZ; global_phase += qc::PI_2; if (conjs == 1) { global_phase -= qc::PI; @@ -921,14 +870,14 @@ struct GateDecompositionPattern final } if (conjs == 1) { cs[2] = qc::PI_2 - cs[2]; - K1l = dot(K1l, IPZ); - K2r = dot(IPZ, K2r); + K1l = K1l * IPZ; + K2r = IPZ * K2r; global_phase += qc::PI_2; } if (cs[2] > qc::PI_4) { cs[2] -= qc::PI_2; - K1l = dot(K1l, IPZ); - K1r = dot(K1r, IPZ); + K1l = K1l * IPZ; + K1r = K1r * IPZ; global_phase -= qc::PI_2; } auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); @@ -1003,9 +952,9 @@ struct GateDecompositionPattern final 0., 0., general.global_phase, - dot(general.K1l, general.K2l), + general.K1l * general.K2l, identityGate, - dot(general.K1r, general.K2r), + general.K1r * general.K2r, identityGate, specialization, general.default_euler_basis, @@ -1027,9 +976,9 @@ struct GateDecompositionPattern final qc::PI_4, qc::PI_4, general.global_phase, - dot(general.K1l, general.K2r), + general.K1l * general.K2r, identityGate, - dot(general.K1r, general.K2l), + general.K1r * general.K2l, identityGate, specialization, general.default_euler_basis, @@ -1044,9 +993,9 @@ struct GateDecompositionPattern final qc::PI_4, qc::PI_4, global_phase + qc::PI_2, - dot(dot(general.K1l, IPZ), general.K2r), + general.K1l * IPZ * general.K2r, identityGate, - dot(dot(general.K1r, IPZ), general.K2l), + general.K1r * IPZ * general.K2l, identityGate, specialization, general.default_euler_basis, @@ -1064,19 +1013,17 @@ struct GateDecompositionPattern final // :math:`K2_l = Id`. if (specialization == Specialization::PartialSWAPEquiv) { auto closest = closest_partial_swap(a, b, c); - auto k2l_dag = transpose(general.K2l); - llvm::transform(k2l_dag, k2l_dag.begin(), - [](auto&& x) { return std::conj(x); }); + auto k2l_dag = general.K2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ closest, closest, closest, general.global_phase, - dot(general.K1l, general.K2l), + general.K1l * general.K2l, identityGate, - dot(general.K1r, general.K2l), - dot(k2l_dag, general.K2r), + general.K1r * general.K2l, + k2l_dag * general.K2r, specialization, general.default_euler_basis, general.requested_fidelity, @@ -1096,19 +1043,17 @@ struct GateDecompositionPattern final // :math:`K2_l = Id` if (specialization == Specialization::PartialSWAPFlipEquiv) { auto closest = closest_partial_swap(a, b, -c); - auto k2l_dag = transpose(general.K2l); - llvm::transform(k2l_dag, k2l_dag.begin(), - [](auto&& x) { return std::conj(x); }); + auto k2l_dag = general.K2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ closest, closest, -closest, general.global_phase, - dot(general.K1l, general.K2l), + general.K1l * general.K2l, identityGate, - dot(dot(dot(general.K1r, IPZ), general.K2l), IPZ), - dot(dot(dot(IPZ, k2l_dag), IPZ), general.K2r), + general.K1r * IPZ * general.K2l * IPZ, + IPZ * k2l_dag * IPZ * general.K2r, specialization, general.default_euler_basis, general.requested_fidelity, @@ -1133,10 +1078,10 @@ struct GateDecompositionPattern final 0., 0., global_phase + k2lphase + k2rphase, - dot(general.K1l, rx_matrix(k2lphi)), - dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), - dot(general.K1r, rx_matrix(k2rphi)), - dot(ry_matrix(k2rtheta), rx_matrix(k2rlambda)), + general.K1l * rx_matrix(k2lphi), + ry_matrix(k2ltheta) * rx_matrix(k2llambda), + general.K1r * rx_matrix(k2rphi), + ry_matrix(k2rtheta) * rx_matrix(k2rlambda), specialization, euler_basis, general.requested_fidelity, @@ -1161,10 +1106,10 @@ struct GateDecompositionPattern final qc::PI_4, c, global_phase + k2lphase + k2rphase, - dot(general.K1l, rz_matrix(k2rphi)), - dot(ry_matrix(k2ltheta), rz_matrix(k2llambda)), - dot(general.K1r, rz_matrix(k2lphi)), - dot(ry_matrix(k2rtheta), rz_matrix(k2rlambda)), + general.K1l * rz_matrix(k2rphi), + ry_matrix(k2ltheta) * rz_matrix(k2llambda), + general.K1r * rz_matrix(k2lphi), + ry_matrix(k2rtheta) * rz_matrix(k2rlambda), specialization, general.default_euler_basis, general.requested_fidelity, @@ -1185,10 +1130,10 @@ struct GateDecompositionPattern final (a + b) / 2., c, global_phase + k2lphase, - dot(general.K1l, rz_matrix(k2lphi)), - dot(ry_matrix(k2ltheta), rz_matrix(k2llambda)), - dot(general.K1r, rz_matrix(k2lphi)), - dot(rz_matrix(-k2lphi), general.K2r), + general.K1l * rz_matrix(k2lphi), + ry_matrix(k2ltheta) * rz_matrix(k2llambda), + general.K1r * rz_matrix(k2lphi), + rz_matrix(-k2lphi) * general.K2r, specialization, general.default_euler_basis, general.requested_fidelity, @@ -1210,10 +1155,10 @@ struct GateDecompositionPattern final (b + c) / 2., (b + c) / 2., global_phase + k2lphase, - dot(general.K1l, rx_matrix(k2lphi)), - dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), - dot(general.K1r, rx_matrix(k2lphi)), - dot(rx_matrix(-k2lphi), general.K2r), + general.K1l * rx_matrix(k2lphi), + ry_matrix(k2ltheta) * rx_matrix(k2llambda), + general.K1r * rx_matrix(k2lphi), + rx_matrix(-k2lphi) * general.K2r, specialization, euler_basis, general.requested_fidelity, @@ -1235,10 +1180,10 @@ struct GateDecompositionPattern final (b - c) / 2., -((b - c) / 2.), global_phase + k2lphase, - dot(general.K1l, rx_matrix(k2lphi)), - dot(ry_matrix(k2ltheta), rx_matrix(k2llambda)), - dot(dot(dot(general.K1r, IPZ), rx_matrix(k2lphi)), IPZ), - dot(dot(dot(IPZ, rx_matrix(-k2lphi)), IPZ), general.K2r), + general.K1l * rx_matrix(k2lphi), + ry_matrix(k2ltheta) * rx_matrix(k2llambda), + general.K1r * IPZ * rx_matrix(k2lphi) * IPZ, + IPZ * rx_matrix(-k2lphi) * IPZ * general.K2r, specialization, euler_basis, general.requested_fidelity, @@ -1295,9 +1240,7 @@ struct GateDecompositionPattern final static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; struct TwoQubitBasisDecomposer { - qc::OpType gate; - std::array gate_qubit_ids; - llvm::SmallVector gate_params; + QubitGateSequence::Gate basis_gate; fp basis_fidelity; EulerBasis euler_basis; std::optional pulse_optimize; @@ -1324,19 +1267,12 @@ struct GateDecompositionPattern final matrix2x2 q2r; public: - static TwoQubitBasisDecomposer new_inner( - qc::OpType gate = qc::X, // CX - std::array gate_qubit_ids = {0, 1}, - const llvm::SmallVector& gate_params = {}, - matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, - // matrix4x4 gate_matrix = {1, 0, 0, 0, 0, 1, - // 0, 0, 0, 0, 0, 1, 0, 0, 1, - 0}, // CX matrix - fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, - std::optional pulse_optimize = std::nullopt) { - using helpers::dot; - using helpers::transpose_conjugate; - + static TwoQubitBasisDecomposer + new_inner(OneQubitGateSequence::Gate basis_gate = {.type = qc::X, + .parameter = {}, + .qubit_id = {0, 1}}, + fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, + std::optional pulse_optimize = std::nullopt) { auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& max_relative) { // Handle same infinities @@ -1365,21 +1301,17 @@ struct GateDecompositionPattern final }; constexpr auto FRAC_1_SQRT_2 = static_cast(0.707106781186547524400844362104849039); - constexpr auto K12R_ARR = std::array{ - qfp(0., FRAC_1_SQRT_2), - qfp(FRAC_1_SQRT_2, 0.), - qfp(-FRAC_1_SQRT_2, 0.), - qfp(0., -FRAC_1_SQRT_2), + const auto K12R_ARR = matrix2x2{ + {qfp(0., FRAC_1_SQRT_2), qfp(FRAC_1_SQRT_2, 0.)}, + {qfp(-FRAC_1_SQRT_2, 0.), qfp(0., -FRAC_1_SQRT_2)}, }; - constexpr auto K12L_ARR = std::array{ - qfp(0.5, 0.5), - qfp(0.5, 0.5), - qfp(-0.5, 0.5), - qfp(0.5, -0.5), + const auto K12L_ARR = matrix2x2{ + {qfp(0.5, 0.5), qfp(0.5, 0.5)}, + {qfp(-0.5, 0.5), qfp(0.5, -0.5)}, }; auto basis_decomposer = TwoQubitWeylDecomposition::new_inner( - gate_matrix, DEFAULT_FIDELITY, std::nullopt); + getTwoQubitMatrix(basis_gate), DEFAULT_FIDELITY, std::nullopt); auto super_controlled = relative_eq(basis_decomposer.a, qc::PI_4, std::numeric_limits::epsilon(), 1e-09) && @@ -1390,82 +1322,76 @@ struct GateDecompositionPattern final // expand as Ui = Ki1.Ubasis.Ki2 auto b = basis_decomposer.b; auto temp = qfp(0.5, -0.5); - auto k11l = std::array{ - temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b)), - temp * (M_IM * std::exp(qfp(0., b))), temp * -(std::exp(qfp(0., b)))}; - auto k11r = std::array{FRAC_1_SQRT_2 * std::exp((IM * qfp(0., -b))), - FRAC_1_SQRT_2 * -std::exp(qfp(0., -b)), - FRAC_1_SQRT_2 * std::exp(qfp(0., b)), - FRAC_1_SQRT_2 * (M_IM * std::exp(qfp(0., b)))}; - auto k32l_k21l = std::array{FRAC_1_SQRT_2 * std::cos(qfp(1., (2. * b))), - FRAC_1_SQRT_2 * (IM * std::sin((2. * b))), - FRAC_1_SQRT_2 * (IM * std::sin(2. * b)), - FRAC_1_SQRT_2 * qfp(1., -std::cos(2. * b))}; + auto k11l = matrix2x2{ + {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, + {temp * (M_IM * std::exp(qfp(0., b))), + temp * -(std::exp(qfp(0., b)))}}; + auto k11r = matrix2x2{{FRAC_1_SQRT_2 * std::exp((IM * qfp(0., -b))), + FRAC_1_SQRT_2 * -std::exp(qfp(0., -b))}, + {FRAC_1_SQRT_2 * std::exp(qfp(0., b)), + FRAC_1_SQRT_2 * (M_IM * std::exp(qfp(0., b)))}}; + auto k32l_k21l = matrix2x2{{FRAC_1_SQRT_2 * std::cos(qfp(1., (2. * b))), + FRAC_1_SQRT_2 * (IM * std::sin((2. * b)))}, + {FRAC_1_SQRT_2 * (IM * std::sin(2. * b)), + FRAC_1_SQRT_2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); - auto k21r = std::array{ - temp * (M_IM * std::exp(qfp(0., -2. * b))), - temp * std::exp(qfp(0., -2. * b)), - temp * (IM * std::exp(qfp(0., 2. * b))), - temp * std::exp(qfp(0., 2. * b)), + auto k21r = matrix2x2{ + {temp * (M_IM * std::exp(qfp(0., -2. * b))), + temp * std::exp(qfp(0., -2. * b))}, + {temp * (IM * std::exp(qfp(0., 2. * b))), + temp * std::exp(qfp(0., 2. * b))}, }; - constexpr auto K22L_ARR = std::array{ - qfp(FRAC_1_SQRT_2, 0.), - qfp(-FRAC_1_SQRT_2, 0.), - qfp(FRAC_1_SQRT_2, 0.), - qfp(FRAC_1_SQRT_2, 0.), + const auto K22L_ARR = matrix2x2{ + {qfp(FRAC_1_SQRT_2, 0.), qfp(-FRAC_1_SQRT_2, 0.)}, + {qfp(FRAC_1_SQRT_2, 0.), qfp(FRAC_1_SQRT_2, 0.)}, }; - constexpr auto K22R_ARR = std::array{C_ZERO, C_ONE, C_M_ONE, C_ZERO}; - auto k31l = std::array{ - FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), - FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), - FRAC_1_SQRT_2 * -std::exp(qfp(0., b)), - FRAC_1_SQRT_2 * std::exp(qfp(0., b)), + const auto K22R_ARR = matrix2x2{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; + auto k31l = matrix2x2{ + {FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), + FRAC_1_SQRT_2 * std::exp(qfp(0., -b))}, + {FRAC_1_SQRT_2 * -std::exp(qfp(0., b)), + FRAC_1_SQRT_2 * std::exp(qfp(0., b))}, }; - auto k31r = std::array{ - IM * std::exp(qfp(0., b)), - C_ZERO, - C_ZERO, - M_IM * std::exp(qfp(0., -b)), + auto k31r = matrix2x2{ + {IM * std::exp(qfp(0., b)), C_ZERO}, + {C_ZERO, M_IM * std::exp(qfp(0., -b))}, }; temp = qfp(0.5, 0.5); - auto k32r = std::array{ - temp * std::exp(qfp(0., b)), - temp * -std::exp(qfp(0., -b)), - temp * (M_IM * std::exp(qfp(0., b))), - temp * (M_IM * std::exp(qfp(0., -b))), + auto k32r = matrix2x2{ + {temp * std::exp(qfp(0., b)), temp * -std::exp(qfp(0., -b))}, + {temp * (M_IM * std::exp(qfp(0., b))), + temp * (M_IM * std::exp(qfp(0., -b)))}, }; - auto k1ld = transpose_conjugate(basis_decomposer.K1l); - auto k1rd = transpose_conjugate(basis_decomposer.K1r); - auto k2ld = transpose_conjugate(basis_decomposer.K2l); - auto k2rd = transpose_conjugate(basis_decomposer.K2r); + auto k1ld = basis_decomposer.K1l.transpose().conjugate(); + auto k1rd = basis_decomposer.K1r.transpose().conjugate(); + auto k2ld = basis_decomposer.K2l.transpose().conjugate(); + auto k2rd = basis_decomposer.K2r.transpose().conjugate(); // Pre-build the fixed parts of the matrices used in 3-part // decomposition - auto u0l = dot(k31l, k1ld); - auto u0r = dot(k31r, k1rd); - auto u1l = dot(dot(k2ld, k32l_k21l), k1ld); - auto u1ra = dot(k2rd, k32r); - auto u1rb = dot(k21r, k1rd); - auto u2la = dot(k2ld, K22L_ARR); - auto u2lb = dot(k11l, k1ld); - auto u2ra = dot(k2rd, K22R_ARR); - auto u2rb = dot(k11r, k1rd); - auto u3l = dot(k2ld, K12L_ARR); - auto u3r = dot(k2rd, K12R_ARR); + auto u0l = k31l * k1ld; + auto u0r = k31r * k1rd; + auto u1l = k2ld * k32l_k21l * k1ld; + auto u1ra = k2rd * k32r; + auto u1rb = k21r * k1rd; + auto u2la = k2ld * K22L_ARR; + auto u2lb = k11l * k1ld; + auto u2ra = k2rd * K22R_ARR; + auto u2rb = k11r * k1rd; + auto u3l = k2ld * K12L_ARR; + auto u3r = k2rd * K12R_ARR; // Pre-build the fixed parts of the matrices used in the 2-part // decomposition - auto q0l = dot(transpose_conjugate(K12L_ARR), k1ld); - auto q0r = dot(dot(transpose_conjugate(K12R_ARR), IPZ), k1rd); - auto q1la = dot(k2ld, transpose_conjugate(k11l)); - auto q1lb = dot(k11l, k1ld); - auto q1ra = dot(dot(k2rd, IPZ), transpose_conjugate(k11r)); - auto q1rb = dot(k11r, k1rd); - auto q2l = dot(k2ld, K12L_ARR); - auto q2r = dot(k2rd, K12R_ARR); + auto q0l = K12L_ARR.transpose().conjugate() * k1ld; + auto q0r = K12R_ARR.transpose().conjugate() * IPZ * k1rd; + auto q1la = k2ld * k11l.transpose().conjugate(); + auto q1lb = k11l * k1ld; + auto q1ra = k2rd * IPZ * k11r.transpose().conjugate(); + auto q1rb = k11r * k1rd; + auto q2l = k2ld * K12L_ARR; + auto q2r = k2rd * K12R_ARR; return TwoQubitBasisDecomposer{ - gate, - gate_qubit_ids, - gate_params, + basis_gate, basis_fidelity, euler_basis, pulse_optimize, @@ -1591,8 +1517,7 @@ struct GateDecompositionPattern final add_euler_decomposition(2 * i, 0); add_euler_decomposition(2 * i + 1, 1); - gates.gates.push_back( - {.type = gate, .parameter = gate_params, .qubit_id = {0, 1}}); + gates.gates.push_back(basis_gate); } add_euler_decomposition(2 * best_nbasis, 0); @@ -1604,52 +1529,47 @@ struct GateDecompositionPattern final private: [[nodiscard]] std::vector decomp0_inner(const TwoQubitWeylDecomposition& target) const { - using helpers::dot; return { - dot(target.K1r, target.K2r), - dot(target.K1l, target.K2l), + target.K1r * target.K2r, + target.K1l * target.K2l, }; } [[nodiscard]] std::vector decomp1_inner(const TwoQubitWeylDecomposition& target) const { - using helpers::dot; - using helpers::transpose_conjugate; // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) return { - dot(transpose_conjugate(basis_decomposer.K2r), target.K2r), - dot(transpose_conjugate(basis_decomposer.K2l), target.K2l), - dot(target.K1r, transpose_conjugate(basis_decomposer.K1r)), - dot(target.K1l, transpose_conjugate(basis_decomposer.K1l)), + basis_decomposer.K2r.transpose().conjugate() * target.K2r, + basis_decomposer.K2l.transpose().conjugate() * target.K2l, + target.K1r * basis_decomposer.K1r.transpose().conjugate(), + target.K1l * basis_decomposer.K1l.transpose().conjugate(), }; } [[nodiscard]] std::vector decomp2_supercontrolled_inner( const TwoQubitWeylDecomposition& target) const { - using helpers::dot; return { - dot(q2r, target.K2r), - dot(q2l, target.K2l), - dot(dot(q1ra, rz_matrix(2. * target.b)), q1rb), - dot(dot(q1la, rz_matrix(-2. * target.a)), q1lb), - dot(target.K1r, q0r), - dot(target.K1l, q0l), + q2r * target.K2r, + q2l * target.K2l, + q1ra * rz_matrix(2. * target.b) * q1rb, + q1la * rz_matrix(-2. * target.a) * q1lb, + target.K1r * q0r, + target.K1l * q0l, }; } [[nodiscard]] std::vector decomp3_supercontrolled_inner( const TwoQubitWeylDecomposition& target) const { - using helpers::dot; return { - dot(u3r, target.K2r), - dot(u3l, target.K2l), - dot(dot(u2ra, rz_matrix(2. * target.b)), u2rb), - dot(dot(u2la, rz_matrix(-2. * target.a)), u2lb), - dot(dot(u1ra, rz_matrix(-2. * target.c)), u1rb), + u3r * target.K2r, + u3l * target.K2l, + u2ra * rz_matrix(2. * target.b) * u2rb, + u2la * rz_matrix(-2. * target.a) * u2lb, + u1ra * rz_matrix(-2. * target.c) * u1rb, u1l, - dot(target.K1r, u0r), - dot(target.K1l, u0l), + target.K1r * u0r, + target.K1l * u0l, }; } @@ -1669,7 +1589,8 @@ struct GateDecompositionPattern final return std::nullopt; } - if (gate != qc::X) { // CX + if (basis_gate.type != qc::X || + basis_gate.qubit_id.size() != 2) { // != CX if (pulse_optimize.has_value()) { throw std::runtime_error{"pulse_optimizer currently only works " "with CNOT entangling gate"}; @@ -1704,7 +1625,6 @@ struct GateDecompositionPattern final TwoQubitGateSequence get_sx_vz_2cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { - using helpers::dot; TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; gates.globalPhase -= 2. * basis_decomposer.global_phase; @@ -1721,16 +1641,15 @@ struct GateDecompositionPattern final auto euler_q0 = get_euler_angles(0, EulerBasis::ZXZ); auto euler_q1 = get_euler_angles(1, EulerBasis::XZX); - auto euler_matrix_q0 = - dot(rx_matrix(euler_q0[0][1]), rz_matrix(euler_q0[0][0])); - euler_matrix_q0 = - dot(rz_matrix(euler_q0[0][2] + euler_q0[1][0] + qc::PI_2), - euler_matrix_q0); + matrix2x2 euler_matrix_q0 = + rx_matrix(euler_q0[0][1]) * rz_matrix(euler_q0[0][0]); + euler_matrix_q0 = rz_matrix(euler_q0[0][2] + euler_q0[1][0] + qc::PI_2) * + euler_matrix_q0; append_1q_sequence(gates, euler_matrix_q0, 0); - auto euler_matrix_q1 = - dot(rz_matrix(euler_q1[0][1]), rx_matrix(euler_q1[0][0])); + matrix2x2 euler_matrix_q1 = + rz_matrix(euler_q1[0][1]) * rx_matrix(euler_q1[0][0]); euler_matrix_q1 = - dot(rx_matrix(euler_q1[0][2] + euler_q1[1][0]), euler_matrix_q1); + rx_matrix(euler_q1[0][2] + euler_q1[1][0]) * euler_matrix_q1; append_1q_sequence(gates, euler_matrix_q1, 1); gates.gates.push_back( {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? @@ -1744,14 +1663,13 @@ struct GateDecompositionPattern final gates.globalPhase += qc::PI_2; gates.gates.push_back( {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? - euler_matrix_q0 = - dot(rx_matrix(euler_q0[2][1]), - rz_matrix(euler_q0[1][2] + euler_q0[2][0] + qc::PI_2)); - euler_matrix_q0 = dot(rz_matrix(euler_q0[2][2]), euler_matrix_q0); + euler_matrix_q0 = rx_matrix(euler_q0[2][1]) * + rz_matrix(euler_q0[1][2] + euler_q0[2][0] + qc::PI_2); + euler_matrix_q0 = rz_matrix(euler_q0[2][2]) * euler_matrix_q0; append_1q_sequence(gates, euler_matrix_q0, 0); - euler_matrix_q1 = dot(rz_matrix(euler_q1[2][1]), - rx_matrix(euler_q1[1][2] + euler_q1[2][0])); - euler_matrix_q1 = dot(rx_matrix(euler_q1[2][2]), euler_matrix_q1); + euler_matrix_q1 = rz_matrix(euler_q1[2][1]) * + rx_matrix(euler_q1[1][2] + euler_q1[2][0]); + euler_matrix_q1 = rx_matrix(euler_q1[2][2]) * euler_matrix_q1; append_1q_sequence(gates, euler_matrix_q1, 1); return gates; } @@ -1770,7 +1688,6 @@ struct GateDecompositionPattern final std::optional get_sx_vz_3cx_efficient_euler( const std::vector& decomposition, const TwoQubitWeylDecomposition& target_decomposed) { - using helpers::dot; TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; gates.globalPhase -= 3. * basis_decomposer.global_phase; gates.globalPhase = remEuclid(gates.globalPhase, qc::TAU); @@ -1802,24 +1719,23 @@ struct GateDecompositionPattern final auto x02_add = x12 - euler_q0[1][0]; auto x12_is_half_pi = std::abs(x12 - qc::PI_2) < atol; - auto euler_matrix_q0 = - dot(rx_matrix(euler_q0[0][1]), rz_matrix(euler_q0[0][0])); + matrix2x2 euler_matrix_q0 = + rx_matrix(euler_q0[0][1]) * rz_matrix(euler_q0[0][0]); if (x12_is_non_zero && x12_is_pi_mult) { - euler_matrix_q0 = - dot(rz_matrix(euler_q0[0][2] - x02_add), euler_matrix_q0); + euler_matrix_q0 = rz_matrix(euler_q0[0][2] - x02_add) * euler_matrix_q0; } else { euler_matrix_q0 = - dot(rz_matrix(euler_q0[0][2] + euler_q0[1][0]), euler_matrix_q0); + rz_matrix(euler_q0[0][2] + euler_q0[1][0]) * euler_matrix_q0; } - euler_matrix_q0 = dot(hGate, euler_matrix_q0); + euler_matrix_q0 = hGate * euler_matrix_q0; append_1q_sequence(gates, euler_matrix_q0, 0); auto rx_0 = rx_matrix(euler_q1[0][0]); auto rz = rz_matrix(euler_q1[0][1]); auto rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]); - auto euler_matrix_q1 = dot(rz, rx_0); - euler_matrix_q1 = dot(rx_1, euler_matrix_q1); - euler_matrix_q1 = dot(hGate, euler_matrix_q1); + matrix2x2 euler_matrix_q1 = rz * rx_0; + euler_matrix_q1 = rx_1 * euler_matrix_q1; + euler_matrix_q1 = hGate * euler_matrix_q1; append_1q_sequence(gates, euler_matrix_q1, 1); gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); @@ -1872,20 +1788,21 @@ struct GateDecompositionPattern final return std::nullopt; } gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); - auto eulerMatrix = dot(rz_matrix(euler_q0[2][2] + euler_q0[3][0]), hGate); - eulerMatrix = dot(rx_matrix(euler_q0[3][1]), eulerMatrix); - eulerMatrix = dot(rz_matrix(euler_q0[3][2]), eulerMatrix); + matrix2x2 eulerMatrix = + rz_matrix(euler_q0[2][2] + euler_q0[3][0]) * hGate; + eulerMatrix = rx_matrix(euler_q0[3][1]) * eulerMatrix; + eulerMatrix = rz_matrix(euler_q0[3][2]) * eulerMatrix; append_1q_sequence(gates, eulerMatrix, 0); - eulerMatrix = dot(rx_matrix(euler_q1[2][2] + euler_q1[3][0]), hGate); - eulerMatrix = dot(rz_matrix(euler_q1[3][1]), eulerMatrix); - eulerMatrix = dot(rx_matrix(euler_q1[3][2]), eulerMatrix); + eulerMatrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]) * hGate; + eulerMatrix = rz_matrix(euler_q1[3][1]) * eulerMatrix; + eulerMatrix = rx_matrix(euler_q1[3][2]) * eulerMatrix; append_1q_sequence(gates, eulerMatrix, 1); auto out_unitary = compute_unitary(gates, gates.globalPhase); // TODO: fix the sign problem to avoid correction here - if (std::abs(target_decomposed.unitary_matrix.at(0 * 4 + 0) - - (-out_unitary.at(0 * 4 + 0))) < atol) { + if (std::abs(target_decomposed.unitary_matrix(0, 0) - + (-out_unitary(0, 0))) < atol) { gates.globalPhase += qc::PI; } return gates; @@ -1893,24 +1810,20 @@ struct GateDecompositionPattern final matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, fp global_phase) { - using helpers::dot; auto phase = std::exp(std::complex{0, global_phase}); - matrix4x4 matrix; - matrix[0 * 4 + 0] = phase; - matrix[1 * 4 + 1] = phase; - matrix[2 * 4 + 2] = phase; - matrix[3 * 4 + 3] = phase; + matrix4x4 matrix{}; + matrix.diagonal().setConstant(phase); for (auto&& gate : sequence.gates) { matrix4x4 gate_matrix = getTwoQubitMatrix(gate); - matrix = dot(gate_matrix, matrix); + matrix = gate_matrix * matrix; } return matrix; } void append_1q_sequence(TwoQubitGateSequence& twoQubitSequence, - matrix2x2 unitary, std::uint8_t qubit) { + matrix2x2 unitary, std::size_t qubit) { std::vector target_1q_basis_list; target_1q_basis_list.push_back(euler_basis); auto sequence = unitary_to_gate_sequence_inner( @@ -2056,6 +1969,14 @@ struct GateDecompositionPattern final }; // namespace mqt::ir::opt }; +const matrix2x2 GateDecompositionPattern::identityGate = matrix2x2::Identity(); +const matrix2x2 GateDecompositionPattern::hGate{{1.0 / sqrt2, 1.0 / sqrt2}, + {1.0 / sqrt2, -1.0 / sqrt2}}; +const matrix2x2 GateDecompositionPattern::IPZ{{IM, C_ZERO}, {C_ZERO, M_IM}}; +const matrix2x2 GateDecompositionPattern::IPY{{C_ZERO, C_ONE}, + {C_M_ONE, C_ZERO}}; +const matrix2x2 GateDecompositionPattern::IPX{{C_ZERO, IM}, {IM, C_ZERO}}; + /** * @brief Populates the given pattern set with patterns for gate * decomposition. diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index b52cf42fe..3a153248e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -17,18 +17,20 @@ #include #include +#include +#include #include #include namespace mqt::ir::opt { using fp = double; using qfp = std::complex; -using diagonal4x4 = std::array; -using rdiagonal4x4 = std::array; -using vector2d = std::vector; -using matrix2x2 = std::array; -using matrix4x4 = std::array; -using rmatrix4x4 = std::array; +using matrix2x2 = Eigen::Matrix2; +using matrix4x4 = Eigen::Matrix4; +using rmatrix4x4 = Eigen::Matrix4; +using diagonal4x4 = Eigen::Vector; +using rdiagonal4x4 = Eigen::Vector; +; constexpr qfp C_ZERO{0., 0.}; constexpr qfp C_ONE{1., 0.}; @@ -40,63 +42,47 @@ constexpr qfp M_IM{0., -1.}; namespace mqt::ir::opt::helpers { -// TODO: remove -template void print(std::array, N> matrix, std::string s = "", bool force = false) { - if (!force) return; - int i{}; - int n = std::sqrt(N); - if (!s.empty()) { - llvm::errs() << "=== " << s << " ===\n"; - } - for (auto&& a : matrix) { - std::cerr << std::setprecision(17) << a.real() << 'i' << a.imag() << ' '; - if (++i % n == 0) { - llvm::errs() << '\n'; - } - } - llvm::errs() << '\n'; +void print(std::size_t x) { std::cerr << x; } +void print(fp x) { std::cerr << x; } + +void print(qfp x) { + std::cerr << std::setprecision(17) << x.real() << 'i' << x.imag(); } -template void print(std::array matrix, std::string s = "", bool force = false) { - if (!force) return; - int i{}; - int n = std::sqrt(N); +// TODO: remove +template +void print(Eigen::Matrix matrix, std::string s = "", + bool force = false) { + if (!force) + return; if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } - for (auto&& a : matrix) { - std::cerr << std::setprecision(17) << a << ' '; - if (++i % n == 0) { - llvm::errs() << '\n'; + for (int i = 0; i < matrix.cols(); ++i) { + for (int j = 0; j < matrix.rows(); ++j) { + print(matrix(j, i)); + std::cerr << ' '; } + llvm::errs() << '\n'; } llvm::errs() << '\n'; } -template void print(std::array matrix, std::string s = "", bool force = false) { - if (!force) return; - int i{}; - int n = std::sqrt(N); +template +void print(T&& matrix, std::string s = "", bool force = false) { + if (!force) + return; if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } + for (auto&& a : matrix) { - std::cerr << a << ' '; - if (++i % n == 0) { - llvm::errs() << '\n'; - } + print(a); + std::cerr << ' '; } llvm::errs() << '\n'; } -inline auto flatten(const dd::TwoQubitGateMatrix& matrix) { - std::array result; - for (std::size_t i = 0; i < result.size(); ++i) { - result[i] = matrix[i / 4][i % 4]; - } - return result; -} - std::optional mlirValueToFp(mlir::Value value); template @@ -179,7 +165,8 @@ inline std::optional mlirValueToFp(mlir::Value value) { return std::nullopt; } -[[nodiscard]] inline llvm::SmallVector getParameters(UnitaryInterface op) { +[[nodiscard]] inline llvm::SmallVector +getParameters(UnitaryInterface op) { llvm::SmallVector parameters; for (auto&& param : op.getParams()) { if (auto value = helpers::mlirValueToFp(param)) { @@ -221,317 +208,24 @@ inline std::optional mlirValueToFp(mlir::Value value) { return isTwoQubitOp; } -template -T kahanSum(const std::array& values) { - auto sum = T{}; - auto c = T{}; // Compensation for lost low-order bits - - for (auto&& value : values) { - auto y = value - c; // Correct for error so far - auto t = sum + y; // Add the value to the running sum - c = (t - sum) - y; // Recompute the error - sum = t; - } - - return sum; -} - -// Modify the matrix multiplication to use Kahan summation -template -std::array matrixMultiplyWithKahan(const std::array& lhs, - const std::array& rhs) { - std::array result; - - const std::size_t n = std::sqrt(N); - for (size_t i = 0; i < n; ++i) { - for (size_t j = 0; j < n; ++j) { - std::array terms; - for (size_t k = 0; k < n; ++k) { - terms[k] = lhs[i * n + k] * rhs[k * n + j]; - } - result[i * n + j] = kahanSum(terms); - } - } - return result; -} - -template -[[nodiscard]] inline Container multiply(fp factor, Container matrix) { - llvm::transform(matrix, std::begin(matrix), - [&](auto&& x) { return factor * x; }); - return matrix; -} - -template -[[nodiscard]] inline Container multiply(qfp factor, Container matrix) { - llvm::transform(matrix, std::begin(matrix), - [&](auto&& x) { return factor * x; }); - return matrix; -} - -template -[[nodiscard]] inline auto multiply(const std::array& lhs, - const std::array& rhs) { - const int n = std::sqrt(N); - Eigen::Matrix lhsEigen; - Eigen::Matrix rhsEigen; - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - lhsEigen(j, i) = lhs[j * n + i]; - rhsEigen(j, i) = rhs[j * n + i]; - } - } - auto r = lhsEigen * rhsEigen; - std::array result{}; - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - result[j * n + i] = r(j, i); - } - } - // std::array result{}; - // const int n = std::sqrt(N); - // for (int i = 0; i < n; i++) { - // for (int j = 0; j < n; j++) { - // for (int k = 0; k < n; k++) { - // result[i * n + j] += lhs[i * n + k] * rhs[k * n + j]; - // } - // } - // } - return result; -} - template -[[nodiscard]] inline auto multiply(const std::vector& lhs, - const std::vector& rhs, int columnsLhs) { - int rowsLhs = lhs.size() / columnsLhs; - int rowsRhs = columnsLhs; - int columnsRhs = rhs.size() / rowsRhs; - assert(rowsLhs * columnsLhs == lhs.size()); - assert(rowsRhs * columnsRhs == rhs.size()); - - std::vector result(rowsLhs * columnsRhs, T{}); - for (int i = 0; i < rowsLhs; i++) { - for (int j = 0; j < columnsRhs; j++) { - for (int k = 0; k < columnsLhs; k++) { - result[i * columnsRhs + j] += - lhs[i * columnsLhs + k] * rhs[k * columnsRhs + j]; - } - } - } +inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, + const Eigen::Matrix2& rhs) { + Eigen::Matrix4 result; + Eigen::KroneckerProduct kroneckerProduct{lhs, rhs}; + kroneckerProduct.evalTo(result); return result; } -template -[[nodiscard]] inline auto LUdecomposition(std::array matrix) { - std::array L{}; - std::array U{}; - int rowPermutations = 0; - - for (int i = 0; i < 4; i++) { - // --- Partial pivoting: find max row in column i --- - int pivotRow = i; - auto maxVal = matrix[i * 4 + i]; - - for (int r = i + 1; r < 4; r++) { - auto val = matrix[r * 4 + i]; - if (std::abs(val) > std::abs(maxVal)) { - maxVal = val; - pivotRow = r; - } - } - - // --- Swap rows in matrix if needed --- - if (pivotRow != i) { - for (int col = 0; col < 4; ++col) { - std::swap(matrix[i * 4 + col], matrix[pivotRow * 4 + col]); - } - ++rowPermutations; - } - - // --- Compute L matrix (column-wise) --- - for (int j = 0; j < 4; j++) { - if (j < i) - L[j * 4 + i] = 0; - else { - L[j * 4 + i] = matrix[j * 4 + i]; - for (int k = 0; k < i; k++) { - L[j * 4 + i] -= L[j * 4 + k] * U[k * 4 + i]; - } - } - } - - // --- Compute U matrix (row-wise) --- - for (int j = 0; j < 4; j++) { - if (j < i) - U[i * 4 + j] = 0; - else if (j == i) - U[i * 4 + j] = 1; // Diagonal of U is set to 1 - else { - U[i * 4 + j] = matrix[i * 4 + j] / L[i * 4 + i]; - for (int k = 0; k < i; k++) { - U[i * 4 + j] -= (L[i * 4 + k] * U[k * 4 + j]) / L[i * 4 + i]; - } - } - } - } - - return std::make_tuple(L, U, rowPermutations); -} - -template -using ValueType = typename std::remove_cvref_t::value_type; - -template -static auto diagonal(Container&& matrix) { - const int n = std::sqrt(matrix.size()); - auto result = [&]() { - using T = std::remove_cvref_t; - if constexpr (std::is_same_v && Offset == 0) { - return diagonal4x4{}; - } else if constexpr (std::is_same_v && Offset == 0) { - return rdiagonal4x4{}; - } else { - return std::vector>(n - std::abs(Offset)); - } - }(); - for (std::size_t i = 0; i < result.size(); ++i) { - auto x = Offset > 0 ? i + Offset : i; - auto y = Offset < 0 ? i - Offset : i; - result[i] = matrix[y * n + x]; - } - return result; -} - -template -auto submatrix(Container&& matrix, int rowStart, int columnStart, int numRows, - int numColumns) { - const int n = std::sqrt(matrix.size()); - assert((rowStart + numRows) <= n); - assert((columnStart + numColumns) <= n); - - std::vector> result(numRows * numColumns); - for (int i = 0; i < numColumns; ++i) { - for (int j = 0; j < numRows; ++j) { - result[j * numColumns + i] = matrix[(rowStart + j) * n + (columnStart + i)]; - } - } - return result; -} - -template -auto assignSubmatrix(Lhs&& lhs, Rhs&& rhs, int rowStart, int columnStart, - int numRows, int numColumns) { - const int n = std::sqrt(lhs.size()); - assert((rowStart + numRows) <= n); - assert((columnStart + numColumns) <= n); - assert(numColumns * numRows == rhs.size()); - - for (int i = 0; i < numColumns; ++i) { - for (int j = 0; j < numRows; ++j) { - lhs[(rowStart + j) * n + (columnStart + i)] = rhs[j * numColumns + i]; - } - } -} - -template inline auto dot(Args&&... args) { - return helpers::multiply(std::forward(args)...); -} - -template -inline auto vectorsDot(Lhs&& lhs, Rhs&& rhs) { - return std::inner_product(lhs.begin(), lhs.end(), rhs.begin(), - typename Lhs::value_type{}); -} - -template auto add(Lhs lhs, Rhs&& rhs) { - assert(lhs.size() == rhs.size()); - for (int i = 0; i < lhs.size(); ++i) { - lhs[i] += rhs[i]; - } - return lhs; -} - -inline matrix2x2 transpose(const matrix2x2& matrix) { - return {matrix[0 * 2 + 0], matrix[1 * 2 + 0], matrix[0 * 2 + 1], - matrix[1 * 2 + 1]}; -} - -template auto transpose(Container&& matrix) { - const std::size_t n = std::sqrt(matrix.size()); - auto result{matrix}; - for (size_t i = 0; i < n; ++i) { - for (size_t j = 0; j < n; ++j) { - result[j * n + i] = matrix[i * n + j]; - } - } - return result; -} - -template auto conj(T&& x) { - using U = std::remove_cvref_t; - if constexpr (std::is_same_v) { - return std::conj(x); - } else if constexpr (std::is_same_v) { - return x; - } else { - static_assert(!sizeof(U), "Unimplemented case for helpers::conj"); - } -} - -template auto conjugate(Container matrix) { - llvm::transform(matrix, matrix.begin(), [](auto&& x) { return conj(x); }); - return matrix; -} - -template -inline auto transpose_conjugate(Container&& matrix) { - auto result = transpose(matrix); - return conjugate(result); -} - -template -inline T determinant(const std::array& mat) { - const int n = std::sqrt(N); - Eigen::Matrix matEigen; - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - matEigen(j, i) = mat[j * n + i]; - } - } - return matEigen.determinant(); -} - -template -inline std::array from(const std::array& first_quadrant, - const std::array& second_quadrant, - const std::array& third_quadrant, - const std::array& fourth_quadrant) { - return { - first_quadrant[0 * 2 + 0], first_quadrant[0 * 2 + 1], - second_quadrant[0 * 2 + 0], second_quadrant[0 * 2 + 1], - first_quadrant[1 * 2 + 0], first_quadrant[1 * 2 + 1], - second_quadrant[1 * 2 + 0], second_quadrant[1 * 2 + 1], - third_quadrant[0 * 2 + 0], third_quadrant[0 * 2 + 1], - fourth_quadrant[0 * 2 + 0], fourth_quadrant[0 * 2 + 1], - third_quadrant[1 * 2 + 0], third_quadrant[1 * 2 + 1], - fourth_quadrant[1 * 2 + 0], fourth_quadrant[1 * 2 + 1], - }; -} - -template inline auto toArray4(const std::vector& vec) { - std::array result; - assert(vec.size() == result.size()); - for (std::size_t i = 0; i < vec.size(); ++i) { - result[i] = vec[i]; - } - return result; -} - -template -inline std::array kroneckerProduct(const std::array& lhs, - const std::array& rhs) { - return from(multiply(lhs[0 * 2 + 0], rhs), multiply(lhs[0 * 2 + 1], rhs), - multiply(lhs[1 * 2 + 0], rhs), multiply(lhs[1 * 2 + 1], rhs)); +auto self_adjoint_evd(rmatrix4x4 A) { + Eigen::SelfAdjointEigenSolver s; + std::cerr << "=EigIN==\n" << A << "\n========\n" << std::endl; + s.compute(A); // TODO: computeDirect is faster + auto vecs = s.eigenvectors().eval(); + auto vals = s.eigenvalues(); + std::cerr << "=Eigen==\n" << vecs << "\n========\n" << std::endl; + std::cerr << "=Eigen==\n" << vals << "\n========\n" << std::endl; + return std::make_pair(vecs, vals); } } // namespace mqt::ir::opt::helpers diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/a.h b/mlir/lib/Dialect/MQTOpt/Transforms/a.h deleted file mode 100644 index 57e4678a3..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/a.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "Helpers.h" - -namespace mqt::ir::opt { - -// Function to compute the norm (Frobenius norm) of a matrix -double norm(const std::array& A) { - double sum = 0.0; - for (size_t i = 0; i < 16; ++i) { - sum += A[i] * A[i]; - } - return sqrt(sum); -} - -// Function to perform a Jacobi rotation -void jacobi_rotate(std::array& A, std::array& V, int p, int q) { - // Compute the Jacobi rotation matrix - double theta = 0.5 * atan2(2 * A[p * 4 + q], A[q * 4 + q] - A[p * 4 + p]); - double c = cos(theta); - double s = sin(theta); - - // Apply the rotation to matrix A - for (int i = 0; i < 4; ++i) { - double ap = A[i * 4 + p]; - double aq = A[i * 4 + q]; - A[i * 4 + p] = c * ap - s * aq; - A[i * 4 + q] = s * ap + c * aq; - } - - // Apply the rotation to matrix V (eigenvectors) - for (int i = 0; i < 4; ++i) { - double vi_p = V[i * 4 + p]; - double vi_q = V[i * 4 + q]; - V[i * 4 + p] = c * vi_p - s * vi_q; - V[i * 4 + q] = s * vi_p + c * vi_q; - } -} - -// Function to perform the Jacobi method for eigenvalue decomposition -void jacobi_eigen_decomposition(std::array& A, std::array& V, std::array& eigenvalues, double tolerance = 1e-9, int max_iterations = 1000) { - // Initialize the eigenvector matrix V to identity - V.fill(0.0); - for (int i = 0; i < 4; ++i) { - V[i * 4 + i] = 1.0; - } - - int iterations = 0; - double off_diagonal_norm = norm(A); - - // Jacobi rotation iterations - while (off_diagonal_norm > tolerance && iterations < max_iterations) { - // Find the largest off-diagonal element - double max_off_diag = 0.0; - int p = 0, q = 0; - for (int i = 0; i < 3; ++i) { - for (int j = i + 1; j < 4; ++j) { - double abs_val = fabs(A[i * 4 + j]); - if (abs_val > max_off_diag) { - max_off_diag = abs_val; - p = i; - q = j; - } - } - } - - // Perform a Jacobi rotation if necessary - if (max_off_diag > tolerance) { - jacobi_rotate(A, V, p, q); - } - - // Update the off-diagonal norm - off_diagonal_norm = norm(A); - ++iterations; - } - - // Extract the eigenvalues from the diagonal of A - for (int i = 0; i < 4; ++i) { - eigenvalues[i] = A[i * 4 + i]; - } -} -} diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/b.h b/mlir/lib/Dialect/MQTOpt/Transforms/b.h deleted file mode 100644 index 3d610ff96..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/b.h +++ /dev/null @@ -1,469 +0,0 @@ -#pragma once - -#include "Helpers.h" - -namespace mqt::ir::opt { -void tridiagonalization_inplace(rmatrix4x4& mat, rdiagonal4x4& hCoeffs) { - auto n = 4; - - auto makeHouseholder = [](llvm::SmallVector& essential, fp& tau, - fp& beta) { - std::vector tail{essential.begin(), essential.end()}; - - auto squaredNorm = [](auto&& v) { - qfp sum{}; - for (auto&& x : v) { - sum += qfp(std::real(x) * std::real(x), std::imag(x) * std::imag(x)); - } - return sum.real() + sum.imag(); - }; - - auto tailSqNorm = essential.size() == 1 ? 0.0 : squaredNorm(tail); - fp c0 = essential[0]; - const fp tol = (std::numeric_limits::min)(); - - if (tailSqNorm <= tol && std::norm(std::imag(c0)) <= tol) { - tau = 0; - beta = std::real(c0); - llvm::fill(essential, 0); - } else { - beta = std::sqrt(std::norm(c0) + tailSqNorm); - if (std::real(c0) >= 0.0) { - beta = -beta; - } - for (std::size_t i = 0; i < essential.size(); ++i) { - essential[i] = tail[i] / (c0 - beta); - } - tau = helpers::conj((beta - c0) / beta); - } - }; - - auto lowerSelfadjointView = [](auto matrix) { - const int n = std::sqrt(matrix.size()); - for (int i = 0; i < n; ++i) { - for (int j = 0; j <= i; ++j) { - matrix[j * n + i] = matrix[i * n + j]; - } - } - return matrix; - }; - - auto bottomRightCorner = [](auto&& matrix, int rows, int columns) { - const int n = std::sqrt(matrix.size()); - return helpers::submatrix(matrix, n - rows, n - columns, rows, columns); - }; - - auto getColumn = [](const rmatrix4x4& matrix, int column) { - rdiagonal4x4 result; - for (int j = 0; j < 4; ++j) { - result[j] = matrix[j * 4 + column]; - } - return result; - }; - - auto getTail = [](auto&& array, int size) { - std::vector result(size); - for (int i = 0; i < size; ++i) { - result[i] = array[array.size() - size + i]; - } - return result; - }; - - auto rankUpdate = [](auto matrix, auto&& u, auto&& v, auto&& alpha) { - rmatrix4x4 add1{}; - for (int i = 0; i < u.size(); ++i) { - for (int j = 0; j < v.size(); ++j) { - add1[j * 4 + i] += alpha * u[i] * helpers::conj(v[j]); - } - } - rmatrix4x4 add2{}; - for (int i = 0; i < v.size(); ++i) { - for (int j = 0; j < u.size(); ++j) { - add2[j * 4 + i] = helpers::conj(alpha) * v[i] * helpers::conj(u[j]); - } - } - for (int i = 0; i < matrix.size(); ++i) { - matrix[i] += add1[i] + add2[i]; - } - return matrix; - }; - - for (auto i = 0; i < n - 1; ++i) { - auto remainingSize = n - i - 1; - fp beta; - fp h; - - // matA.col(i).tail(remainingSize).makeHouseholderInPlace(h, beta); - llvm::SmallVector tmp; - for (int j = n - remainingSize; j < n; ++j) { - tmp.push_back(mat[j * n + i]); - } - makeHouseholder(tmp, h, beta); - - // Apply similarity transformation to remaining columns, - // i.e., A = H A H' where H = I - h v v' and v = matA.col(i).tail(n-i-1) - // matA.col(i).coeffRef(i + 1) = fp(1); - mat[(i + 1) * n + i] = 1.0; - - // hCoeffs.tail(n - i - 1).noalias() = - // (matA.bottomRightCorner(remainingSize, remainingSize).template - // selfadjointView() * - // (conj(h) * matA.col(i).tail(remainingSize))); - auto tmp2 = helpers::multiply( - lowerSelfadjointView( - bottomRightCorner(mat, remainingSize, remainingSize)), - helpers::multiply(helpers::conj(h), - getTail(getColumn(mat, i), remainingSize)), - remainingSize); - for (int a = 0; a < remainingSize; ++a) { - hCoeffs[i + 1 + a] = tmp2[a]; - } - - // hCoeffs.tail(n - i - 1) += - // (conj(h) * RealScalar(-0.5) * - // (hCoeffs.tail(remainingSize).dot(matA.col(i).tail(remainingSize)))) - // * matA.col(i).tail(n - i - 1); - auto tmpFactor = - helpers::conj(h) * static_cast(-0.5) * - helpers::vectorsDot(getTail(hCoeffs, remainingSize), - getTail(getColumn(mat, i), remainingSize)); - tmp2 = helpers::multiply(tmpFactor, - helpers::submatrix(mat, i + 1, i, n - i - 1, 1)); - for (int a = 0; a < remainingSize; ++a) { - hCoeffs[i + 1 + a] += tmp2[a]; - } - - // matA.bottomRightCorner(remainingSize, remainingSize) - // .template selfadjointView() - // .rankUpdate(matA.col(i).tail(remainingSize), - // hCoeffs.tail(remainingSize), Scalar(-1)); - auto updatedMatrix = bottomRightCorner(mat, remainingSize, remainingSize); - updatedMatrix = lowerSelfadjointView(updatedMatrix); - updatedMatrix = rankUpdate( - updatedMatrix, helpers::submatrix(mat, n - remainingSize, i, remainingSize, 1), - std::vector{hCoeffs.begin() + (n - remainingSize), hCoeffs.end()}, - -1.0); - // update bottom right corner - helpers::assignSubmatrix(mat, updatedMatrix, n - remainingSize, - n - remainingSize, remainingSize, remainingSize); - - // matA.col(i).coeffRef(i + 1) = beta; - mat[(i + 1) * n + i] = beta; - // hCoeffs.coeffRef(i) = h; - hCoeffs[i] = h; - } -} - -void tridiagonal_qr_step(rdiagonal4x4& diag, std::vector& subdiag, - int start, int end, rmatrix4x4& matrixQ) { - // Wilkinson Shift. - auto td = (diag[end - 1] - diag[end]) * static_cast(0.5); - auto e = subdiag[end - 1]; - // Note that thanks to scaling, e^2 or td^2 cannot overflow, however they can - // still underflow thus leading to inf/NaN values when using the following - // commented code: - // RealScalar e2 = numext::abs2(subdiag[end-1]); - // RealScalar mu = diag[end] - e2 / (td + (td>0 ? 1 : -1) * sqrt(td*td + - // e2)); - // This explain the following, somewhat more complicated, version: - auto mu = diag[end]; - if (td == 0.0) { - mu -= std::abs(e); - } else if (e != 0.0) { - const auto e2 = std::norm(e); - const auto h = std::hypot(td, e); - if (e2 == 0.0) { - mu -= e / ((td + (td > static_cast(0) ? h : -h)) / e); - } else { - mu -= e2 / (td + (td > static_cast(0) ? h : -h)); - } - } - - auto x = diag[start] - mu; - auto z = subdiag[start]; - // If z ever becomes zero, the Givens rotation will be the identity and - // z will stay zero for all future iterations. - for (int k = start; k < end && z != 0.0; ++k) { - struct JacobiRotation { - fp c; - fp s; - - void makeGivens(fp p, fp q) { - if (q == 0.0) { - c = p < 0 ? -1 : 1; - s = 0; - } else if (p == 0.0) { - c = 0; - s = q < 0 ? 1 : -1; - } else if (std::abs(p) > std::abs(q)) { - auto t = q / p; - auto u = std::sqrt(1.0 + std::norm(t)); - if (p < 0.0) - u = -u; - c = 1.0 / u; - s = -t * c; - } else { - auto t = p / q; - auto u = std::sqrt(1.0 + std::norm(t)); - if (q < 0) - u = -u; - s = -1.0 / u; - c = -t * s; - } - } - - void applyOnTheRight(rmatrix4x4& matrix, int p, int q) { - const int n = std::sqrt(matrix.size()); - auto x = helpers::submatrix(matrix, 0, p, n, 1); - auto y = helpers::submatrix(matrix, 0, q, n, 1); - auto j = *this; - j.transpose(); - - if (j.c == 0.0 && j.s == 0.0) { - return; - } - - for (int i = 0; i < n; ++i) { - auto xi = x[i]; - auto yi = y[i]; - x[i] = j.c * xi + helpers::conj(j.s) * yi; - y[i] = -j.s * xi + helpers::conj(j.c) * yi; - } - - helpers::assignSubmatrix(matrix, x, 0, p, n, 1); - helpers::assignSubmatrix(matrix, y, 0, q, n, 1); - } - - void transpose() { s = -helpers::conj(s); } - } rot; - rot.makeGivens(x, z); - - // do T = G' T G - auto sdk = rot.s * diag[k] + rot.c * subdiag[k]; - auto dkp1 = rot.s * subdiag[k] + rot.c * diag[k + 1]; - - diag[k] = rot.c * (rot.c * diag[k] - rot.s * subdiag[k]) - - rot.s * (rot.c * subdiag[k] - rot.s * diag[k + 1]); - diag[k + 1] = rot.s * sdk + rot.c * dkp1; - subdiag[k] = rot.c * sdk - rot.s * dkp1; - - if (k > start) - subdiag[k - 1] = rot.c * subdiag[k - 1] - rot.s * z; - - // "Chasing the bulge" to return to triangular form. - x = subdiag[k]; - if (k < end - 1) { - z = -rot.s * subdiag[k + 1]; - subdiag[k + 1] = rot.c * subdiag[k + 1]; - } - - // apply the givens rotation to the unit matrix Q = Q * G - rot.applyOnTheRight(matrixQ, k, k + 1); - } -} - -void computeFromTridiagonal_impl(rdiagonal4x4& diag, std::vector& subdiag, - const int maxIterations, - bool computeEigenvectors, rmatrix4x4& eivec) { - auto n = diag.size(); - auto end = n - 1; - int start = 0; - int iter = 0; // total number of iterations - - constexpr auto considerAsZero = (std::numeric_limits::min)(); - const auto precision_inv = - static_cast(1.0) / std::numeric_limits::epsilon(); - while (end > 0) { - for (int i = start; i < end; ++i) { - if (std::abs(subdiag[i]) < considerAsZero) { - subdiag[i] = static_cast(0); - } else { - // abs(subdiag[i]) <= epsilon * sqrt(abs(diag[i]) + abs(diag[i+1])) - // Scaled to prevent underflows. - const auto scaled_subdiag = precision_inv * subdiag[i]; - if (scaled_subdiag * scaled_subdiag <= - (std::abs(diag[i]) + std::abs(diag[i + 1]))) { - subdiag[i] = static_cast(0); - } - } - } - - // find the largest unreduced block at the end of the matrix. - while (end > 0 && subdiag[end - 1] == 0.0) { - end--; - } - if (end <= 0) { - break; - } - - // if we spent too many iterations, we give up - iter++; - if (iter > maxIterations * n) { - break; - } - - start = end - 1; - while (start > 0 && subdiag[start - 1] != 0.0) { - start--; - } - - tridiagonal_qr_step(diag, subdiag, start, end, eivec); - } - // Sort eigenvalues and corresponding vectors. - // TODO make the sort optional ? - // TODO use a better sort algorithm !! - if (iter > maxIterations * n) { - throw std::runtime_error{"No convergence for eigenvalue decomposition!"}; - } - for (int i = 0; i < n - 1; ++i) { - // diag.segment(i, n - i).minCoeff(&k); - int k = std::distance(diag.begin() + i, - std::min_element(diag.begin() + i, diag.end())); - if (k > 0 && k < n - i) { - std::swap(diag[i], diag[k + i]); - if (computeEigenvectors) { - for (int j = 0; j < n; ++j) { - std::swap(eivec[j * n + i], eivec[j * n + (k + i)]); - } - } - } - } -} - -void householderSequenceEval(rmatrix4x4& m_vectors, - const rdiagonal4x4& m_coeffs, int length, - int shift) { - auto essentialVector = [&](const rmatrix4x4& vectors, int k) { - int start = k + 1 + shift; - return helpers::submatrix(vectors, start, k, 4 - start, 1); - }; - - auto applyThisOnTheLeft = [&](auto&& vectors, auto& dst) { - const auto n = std::sqrt(dst.size()); - for (int k = 0; k < length; ++k) { - int actual_k = n - k - 1; - int dstRows = n - shift - actual_k; - - // TODO - // auto sub_dst = dst.bottomRows(dstRows); - // sub_dst.applyHouseholderOnTheLeft(essentialVector(vectors, actual_k), - // m_coeffs[actual_k]); - } - }; - - auto applyHouseholderOnTheLeft = [](std::vector& matrix, - const std::vector& essential, - const fp& tau) { - const int n = std::sqrt(matrix.size()); - if (tau != 0.0) { - auto firstRow = helpers::submatrix(matrix, 0, 0, 1, n); - auto bottom = helpers::submatrix(matrix, 1, 0, n - 1, n); - - auto tmp = helpers::multiply(helpers::transpose_conjugate(essential), - bottom, essential.size()); - tmp = helpers::add(tmp, firstRow); - - auto tmp2 = helpers::add( - firstRow, helpers::multiply(-1.0, helpers::multiply(tau, tmp))); - // insert first row (first 4 elements) - llvm::copy(tmp2, matrix.begin()); - - tmp2 = helpers::add( - bottom, helpers::multiply(-tau, helpers::multiply(essential, tmp, - 1))); - // insert all rows except first row - llvm::copy(tmp2, matrix.begin() + n); - } - }; - - auto applyHouseholderOnTheRight = [](std::vector& matrix, - const std::vector& essential, - const fp& tau) { - const int n = std::sqrt(matrix.size()); - if (tau != 0.0) { - auto firstColumn = helpers::submatrix(matrix, 0, 0, n, 1); - auto right = helpers::submatrix(matrix, 0, 1, n, n - 1); - - auto tmp = helpers::multiply(right, essential, n - 1); - tmp = helpers::add(tmp, firstColumn); - - auto tmp2 = helpers::add(firstColumn, helpers::multiply(-tau, tmp)); - auto tmp3 = helpers::add( - right, - helpers::multiply( - -tau, helpers::multiply( - tmp, helpers::transpose_conjugate(essential), 1))); - for (int i = 0; i < n; ++i) { - for (int j = 0; j < n; ++j) { - if (i == 0) { - // insert first column (first 4 elements) - matrix[j * n + i] = tmp2[j]; - } else { - // insert all except first column - matrix[j * n + i] = tmp3[j * (n - 1) + i]; - } - } - } - } - }; - - rmatrix4x4 dst; - const int n = std::sqrt(m_vectors.size()); - const int vecs = length; - dst = helpers::kroneckerProduct(std::array{1.0, 0.0, 0.0, 1.0}, - {1.0, 0.0, 0.0, 1.0}); - for (int k = vecs - 1; k >= 0; --k) { - int cornerSize = n - k - shift; - auto bottomRightCorner = helpers::submatrix( - dst, n - cornerSize, n - cornerSize, cornerSize, cornerSize); - applyHouseholderOnTheLeft(bottomRightCorner, essentialVector(m_vectors, k), - m_coeffs[k]); - // update bottom right corner - helpers::assignSubmatrix(dst, bottomRightCorner, n - cornerSize, - n - cornerSize, cornerSize, cornerSize); - } -} - -// https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen -inline std::pair self_adjoint_evd(rmatrix4x4 matrix) { - - auto lowerTriangularView = [](auto matrix) { - const int n = std::sqrt(matrix.size()); - for (int i = 0; i < n; ++i) { - for (int j = 0; j <= i; ++j) { - matrix[j * n + i] = 0.0; - } - } - return matrix; - }; - - rdiagonal4x4 eigenvalues{1}; - rmatrix4x4 eigenvectors; - int n = 4; - - // map the matrix coefficients to [-1:1] to avoid over- and underflow. - eigenvectors = lowerTriangularView(matrix); - fp scale = *llvm::max_element(eigenvalues, [](auto&& a, auto&& b) { - return std::abs(a) < std::abs(b); - }); - if (scale == 0.0) - scale = static_cast(1.0); - for (auto& eigenvector : eigenvectors) { - eigenvector /= scale; - } - rdiagonal4x4 hCoeffs; - tridiagonalization_inplace(eigenvectors, hCoeffs); - eigenvalues = helpers::diagonal(eigenvectors); - auto subdiag = helpers::diagonal<-1>(eigenvectors); - householderSequenceEval(eigenvectors, helpers::conjugate(hCoeffs), n - 1, 1); - - computeFromTridiagonal_impl(eigenvalues, subdiag, 1000, true, eigenvectors); - - // scale back the eigen values - for (auto& eigenvalue : eigenvalues) { - eigenvalue *= scale; - } - - return {eigenvectors, eigenvalues}; -} -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/c.h b/mlir/lib/Dialect/MQTOpt/Transforms/c.h deleted file mode 100644 index 315ed0a69..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/c.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include "Helpers.h" - -namespace mqt::ir::opt { -// return Q, R such that A = Q * R -static void qrDecomposition(const rmatrix4x4& A, rmatrix4x4& Q, rmatrix4x4& R) { - // array of factor Q1, Q2, ... Qm - std::vector qv(4); - - // temp array - auto z(A); - rmatrix4x4 z1; - - auto vmadd = [](const auto& a, const auto& b, double s, auto& c) { - for (int i = 0; i < 4; i++) - c[i] = a[i] + s * b[i]; - }; - - auto compute_householder_factor = [](rmatrix4x4& mat, const rdiagonal4x4& v) { - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - mat[i + 4 * j] = -2.0 * v[i] * v[j]; - for (int i = 0; i < 4; i++) - mat[i * 4 + i] += 1; - }; - - // take c-th column of a matrix, put results in Vector v - auto extract_column = [](const rmatrix4x4& m, rdiagonal4x4& v, int c) { - for (int i = 0; i < 4; i++) - v[i] = m[i + 4 * c]; - }; - - auto compute_minor = [](rmatrix4x4& lhs, const rmatrix4x4& rhs, int d) { - for (int i = 0; i < d; i++) - lhs[i * 4 + i] = 1.0; - for (int i = d; i < 4; i++) - for (int j = d; j < 4; j++) - lhs[i + 4 * j] = rhs[i + 4 * j]; - }; - - auto norm = [](auto&& m) { - double sum = 0; - for (int i = 0; i < m.size(); i++) - sum += m[i] * m[i]; - return sqrt(sum); - }; - - auto rescale_unit = [&](auto& m) { - auto factor = norm(m); - for (int i = 0; i < m.size(); i++) - m[i] /= factor; - }; - - for (int k = 0; k < 4 && k < 4 - 1; k++) { - - rdiagonal4x4 e{}, x{}; - double a{}; - - // compute minor - compute_minor(z1, z, k); - - // extract k-th column into x - extract_column(z1, x, k); - - a = norm(x); - if (A[k * 4 + k] > 0) - a = -a; - - for (int i = 0; i < 4; i++) - e[i] = (i == k) ? 1 : 0; - - // e = x + a*e - vmadd(x, e, a, e); - - // e = e / ||e|| - rescale_unit(e); - - // qv[k] = I - 2 *e*e^T - compute_householder_factor(qv[k], e); - - // z = qv[k] * z1 - z = helpers::multiply(qv[k], z1); - } - - Q = qv[0]; - - // after this loop, we will obtain Q (up to a transpose operation) - for (int i = 1; i < 4 && i < 4 - 1; i++) { - - z1 = helpers::multiply(qv[i], Q); - Q = z1; - } - - R = helpers::multiply(Q, A); - Q = helpers::transpose(Q); -} - -// Function to perform self-adjoint eigenvalue decomposition (4x4 matrix) -static rmatrix4x4 // eigenvectors (4x4) -self_adjoint_evd(rmatrix4x4 A, // input symmetric matrix (4x4) - rdiagonal4x4& s // eigenvalues -) { - rmatrix4x4 U = {1, 0, 0, 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 1}; // Start with identity - - auto isConverged = [](const rmatrix4x4& A, double tol = 1e-10) -> bool { - double sum = 0.0; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - if (i != j) - sum += A[i + 4 * j] * A[i + 4 * j]; - } - } - return std::sqrt(sum) < tol; - }; - - rmatrix4x4 Q{}; - rmatrix4x4 R{}; - - constexpr auto maxIters = 100; - for (int iter = 0; iter < maxIters; ++iter) { - qrDecomposition(A, Q, R); - - // A = R * Q - A = helpers::multiply(R, Q); - - // eigenVectors = eigenVectors * Q - U = helpers::multiply(U, Q); - - if (isConverged(A)) { - break; - } - } - - for (int i = 0; i < 4; ++i) { - s[i] = A[i * 4 + i]; - } - return U; -} - -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/d.h b/mlir/lib/Dialect/MQTOpt/Transforms/d.h deleted file mode 100644 index 6d2a849f5..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/d.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "Helpers.h" - -#include - -namespace mqt::ir::opt { - -auto self_adjoint_evd(rmatrix4x4 A) { - arma::Mat a(4, 4, arma::fill::scalar_holder(0.0)); - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - a.at(j, i) = A[j * 4 + i]; - } - } - arma::Mat vecs; - arma::Col vals; - arma::eig_sym(vals, vecs, a, "std"); - rmatrix4x4 rvecs; - rdiagonal4x4 rvals; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - rvecs[j * 4 + i] = vecs.at(j, i); - } - rvals[i] = vals.at(i); - } - std::cerr << "========\n" << vecs << "========\n" << std::endl; - std::cerr << "========\n" << vals << "========\n" << std::endl; - return std::make_pair(rvecs, rvals); -} -} // namespace mqt::ir::opt diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/e.h b/mlir/lib/Dialect/MQTOpt/Transforms/e.h deleted file mode 100644 index d407422a0..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/e.h +++ /dev/null @@ -1,976 +0,0 @@ -// -// Analytical Eigen-decomposition for 4x4 and 3x3 matrices -// -// -// Robin Straebler - George Terzakis -// -// University of Portsmouth 2016 -// - -#ifndef __EIGENDECOMPOSE_H__ -#define __EIGENDECOMPOSE_H__ - -#include "Helpers.h" - -#include -#include -#include -#include -#include -#include - -// The 4x4 implies that the namespace contains algorithms -// for the eigen decomposition of 3x3 or 4x4 matrices. -// -namespace mqt::ir::opt { - -namespace PolySolvers { - -// see http://mathworld.wolfram.com/QuadraticFormula.html -template inline std::vector

SolveQuadratic(P a, P b, P c) { - P delta = b * b - 4 * a * c; - P inv_2a, sqrt_delta; - P x1, x0; - - if (delta < 0) - return std::vector

(); - if (a == 0) { - // solve first order system - x1 = 0; - if (b != 0) { - x0 = -c / b; - return std::vector

({x0}); - } - - x0 = 0; - return std::vector

(); - } - - inv_2a = 0.5 / a; - if (delta == 0) { - x0 = -b * inv_2a; - x1 = x0; - return std::vector

({x0}); - } - - sqrt_delta = sqrt(delta); - x0 = (-b + sqrt_delta) * inv_2a; - x1 = (-b - sqrt_delta) * inv_2a; - return std::vector

({x0, x1}); -} - -/* see http://mathworld.wolfram.com/CubicEquation.html */ -template inline std::vector

SolveCubic(P a, P b, P c, P d) { - P inv_a, b_a, b_a2, c_a, d_a; - P Q, R, Q3, D, b_a_3; - P AD, BD; - - P x0, x1, x2; - - if (a == 0) { - /* solve second order system */ - if (b == 0) { - /* solve first order system */ - if (c == 0) - return std::vector

(); - - x0 = -d / c; - return std::vector

({x0}); - } - - x2 = 0; - return SolveQuadratic

(b, c, d); - } - - /* calculate the normalized form x^3 + a2 * x^2 + a1 * x + a0 = 0 */ - inv_a = 1.0 / a; - b_a = inv_a * b; - b_a2 = b_a * b_a; - c_a = inv_a * c; - d_a = inv_a * d; - - /* solve the cubic equation */ - Q = (3 * c_a - b_a2) / 9; - R = (9 * b_a * c_a - 27 * d_a - 2 * b_a * b_a2) / 54; - Q3 = Q * Q * Q; - D = Q3 + R * R; - b_a_3 = (1.0 / 3.0) * b_a; - - if (Q == 0) { - if (R == 0) { - x0 = x1 = x2 = -b_a_3; - return std::vector

({x0, x1, x2}); - } else { - x0 = pow(2 * R, 1 / 3.0) - b_a_3; - return std::vector

({x0}); - } - } - - if (D <= 0) { - /* three real roots */ - P theta = acos(R / sqrt(-Q3)); - P sqrt_Q = sqrt(-Q); - x0 = 2 * sqrt_Q * cos(theta / 3.0) - b_a_3; - x1 = 2 * sqrt_Q * cos((theta + 2 * M_PI) / 3.0) - b_a_3; - x2 = 2 * sqrt_Q * cos((theta + 4 * M_PI) / 3.0) - b_a_3; - - return std::vector

({x0, x1, x2}); - } - - /* D > 0, only one real root */ - AD = pow(fabs(R) + sqrt(D), 1.0 / 3.0) * (R > 0 ? 1 : (R < 0 ? -1 : 0)); - BD = (AD == 0) ? 0 : -Q / AD; - - /* calculate the sole real root */ - x0 = AD + BD - b_a_3; - - return std::vector

({x0}); -} - -/* see http://mathworld.wolfram.com/QuarticEquation.html */ -template -inline std::vector

SolveQuartic(P a, P b, P c, P d, P e) { - P inv_a, b2, bc, b3, b_4; - P r0; - int nb_real_roots; - P R2, R_2, R, inv_R; - P D2, E2; - - P x0 = 0, x1 = 0, x2 = 0, x3 = 0; - - if (a == 0) { - x3 = 0; - return SolveCubic

(b, c, d, e); - } - - /* normalize coefficients */ - inv_a = 1.0 / a; - b *= inv_a; - c *= inv_a; - d *= inv_a; - e *= inv_a; - b2 = b * b; - bc = b * c; - b3 = b2 * b; - - /* solve resultant cubic */ - auto solution3 = - SolveCubic

(1, -c, d * b - 4 * e, 4 * c * e - d * d - b2 * e); - int n = (solution3.size() == 0) ? 0 : solution3.size(); - - if (n == 0) - return solution3; - - r0 = solution3[0]; - /* calculate R^2 */ - R2 = 0.25 * b2 - c + r0; - if (R2 < 0) - return std::vector

(); - - R = sqrt(R2); - inv_R = 1.0 / R; - - nb_real_roots = 0; - - /* calculate D^2 and E^2 */ - if (R < 10E-12) { - P temp = r0 * r0 - 4 * e; - if (temp < 0) { - D2 = E2 = -1; - } else { - P sqrt_temp = sqrt(temp); - D2 = 0.75 * b2 - 2 * c + 2 * sqrt_temp; - E2 = D2 - 4 * sqrt_temp; - } - } else { - P u = 0.75 * b2 - 2 * c - R2, v = 0.25 * inv_R * (4 * bc - 8 * d - b3); - D2 = u + v; - E2 = u - v; - } - - b_4 = 0.25 * b; - R_2 = 0.5 * R; - if (D2 >= 0) { - P D = sqrt(D2); - P D_2 = 0.5 * D; - nb_real_roots = 2; - x0 = R_2 + D_2 - b_4; - x1 = x0 - D; - } - - /* calculate E^2 */ - if (E2 >= 0) { - P E = sqrt(E2); - P E_2 = 0.5 * E; - if (nb_real_roots == 0) { - x0 = -R_2 + E_2 - b_4; - x1 = x0 - E; - nb_real_roots = 2; - } else { - x2 = -R_2 + E_2 - b_4; - x3 = x2 - E; - nb_real_roots = 4; - } - } - switch (nb_real_roots) { - // case 0: - // return new Tuple(0, null); - // break; // covered by the "default" case - case 1: - return std::vector

({x0}); - // break; - case 2: - return std::vector

({x0, x1}); - // break; - case 3: - return std::vector

({x0, x1, x2}); - // break; - case 4: - return std::vector

({x0, x1, x2, x3}); - // break; - default: - return std::vector

(); // just to shut the compiler up.... - // break; - } -} - -} // end namespace PolySolvers - -///

-/// This function performs two steps of Gauss-Jordan elmination in a 3x3 matrix. -/// NOTE: The function assumes that the matrix provided is RANK-2 and therefore -/// the solution -// will be the null space (vector). This way we obtain an eigen vector in -// two steps. -/// -/// is the coefficient matrix in flat -/// form std::vector

solutions -template std::vector

GaussJordan3x3(std::vector

flat_mat) { - // For the first step of the Gauss-Jordan Pivoting, we need the max of the - // coefficient from list0Pivot - // - auto absmax1it = std::max_element( - flat_mat.begin(), flat_mat.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - - P max1 = *absmax1it; - - // Get the index of max (in absolute value) - int index1 = std::distance(flat_mat.begin(), absmax1it); - - // If max1 = 0, we have a zero matrix and we need to return an empty list - if (max1 == 0) - return std::vector

(); - - // For the second step of the Gauss-Jordan Pivoting, we will require the - // maximum of the elements of the resulting matrix. - P max2; // The value of the absolute max in the next stage of the elimination - // (only need 2 stages for 3x3 matrices) - int index2; // the index of the absolute maximum in the next stage of the - // elimination - std::vector

flat_mat1; // and the next matrix - - // Variable which contain the coordinates of a vector - P x1 = 0, x2 = 0, x3 = 0; - - P* pX[3]; // using a pointer array to store permutations of x1, x2, x3 - P listFinalCoef[2]; - - // Taking cases according to where the maximum lies - if (index1 == 0) { - // List of the value after the first step of the Gauss-Jordan Pivoting - flat_mat1.push_back(flat_mat[4] - ((flat_mat[1] * flat_mat[3]) / max1)); - flat_mat1.push_back(flat_mat[5] - ((flat_mat[2] * flat_mat[3]) / max1)); - flat_mat1.push_back(flat_mat[7] - ((flat_mat[1] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[8] - ((flat_mat[2] * flat_mat[6]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x1; - pX[1] = &x2; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[1]; - listFinalCoef[1] = flat_mat[2]; - - } else if (index1 == 1) { - flat_mat1.push_back(flat_mat[3] - ((flat_mat[0] * flat_mat[4]) / max1)); - flat_mat1.push_back(flat_mat[5] - ((flat_mat[2] * flat_mat[4]) / max1)); - flat_mat1.push_back(flat_mat[6] - ((flat_mat[0] * flat_mat[7]) / max1)); - flat_mat1.push_back(flat_mat[8] - ((flat_mat[2] * flat_mat[7]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x2; - pX[1] = &x1; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[0]; - listFinalCoef[1] = flat_mat[2]; - - } else if (index1 == 2) { - flat_mat1.push_back(flat_mat[3] - ((flat_mat[0] * flat_mat[5]) / max1)); - flat_mat1.push_back(flat_mat[4] - ((flat_mat[1] * flat_mat[5]) / max1)); - flat_mat1.push_back(flat_mat[6] - ((flat_mat[0] * flat_mat[8]) / max1)); - flat_mat1.push_back(flat_mat[7] - ((flat_mat[1] * flat_mat[8]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x3; - pX[1] = &x1; - pX[2] = &x2; - - listFinalCoef[0] = flat_mat[0]; - listFinalCoef[1] = flat_mat[1]; - - } else if (index1 == 3) { - flat_mat1.push_back(flat_mat[1] - ((flat_mat[4] * flat_mat[0]) / max1)); - flat_mat1.push_back(flat_mat[2] - ((flat_mat[0] * flat_mat[5]) / max1)); - flat_mat1.push_back(flat_mat[7] - ((flat_mat[4] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[8] - ((flat_mat[5] * flat_mat[6]) / max1)); - - auto max2it = std::max_element(flat_mat1.begin(), flat_mat1.end()); - auto min2it = std::min_element(flat_mat1.begin(), flat_mat1.end()); - max2 = *max2it; - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x1; - pX[1] = &x2; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[4]; - listFinalCoef[1] = flat_mat[5]; - - } else if (index1 == 4) { - flat_mat1.push_back(flat_mat[0] - ((flat_mat[1] * flat_mat[3]) / max1)); - flat_mat1.push_back(flat_mat[2] - ((flat_mat[5] * flat_mat[1]) / max1)); - flat_mat1.push_back(flat_mat[6] - ((flat_mat[3] * flat_mat[7]) / max1)); - flat_mat1.push_back(flat_mat[8] - ((flat_mat[5] * flat_mat[7]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x2; - pX[1] = &x1; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[3]; - listFinalCoef[1] = flat_mat[5]; - - } else if (index1 == 5) { - flat_mat1.push_back(flat_mat[0] - ((flat_mat[2] * flat_mat[3]) / max1)); - flat_mat1.push_back(flat_mat[1] - ((flat_mat[2] * flat_mat[4]) / max1)); - flat_mat1.push_back(flat_mat[6] - ((flat_mat[8] * flat_mat[3]) / max1)); - flat_mat1.push_back(flat_mat[7] - ((flat_mat[8] * flat_mat[4]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x3; - pX[1] = &x1; - pX[2] = &x2; - - listFinalCoef[0] = flat_mat[3]; - listFinalCoef[1] = flat_mat[4]; - - } else if (index1 == 6) { - flat_mat1.push_back(flat_mat[1] - ((flat_mat[0] * flat_mat[7]) / max1)); - flat_mat1.push_back(flat_mat[2] - ((flat_mat[0] * flat_mat[8]) / max1)); - flat_mat1.push_back(flat_mat[4] - ((flat_mat[3] * flat_mat[7]) / max1)); - flat_mat1.push_back(flat_mat[5] - ((flat_mat[3] * flat_mat[8]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x1; - pX[1] = &x2; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[7]; - listFinalCoef[1] = flat_mat[8]; - - } else if (index1 == 7) { - flat_mat1.push_back(flat_mat[0] - ((flat_mat[1] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[2] - ((flat_mat[1] * flat_mat[8]) / max1)); - flat_mat1.push_back(flat_mat[3] - ((flat_mat[4] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[5] - ((flat_mat[4] * flat_mat[8]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x2; - pX[1] = &x1; - pX[2] = &x3; - - listFinalCoef[0] = flat_mat[6]; - listFinalCoef[1] = flat_mat[8]; - - } else if (index1 == 8) { - flat_mat1.push_back(flat_mat[0] - ((flat_mat[2] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[1] - ((flat_mat[2] * flat_mat[7]) / max1)); - flat_mat1.push_back(flat_mat[3] - ((flat_mat[5] * flat_mat[6]) / max1)); - flat_mat1.push_back(flat_mat[4] - ((flat_mat[5] * flat_mat[7]) / max1)); - - // Get maximum and minimum elements (in order to get the maximum in absolute - // value) - auto absmax2it = std::max_element( - flat_mat1.begin(), flat_mat1.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - max2 = *absmax2it; - - // NOTE: Now max2it points to the absolute maximum ! - index2 = std::distance(flat_mat1.begin(), absmax2it); - - pX[0] = &x3; - pX[1] = &x1; - pX[2] = &x2; - - listFinalCoef[0] = flat_mat[6]; - listFinalCoef[1] = flat_mat[7]; - } - - if (index2 == 0) { - *(pX[2]) = 1; - *(pX[1]) = -flat_mat1[1] / max2; - } else if (index2 == 1) { - *(pX[2]) = -flat_mat1[0] / max2; - *(pX[1]) = 1; - } else if (index2 == 2) { - *(pX[2]) = 1; - *(pX[1]) = -flat_mat1[3] / max2; - } else if (index2 == 3) { - *(pX[2]) = -flat_mat1[2] / max2; - *(pX[1]) = 1; - } - - *(pX[0]) = -((listFinalCoef[0] * *(pX[1])) / max1) - - ((listFinalCoef[1] * *(pX[2])) / max1); - - // normalize - P norm = sqrt(x1 * x1 + x2 * x2 + x3 * x3); - - return std::vector

({x1 / norm, x2 / norm, x3 / norm}); -} - -/// -/// This function eliminates the coefficients of the column "col" based on an -/// element at (row, col) -/// -/// -template -std::vector

GaussJordanFirstStep(std::vector

flat_mat, int row, int col) { - - // the result - std::vector

result; - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - - if (i != row && j != col) { - result.push_back(flat_mat[i * 4 + j] - - ((flat_mat[row * 4 + j] * flat_mat[i * 4 + col]) / - flat_mat[row * 4 + col])); - } - } - } - - return result; -} - -/// -/// The Gauss-Jordan elimination for Rank-3 4x4 matrices -/// The steps are fixed and lead to a solution up to arbitrary scale. -/// This is how we solve for the eigenvectors of the a 4x4 marix. -/// -/// The input vector is a flat version of the 4x4 matrix -/// -template std::vector

GaussJordan4x4(std::vector

flat_mat) { - // Working out the the index of the max coefficient (absolute value). - auto absmax1it = std::max_element( - flat_mat.begin(), flat_mat.end(), - [](const int& a, const int& b) { return fabs(a) < fabs(b); }); - - P max1 = *absmax1it; - int index1 = std::distance(flat_mat.begin(), absmax1it); - - // Return empty list if the maximum is zero (zero matrix) - if (max1 == 0) - return std::vector

(); - - // Create new variable which are use after. - std::vector

flat_mat1; - - P x1 = 0, x2 = 0, x3 = 0, x4 = 0; - // Cache for the result of 3x3 Gauss Jordan elimination - std::vector

resultGaussJordan3x3; - - // go... - if (index1 == 0) { - /// We use the GaussJordan3x3 solver after the first step, which is the - /// elimination of coeffcieints along column 0. - flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 0); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x2 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x1 = -(1 / flat_mat[0]) * - (flat_mat[1] * x2 + flat_mat[2] * x3 + flat_mat[3] * x4); - } else if (index1 == 1) { - /// We use the GaussJordan3x3 solver after the first step, which is the - /// elimination of coeffcieints along column 1. - flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 1); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x2 = -(1 / flat_mat[1]) * - (flat_mat[0] * x1 + flat_mat[2] * x3 + flat_mat[3] * x4); - } else if (index1 == 2) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 2); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x3 = -(1 / flat_mat[2]) * - (flat_mat[0] * x1 + flat_mat[1] * x2 + flat_mat[3] * x4); - } else if (index1 == 3) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 0, 3); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x3 = resultGaussJordan3x3[2]; - x4 = -(1 / flat_mat[3]) * - (flat_mat[0] * x1 + flat_mat[1] * x2 + flat_mat[2] * x3); - } else if (index1 == 4) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 0); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x2 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x1 = -(1 / flat_mat[4]) * - (flat_mat[5] * x2 + flat_mat[6] * x3 + flat_mat[7] * x4); - } else if (index1 == 5) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 1); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x2 = -(1 / flat_mat[5]) * - (flat_mat[4] * x1 + flat_mat[6] * x3 + flat_mat[7] * x4); - } else if (index1 == 6) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 2); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x3 = -(1 / flat_mat[6]) * - (flat_mat[4] * x1 + flat_mat[5] * x2 + flat_mat[7] * x4); - } else if (index1 == 7) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 1, 3); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x3 = resultGaussJordan3x3[2]; - x4 = -(1 / flat_mat[7]) * - (flat_mat[4] * x1 + flat_mat[5] * x2 + flat_mat[6] * x3); - } else if (index1 == 8) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 0); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x2 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x1 = -(1 / flat_mat[8]) * - (flat_mat[9] * x2 + flat_mat[10] * x3 + flat_mat[11] * x4); - } else if (index1 == 9) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 1); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x2 = -(1 / flat_mat[9]) * - (flat_mat[8] * x1 + flat_mat[10] * x3 + flat_mat[11] * x4); - } else if (index1 == 10) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 2); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x3 = -(1 / flat_mat[10]) * - (flat_mat[8] * x1 + flat_mat[9] * x2 + flat_mat[11] * x4); - } else if (index1 == 11) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 2, 3); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x3 = resultGaussJordan3x3[2]; - x4 = -(1 / flat_mat[11]) * - (flat_mat[8] * x1 + flat_mat[9] * x2 + flat_mat[10] * x3); - } else if (index1 == 12) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 0); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x2 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x1 = -(1 / flat_mat[12]) * - (flat_mat[13] * x2 + flat_mat[14] * x3 + flat_mat[15] * x4); - - } else if (index1 == 13) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 1); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x3 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x2 = -(1 / flat_mat[13]) * - (flat_mat[12] * x1 + flat_mat[14] * x3 + flat_mat[15] * x4); - } else if (index1 == 14) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 2); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x4 = resultGaussJordan3x3[2]; - x3 = -(1 / flat_mat[14]) * - (flat_mat[12] * x1 + flat_mat[13] * x2 + flat_mat[15] * x4); - } else if (index1 == 15) { - flat_mat1 = GaussJordanFirstStep(flat_mat, 3, 3); - resultGaussJordan3x3 = GaussJordan3x3(flat_mat1); - x1 = resultGaussJordan3x3[0]; - x2 = resultGaussJordan3x3[1]; - x3 = resultGaussJordan3x3[2]; - x4 = -(1 / flat_mat[15]) * - (flat_mat[12] * x1 + flat_mat[13] * x2 + flat_mat[14] * x3); - } - - // normalize the solution - P norm = sqrt(x1 * x1 + x2 * x2 + x3 * x3 + x4 * x4); - - return std::vector

({x1 / norm, x2 / norm, x3 / norm, x4 / norm}); -} - -/// -/// Return the list of the eigenvalue of a 4x4 matrix. -/// Parameter is he argumen is a 1D array representing the 4x4 matrix -/// T -/// -template std::vector

EigenValues4x4(P* array) { - - P a11 = array[0 * 4 + 0], a12 = array[0 * 4 + 1], a13 = array[0 * 4 + 2], - a14 = array[0 * 4 + 3]; - P a21 = array[1 * 4 + 0], a22 = array[1 * 4 + 1], a23 = array[1 * 4 + 2], - a24 = array[1 * 4 + 3]; - P a31 = array[2 * 4 + 0], a32 = array[2 * 4 + 1], a33 = array[2 * 4 + 2], - a34 = array[2 * 4 + 3]; - P a41 = array[3 * 4 + 0], a42 = array[3 * 4 + 1], a43 = array[3 * 4 + 2], - a44 = array[3 * 4 + 3]; - - // 1. Obtaining the coefficients of the characteristics polynomial of A11 - - // λ*I where A11 is the (1, 1) submatrix of A: A11 = [a22-λ a23 a24; - // a32 a33-λ a34; - // a42 a43 a44 - λ] - - // The coefficients of the cubic polynomial det(A11-λI) are: - P C3 = -1, C2 = a22 + a33 + a44; - P C1 = a42 * a24 + a32 * a23 + a34 * a43 - a22 * a33 - a22 * a44 - a33 * a44; - P C0 = a22 * a33 * a44 + a23 * a42 * a34 + a24 * a32 * a43 - a22 * a34 * a43 - - a23 * a32 * a44 - a24 * a42 * a33; - - // 2. Now multiplying with a11 to get a quartic polynomial with coeffcients - // W4, W3, W2, W1, W0 as follows: - P W4 = -C3, W3 = a11 * C3 - C2, W2 = a11 * C2 - C1, W1 = a11 * C1 - C0, - W0 = a11 * C0; - - // 4. Now we obtain the coefficients of the 3 quadratics (from the algebraic - // complements A12, A13, A14) as follows: a. (A-λI)12 (-) - P Q1_2 = -a12 * a21, - Q1_1 = -a12 * (-a21 * (a33 + a44) + a23 * a31 + a24 * a41), - Q1_0 = - -a12 * (a24 * (a31 * a43 - a41 * a33) - a23 * (a31 * a44 - a41 * a34) + - a21 * (a33 * a44 - - a43 * a34)); // good all being well... - // multiplying with -a12 - // P1_2 *= -a12; P1_1 *= -a12; P1_0 *= -a12; - - // b. (A-λ)13 (+) - P Q2_2 = a13 * -a31, - Q2_1 = a13 * ((a31 * a44 - a41 * a34 + a22 * a31) - a21 * a32), - Q2_0 = - a13 * (a24 * (a31 * a42 - a41 * a32) + a22 * (a41 * a34 - a31 * a44) + - a21 * (a32 * a44 - a42 * a34)); - - // c. (A-λ)14 (-) - P Q3_2 = -a14 * a41, - Q3_1 = -a14 * ((a31 * a43 - a41 * a33 - a22 * a41) + a21 * a42), - Q3_0 = - -a14 * (a23 * (a31 * a42 - a41 * a32) - a22 * (a31 * a43 - a41 * a33) + - a21 * (a32 * a43 - a42 * a33)); - - // 5. Final coefficients - P A0 = Q3_0 + Q2_0 + Q1_0 + W0, A1 = Q3_1 + Q2_1 + Q1_1 + W1, - A2 = Q3_2 + Q2_2 + Q1_2 + W2, A3 = W3, A4 = W4; - - std::vector

solution = PolySolvers::SolveQuartic(A4, A3, A2, A1, A0); - - // Put the solutions in ascending order - std::sort(solution.begin(), solution.end()); - - return solution; -} - -/// -/// Return the list of the eigenvalues of a 3x3 matrix. -/// Parameter is he argumen is a 1D array representing the 4x4 matrix -/// T -/// -template std::vector

EigenValues3x3(const P* array) { - - P coef1 = -1; - P coef2 = (array[0 * 3 + 0] + array[1 * 3 + 1] + array[2 * 3 + 2]); - P coef3 = (array[2 * 3 + 0] * array[0 * 3 + 2]) + - (array[1 * 3 + 0] * array[0 * 3 + 1]) + - (array[1 * 3 + 2] * array[2 * 3 + 1]) - - (array[0 * 3 + 0] * array[1 * 3 + 1]) - - (array[0 * 3 + 0] * array[2 * 3 + 2]) - - (array[1 * 3 + 1] * array[2 * 3 + 2]); - - P coef4 = (array[0 * 3 + 0] * array[1 * 3 + 1] * array[2 * 3 + 2]) + - (array[0 * 3 + 1] * array[2 * 3 + 0] * array[1 * 3 + 2]) + - (array[0 * 3 + 2] * array[1 * 3 + 0] * array[2 * 3 + 1]) - - (array[0 * 3 + 0] * array[1 * 3 + 2] * array[2 * 3 + 1]) - - (array[0 * 3 + 1] * array[1 * 3 + 0] * array[2 * 3 + 2]) - - (array[0 * 3 + 2] * array[2 * 3 + 0] * array[1 * 3 + 1]); - - std::vector

solution = PolySolvers::SolveCubic(coef1, coef2, coef3, coef4); - - std::sort(solution.begin(), solution.end()); - - // for (int i = 0; i < solution.size(); i++) - // std::cout << "eigenvalue "< std::vector

PrincipalEigenvector4x4(P* M) { - // First obtain the eigenvalues of M - auto eigenvalues = EigenValues4x4(M); - if (eigenvalues.size() == 0) - return std::vector

({0, 0, 0, 0}); - // auto absmaxit = std::max_element( eigenvalues.begin(), - // eigenvalues.end() , - // [](const int& a, const int& b) { return fabs(a) < - // fabs(b);} - // ); - // P lambda = *absmaxit; - P lambda = eigenvalues[eigenvalues.size() - - 1]; // returning the largest eigenvalue (instead or - // trhe largest in absolute value) - if (lambda == 0) - return std::vector

({0, 0, 0, 0}); - // Now obtaining a vector containing the M-lambda * eye(3) - std::vector

A; - A.push_back(M[0] - lambda); - A.push_back(M[1]); - A.push_back(M[2]); - A.push_back(M[3]); - A.push_back(M[4]); - A.push_back(M[5] - lambda); - A.push_back(M[6]); - A.push_back(M[7]); - A.push_back(M[8]); - A.push_back(M[9]); - A.push_back(M[10] - lambda); - A.push_back(M[11]); - A.push_back(M[12]); - A.push_back(M[13]); - A.push_back(M[14]); - A.push_back(M[15] - lambda); - - // Now get the eigenvector using the Gauss-jordan steps - return GaussJordan4x4(A); -} - -// Get the eigenvector of the largest eigenvalue of a 4x4 matrix. -// NOTE: This function will return the zero vector even if no eigenvalues exist -template std::vector

PrincipalEigenvector3x3(P* M) { - // First obtain the eigenvalues of M - auto eigenvalues = EigenValues3x3(M); - if (eigenvalues.size() == 0) - return std::vector

({0, 0, 0}); - // auto maxit = std::max_element( eigenvalues.begin(), - // eigenvalues.end() , - // [](const int& a, const int& b) { return fabs(a) < - // fabs(b); } - // ); - - // P lambda = *maxit; - P lambda = eigenvalues[eigenvalues.size() - 1]; // get the largest eigenvalue - // if there are only zero eigenvalues, return the zero vector - if (lambda == 0) - return std::vector

({0, 0, 0}); // extra check - - // Now obtaining a vector containing the M-lambda * eye(3) - std::vector

A; - A.push_back(M[0] - lambda); - A.push_back(M[1]); - A.push_back(M[2]); - A.push_back(M[3]); - A.push_back(M[4] - lambda); - A.push_back(M[5]); - A.push_back(M[6]); - A.push_back(M[7]); - A.push_back(M[8] - lambda); - - // Now get the eigenvector using the Gauss-jordan steps - return GaussJordan3x3(A); -} - -// 3x3 Matrix eigen decomposition -template -std::pair, std::vector>> EigenDecompose3x3(P* M) { - // First obtain the eigenvalues of M - auto eigenvalues = EigenValues3x3(M); - // vector of eigenvectors - std::vector> eigenvectors; - // return an empty eigenvalue list and a single eigenvector withg zeros in it - if (eigenvalues.size() == 0) - return std::pair, std::vector>>(eigenvalues, - eigenvectors); - - std::vector

A({M[0], M[1], M[2], M[3], M[4], M[5], M[6], M[7], M[8]}); - - for (int i = 0; i < eigenvalues.size(); i++) { - // Now subtract the eigenvalue from the diagonal - A[0] -= eigenvalues[i]; - A[4] -= eigenvalues[i]; - A[8] -= eigenvalues[i]; - - // compute and add the eigen vector to the list - eigenvectors.push_back(GaussJordan3x3(A)); - - // add the eigenvalue back to the diagonal in order to undo the change in - // the elements of A - A[0] += eigenvalues[i]; - A[4] += eigenvalues[i]; - A[8] += eigenvalues[i]; - } - - // return the decomposition (in ascending eigenvalue order) - return std::pair, std::vector>>(eigenvalues, - eigenvectors); -} - -// 4x4 Matrix eigen decomposition -template -std::pair, std::vector>> EigenDecompose4x4(P* M) { - // First obtain the eigenvalues of M - auto eigenvalues = EigenValues4x4(M); - // vector of eigenvectors - std::vector> eigenvectors; - - // return an empty eigenvalue list and a single eigenvector withg zeros in it - if (eigenvalues.size() == 0) - return std::pair, std::vector>>(eigenvalues, - eigenvectors); - - std::vector

A({M[0], M[1], M[2], M[3], M[4], M[5], M[6], M[7], M[8], M[9], - M[10], M[11], M[12], M[13], M[14], M[15]}); - - for (int i = 0; i < eigenvalues.size(); i++) { - // Now subtract the eigenvalue from the diagonal - A[0] -= eigenvalues[i]; - A[5] -= eigenvalues[i]; - A[10] -= eigenvalues[i]; - A[15] -= eigenvalues[i]; - - // compute and add the eigen vector to the list - eigenvectors.push_back(GaussJordan4x4(A)); - - // add the eigenvalue back to the diagonal in order to undo the change in - // the elements of A - A[0] += eigenvalues[i]; - A[5] += eigenvalues[i]; - A[10] += eigenvalues[i]; - A[15] += eigenvalues[i]; - } - - // return the decomposition (in ascending eigenvalue order) - return std::pair, std::vector>>(eigenvalues, - eigenvectors); -} - -std::pair self_adjoint_evd(rmatrix4x4 A) { - rmatrix4x4 rvectors; // TODO: vectors result in nan because max2 is zero - rdiagonal4x4 rvalues; - auto [values, vectors] = EigenDecompose4x4(A.data()); - for (int i = 0; i < values.size(); ++i) { - for (int j = 0; j < vectors[i].size(); ++j) { - rvectors[j * 4 + i] = vectors[j][i]; - } - rvalues[i] = values[i]; - } - return {rvectors, rvalues}; -} -} // namespace mqt::ir::opt - -#endif diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/f.h b/mlir/lib/Dialect/MQTOpt/Transforms/f.h deleted file mode 100644 index 4cbb4c86c..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/f.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include "Helpers.h" - -namespace mqt::ir::opt { -inline bool jacobi_4x4(double * A, double * D, double * U) -{ - double B[4], Z[4]; - double Id[16] = {1., 0., 0., 0., - 0., 1., 0., 0., - 0., 0., 1., 0., - 0., 0., 0., 1.}; - - memcpy(U, Id, 16 * sizeof(double)); - - B[0] = A[0]; B[1] = A[5]; B[2] = A[10]; B[3] = A[15]; - memcpy(D, B, 4 * sizeof(double)); - memset(Z, 0, 4 * sizeof(double)); - - for(int iter = 0; iter < 50; iter++) { - double sum = fabs(A[1]) + fabs(A[2]) + fabs(A[3]) + fabs(A[6]) + fabs(A[7]) + fabs(A[11]); - - if (sum == 0.0) - return true; - - double tresh = (iter < 3) ? 0.2 * sum / 16. : 0.0; - for(int i = 0; i < 3; i++) { - double * pAij = A + 5 * i + 1; - for(int j = i + 1 ; j < 4; j++) { - double Aij = *pAij; - double eps_machine = 100.0 * fabs(Aij); - - if ( iter > 3 && fabs(D[i]) + eps_machine == fabs(D[i]) && fabs(D[j]) + eps_machine == fabs(D[j]) ) - *pAij = 0.0; - else if (fabs(Aij) > tresh) { - double hh = D[j] - D[i], t; - if (fabs(hh) + eps_machine == fabs(hh)) - t = Aij / hh; - else { - double theta = 0.5 * hh / Aij; - t = 1.0 / (fabs(theta) + sqrt(1.0 + theta * theta)); - if (theta < 0.0) t = -t; - } - - hh = t * Aij; - Z[i] -= hh; - Z[j] += hh; - D[i] -= hh; - D[j] += hh; - *pAij = 0.0; - - double c = 1.0 / sqrt(1 + t * t); - double s = t * c; - double tau = s / (1.0 + c); - for(int k = 0; k <= i - 1; k++) { - double g = A[k * 4 + i], h = A[k * 4 + j]; - A[k * 4 + i] = g - s * (h + g * tau); - A[k * 4 + j] = h + s * (g - h * tau); - } - for(int k = i + 1; k <= j - 1; k++) { - double g = A[i * 4 + k], h = A[k * 4 + j]; - A[i * 4 + k] = g - s * (h + g * tau); - A[k * 4 + j] = h + s * (g - h * tau); - } - for(int k = j + 1; k < 4; k++) { - double g = A[i * 4 + k], h = A[j * 4 + k]; - A[i * 4 + k] = g - s * (h + g * tau); - A[j * 4 + k] = h + s * (g - h * tau); - } - for(int k = 0; k < 4; k++) { - double g = U[k * 4 + i], h = U[k * 4 + j]; - U[k * 4 + i] = g - s * (h + g * tau); - U[k * 4 + j] = h + s * (g - h * tau); - } - } - pAij++; - } - } - - for(int i = 0; i < 4; i++) B[i] += Z[i]; - memcpy(D, B, 4 * sizeof(double)); - memset(Z, 0, 4 * sizeof(double)); - } - - return false; -} - - -std::pair self_adjoint_evd(rmatrix4x4 A) { - rmatrix4x4 rvectors{}; - rdiagonal4x4 rvalues{}; - if (!jacobi_4x4(A.data(), rvectors.data(), rvalues.data())) { - throw std::runtime_error{"bad luck..."}; - } - return {rvectors, rvalues}; -} -} diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/g.h b/mlir/lib/Dialect/MQTOpt/Transforms/g.h deleted file mode 100644 index 8eb6c4054..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/g.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "Helpers.h" -#include "eigen3/Eigen/Eigen" - -namespace mqt::ir::opt { - -auto self_adjoint_evd(rmatrix4x4 A) { - Eigen::Matrix4d a; - Eigen::SelfAdjointEigenSolver s; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - a(j, i) = A[j * 4 + i]; - } - } - std::cerr << "=EigIN==\n" << a << "\n========\n" << std::endl; - s.compute(a); // TODO: computeDirect is faster - auto vecs = s.eigenvectors(); - auto vals = s.eigenvalues(); - rmatrix4x4 rvecs; - rdiagonal4x4 rvals; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - rvecs[j * 4 + i] = vecs(j, i); - } - rvals[i] = vals(i); - } - std::cerr << "=Eigen==\n" << vecs << "\n========\n" << std::endl; - std::cerr << "=Eigen==\n" << vals << "\n========\n" << std::endl; - return std::make_pair(rvecs, rvals); -} -} // namespace mqt::ir::opt From d50484d428a6b9b8d1d691ea19850f4804c83bef Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 10 Nov 2025 21:35:01 +0100 Subject: [PATCH 045/100] delete pulse-optimizer code (KEEP for future; should be re-added at some point) --- .../Transforms/GateDecompositionPattern.cpp | 260 +----------------- 1 file changed, 1 insertion(+), 259 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 69f5f1a25..bdfdd14bb 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -1243,7 +1243,6 @@ struct GateDecompositionPattern final QubitGateSequence::Gate basis_gate; fp basis_fidelity; EulerBasis euler_basis; - std::optional pulse_optimize; TwoQubitWeylDecomposition basis_decomposer; bool super_controlled; matrix2x2 u0l; @@ -1271,8 +1270,7 @@ struct GateDecompositionPattern final new_inner(OneQubitGateSequence::Gate basis_gate = {.type = qc::X, .parameter = {}, .qubit_id = {0, 1}}, - fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ, - std::optional pulse_optimize = std::nullopt) { + fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ) { auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& max_relative) { // Handle same infinities @@ -1394,7 +1392,6 @@ struct GateDecompositionPattern final basis_gate, basis_fidelity, euler_basis, - pulse_optimize, basis_decomposer, super_controlled, u0l, @@ -1470,12 +1467,6 @@ struct GateDecompositionPattern final for (auto x : decomposition) { helpers::print(x, "", true); } - if (pulse_optimize.value_or(true)) { - if (auto result = pulse_optimal_chooser(best_nbasis, decomposition, - target_decomposed)) { - return result; - } - } std::vector target_1q_basis_list; // TODO: simplify because list only has one // element? @@ -1573,241 +1564,6 @@ struct GateDecompositionPattern final }; } - std::optional - pulse_optimal_chooser(std::uint8_t best_nbasis, - std::vector decomposition, - const TwoQubitWeylDecomposition& target_decomposed) { - if (pulse_optimize.has_value() && - (best_nbasis == 0 || best_nbasis == 1 || best_nbasis > 3)) { - return std::nullopt; - } - if (euler_basis != EulerBasis::ZSX && euler_basis != EulerBasis::ZSXX) { - if (pulse_optimize.has_value()) { - throw std::runtime_error{ - "'pulse_optimize' currently only works with ZSX basis"}; - } - return std::nullopt; - } - - if (basis_gate.type != qc::X || - basis_gate.qubit_id.size() != 2) { // != CX - if (pulse_optimize.has_value()) { - throw std::runtime_error{"pulse_optimizer currently only works " - "with CNOT entangling gate"}; - } - return std::nullopt; - } - std::optional result; - if (best_nbasis == 3) { - result = - get_sx_vz_3cx_efficient_euler(decomposition, target_decomposed); - } else if (best_nbasis == 2) { - result = - get_sx_vz_2cx_efficient_euler(decomposition, target_decomposed); - } - if (pulse_optimize.has_value() && !result.has_value()) { - throw std::runtime_error{ - "Failed to compute requested pulse optimal decomposition"}; - } - return result; - } - - /// - /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT - /// gates assuming two CNOT gates are needed. - /// - /// This first decomposes each unitary from the KAK decomposition into ZXZ - /// on the source qubit of the CNOTs and XZX on the targets in order to - /// commute operators to beginning and end of decomposition. The beginning - /// and ending single qubit gates are then collapsed and re-decomposed - /// with the single qubit decomposer. This last step could be avoided if - /// performance is a concern. - TwoQubitGateSequence get_sx_vz_2cx_efficient_euler( - const std::vector& decomposition, - const TwoQubitWeylDecomposition& target_decomposed) { - TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; - gates.globalPhase -= 2. * basis_decomposer.global_phase; - - auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { - std::vector> result; - for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { - auto euler_angles = angles_from_unitary(decomposition[i], basis); - gates.globalPhase += euler_angles[3]; - result.push_back({euler_angles[2], euler_angles[0], euler_angles[1]}); - } - return result; - }; - - auto euler_q0 = get_euler_angles(0, EulerBasis::ZXZ); - auto euler_q1 = get_euler_angles(1, EulerBasis::XZX); - - matrix2x2 euler_matrix_q0 = - rx_matrix(euler_q0[0][1]) * rz_matrix(euler_q0[0][0]); - euler_matrix_q0 = rz_matrix(euler_q0[0][2] + euler_q0[1][0] + qc::PI_2) * - euler_matrix_q0; - append_1q_sequence(gates, euler_matrix_q0, 0); - matrix2x2 euler_matrix_q1 = - rz_matrix(euler_q1[0][1]) * rx_matrix(euler_q1[0][0]); - euler_matrix_q1 = - rx_matrix(euler_q1[0][2] + euler_q1[1][0]) * euler_matrix_q1; - append_1q_sequence(gates, euler_matrix_q1, 1); - gates.gates.push_back( - {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? - gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); - gates.gates.push_back({.type = qc::RZ, - .parameter = {euler_q0[1][1] - qc::PI}, - .qubit_id = {0}}); - gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); - gates.gates.push_back( - {.type = qc::RZ, .parameter = {euler_q1[1][1]}, .qubit_id = {1}}); - gates.globalPhase += qc::PI_2; - gates.gates.push_back( - {.type = qc::X, .qubit_id = {0, 1}}); // TODO: mark CX somehow? - euler_matrix_q0 = rx_matrix(euler_q0[2][1]) * - rz_matrix(euler_q0[1][2] + euler_q0[2][0] + qc::PI_2); - euler_matrix_q0 = rz_matrix(euler_q0[2][2]) * euler_matrix_q0; - append_1q_sequence(gates, euler_matrix_q0, 0); - euler_matrix_q1 = rz_matrix(euler_q1[2][1]) * - rx_matrix(euler_q1[1][2] + euler_q1[2][0]); - euler_matrix_q1 = rx_matrix(euler_q1[2][2]) * euler_matrix_q1; - append_1q_sequence(gates, euler_matrix_q1, 1); - return gates; - } - - /// Decomposition of SU(4) gate for device with SX, virtual RZ, and CNOT - /// gates assuming three CNOT gates are needed. - /// - /// This first decomposes each unitary from the KAK decomposition into ZXZ - /// on the source qubit of the CNOTs and XZX on the targets in order - /// commute operators to beginning and end of decomposition. Inserting - /// Hadamards reverses the direction of the CNOTs and transforms a - /// variable Rx -> variable virtual Rz. The beginning and ending single - /// qubit gates are then collapsed and re-decomposed with the single qubit - /// decomposer. This last step could be avoided if performance is a - /// concern. - std::optional get_sx_vz_3cx_efficient_euler( - const std::vector& decomposition, - const TwoQubitWeylDecomposition& target_decomposed) { - TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; - gates.globalPhase -= 3. * basis_decomposer.global_phase; - gates.globalPhase = remEuclid(gates.globalPhase, qc::TAU); - auto atol = 1e-10; // absolute tolerance for floats - // Decompose source unitaries to zxz - - auto get_euler_angles = [&](std::size_t startIndex, EulerBasis basis) { - std::vector> result; - for (std::size_t i = startIndex; i < decomposition.size(); i += 2) { - auto euler_angles = angles_from_unitary(decomposition[i], basis); - gates.globalPhase += euler_angles[3]; - result.push_back({euler_angles[2], euler_angles[0], euler_angles[1]}); - } - return result; - }; - - auto euler_q0 = get_euler_angles(0, EulerBasis::ZXZ); - // Decompose target unitaries to xzx - auto euler_q1 = get_euler_angles(1, EulerBasis::XZX); - - auto x12 = euler_q0[1][2] + euler_q0[2][0]; - auto x12_is_non_zero = std::abs(x12) < atol; - auto x12_is_old_mult = std::abs(std::cos(x12) - 1.0) < atol; - auto x12_phase = 0.; - auto x12_is_pi_mult = std::abs(std::sin(x12)) < atol; - if (x12_is_pi_mult) { - x12_phase = qc::PI * std::cos(x12); - } - auto x02_add = x12 - euler_q0[1][0]; - auto x12_is_half_pi = std::abs(x12 - qc::PI_2) < atol; - - matrix2x2 euler_matrix_q0 = - rx_matrix(euler_q0[0][1]) * rz_matrix(euler_q0[0][0]); - if (x12_is_non_zero && x12_is_pi_mult) { - euler_matrix_q0 = rz_matrix(euler_q0[0][2] - x02_add) * euler_matrix_q0; - } else { - euler_matrix_q0 = - rz_matrix(euler_q0[0][2] + euler_q0[1][0]) * euler_matrix_q0; - } - euler_matrix_q0 = hGate * euler_matrix_q0; - append_1q_sequence(gates, euler_matrix_q0, 0); - - auto rx_0 = rx_matrix(euler_q1[0][0]); - auto rz = rz_matrix(euler_q1[0][1]); - auto rx_1 = rx_matrix(euler_q1[0][2] + euler_q1[1][0]); - matrix2x2 euler_matrix_q1 = rz * rx_0; - euler_matrix_q1 = rx_1 * euler_matrix_q1; - euler_matrix_q1 = hGate * euler_matrix_q1; - append_1q_sequence(gates, euler_matrix_q1, 1); - - gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); - - if (x12_is_pi_mult) { - // even or odd multiple - if (x12_is_non_zero) { - gates.globalPhase += x12_phase; - } - if (x12_is_non_zero && x12_is_old_mult) { - gates.gates.push_back({.type = qc::RZ, - .parameter = {-euler_q0[1][1]}, - .qubit_id = {0}}); - } else { - gates.gates.push_back( - {.type = qc::RZ, .parameter = {euler_q0[1][1]}, .qubit_id = {0}}); - gates.globalPhase += qc::PI; - } - } - if (x12_is_half_pi) { - gates.gates.push_back({.type = qc::SX, .qubit_id = {0}}); - gates.globalPhase -= qc::PI_4; - } else if (x12_is_non_zero && !x12_is_pi_mult) { - if (!pulse_optimize.has_value()) { - append_1q_sequence(gates, rx_matrix(x12), 0); - } else { - return std::nullopt; - } - } - if (std::abs(euler_q1[1][1] - qc::PI_2) < atol) { - gates.gates.push_back({.type = qc::SX, .qubit_id = {1}}); - gates.globalPhase -= qc::PI_4; - } else if (!pulse_optimize.has_value()) { - append_1q_sequence(gates, rx_matrix(euler_q1[1][1]), 1); - } else { - return std::nullopt; - } - gates.gates.push_back({.type = qc::RZ, - .parameter = {euler_q1[1][2] + euler_q1[2][0]}, - .qubit_id = {1}}); - gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); - gates.gates.push_back( - {.type = qc::RZ, .parameter = {euler_q1[2][1]}, .qubit_id = {0}}); - if (std::abs(euler_q1[2][1] - qc::PI_2) < atol) { - gates.gates.push_back({.type = qc::SX, .qubit_id = {1}}); - gates.globalPhase -= qc::PI_4; - } else if (!pulse_optimize.has_value()) { - append_1q_sequence(gates, rx_matrix(euler_q1[2][1]), 1); - } else { - return std::nullopt; - } - gates.gates.push_back({.type = qc::X, .qubit_id = {1, 0}}); - matrix2x2 eulerMatrix = - rz_matrix(euler_q0[2][2] + euler_q0[3][0]) * hGate; - eulerMatrix = rx_matrix(euler_q0[3][1]) * eulerMatrix; - eulerMatrix = rz_matrix(euler_q0[3][2]) * eulerMatrix; - append_1q_sequence(gates, eulerMatrix, 0); - - eulerMatrix = rx_matrix(euler_q1[2][2] + euler_q1[3][0]) * hGate; - eulerMatrix = rz_matrix(euler_q1[3][1]) * eulerMatrix; - eulerMatrix = rx_matrix(euler_q1[3][2]) * eulerMatrix; - append_1q_sequence(gates, eulerMatrix, 1); - - auto out_unitary = compute_unitary(gates, gates.globalPhase); - // TODO: fix the sign problem to avoid correction here - if (std::abs(target_decomposed.unitary_matrix(0, 0) - - (-out_unitary(0, 0))) < atol) { - gates.globalPhase += qc::PI; - } - return gates; - } - matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, fp global_phase) { auto phase = std::exp(std::complex{0, global_phase}); @@ -1822,20 +1578,6 @@ struct GateDecompositionPattern final return matrix; } - void append_1q_sequence(TwoQubitGateSequence& twoQubitSequence, - matrix2x2 unitary, std::size_t qubit) { - std::vector target_1q_basis_list; - target_1q_basis_list.push_back(euler_basis); - auto sequence = unitary_to_gate_sequence_inner( - unitary, target_1q_basis_list, qubit, {}, true, std::nullopt); - twoQubitSequence.globalPhase += sequence.globalPhase; - for (auto&& gate : sequence.gates) { - twoQubitSequence.gates.push_back({.type = gate.type, - .parameter = gate.parameter, - .qubit_id = {qubit}}); - } - } - [[nodiscard]] std::array traces(TwoQubitWeylDecomposition target) const { return { From 9f7ac0ea625522812580c27e4be170206b2bde01 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 11 Nov 2025 12:39:49 +0100 Subject: [PATCH 046/100] use designated initializer lists --- .../Transforms/GateDecompositionPattern.cpp | 338 +++++++++--------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 2 +- 2 files changed, 169 insertions(+), 171 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index bdfdd14bb..85eca547f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -422,9 +422,7 @@ struct GateDecompositionPattern final std::cerr << "CORRECTION!\n"; // if determinant of eigenvector matrix is -1.0, multiply first // eigenvector by -1.0 - for (int i = 0; i < 4; ++i) { - U(i, 0) *= -1.0; - } + U.col(0) *= -1.0; } return std::make_pair(U, S); @@ -926,19 +924,19 @@ struct GateDecompositionPattern final auto specialization = _specialization.value_or(get_default_specialzation()); TwoQubitWeylDecomposition general{ - a, - b, - c, - global_phase, - K1l, - K2l, - K1r, - K2r, - Specialization::General, - default_euler_basis, - fidelity, - -1.0, - unitary_matrix, + .a=a, + .b=b, + .c=c, + .global_phase=global_phase, + .K1l=K1l, + .K2l=K2l, + .K1r=K1r, + .K2r=K2r, + .specialization=Specialization::General, + .default_euler_basis=default_euler_basis, + .requested_fidelity=fidelity, + .calculated_fidelity=-1.0, + .unitary_matrix=unitary_matrix, }; auto get_specialized_decomposition = [&]() { // :math:`U \sim U_d(0,0,0) \sim Id` @@ -948,19 +946,19 @@ struct GateDecompositionPattern final // :math:`K2_l = Id` , :math:`K2_r = Id`. if (specialization == Specialization::IdEquiv) { return TwoQubitWeylDecomposition{ - 0., - 0., - 0., - general.global_phase, - general.K1l * general.K2l, - identityGate, - general.K1r * general.K2r, - identityGate, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=0., + .b=0., + .c=0., + .global_phase=general.global_phase, + .K1l=general.K1l * general.K2l, + .K2l=identityGate, + .K1r=general.K1r * general.K2r, + .K2r=identityGate, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, @@ -972,36 +970,36 @@ struct GateDecompositionPattern final if (specialization == Specialization::SWAPEquiv) { if (c > 0.) { return TwoQubitWeylDecomposition{ - qc::PI_4, - qc::PI_4, - qc::PI_4, - general.global_phase, - general.K1l * general.K2r, - identityGate, - general.K1r * general.K2l, - identityGate, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=qc::PI_4, + .b=qc::PI_4, + .c=qc::PI_4, + .global_phase=general.global_phase, + .K1l=general.K1l * general.K2r, + .K2l=identityGate, + .K1r=general.K1r * general.K2l, + .K2r=identityGate, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } else { flipped_from_original = true; return TwoQubitWeylDecomposition{ - qc::PI_4, - qc::PI_4, - qc::PI_4, - global_phase + qc::PI_2, - general.K1l * IPZ * general.K2r, - identityGate, - general.K1r * IPZ * general.K2l, - identityGate, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=qc::PI_4, + .b=qc::PI_4, + .c=qc::PI_4, + .global_phase=global_phase + qc::PI_2, + .K1l=general.K1l * IPZ * general.K2r, + .K2l=identityGate, + .K1r=general.K1r * IPZ * general.K2l, + .K2r=identityGate, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } } @@ -1016,19 +1014,19 @@ struct GateDecompositionPattern final auto k2l_dag = general.K2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ - closest, - closest, - closest, - general.global_phase, - general.K1l * general.K2l, - identityGate, - general.K1r * general.K2l, - k2l_dag * general.K2r, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=closest, + .b=closest, + .c=closest, + .global_phase=general.global_phase, + .K1l=general.K1l * general.K2l, + .K2l=identityGate, + .K1r=general.K1r * general.K2l, + .K2r=k2l_dag * general.K2r, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim @@ -1046,19 +1044,19 @@ struct GateDecompositionPattern final auto k2l_dag = general.K2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ - closest, - closest, - -closest, - general.global_phase, - general.K1l * general.K2l, - identityGate, - general.K1r * IPZ * general.K2l * IPZ, - IPZ * k2l_dag * IPZ * general.K2r, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=closest, + .b=closest, + .c=-closest, + .global_phase=general.global_phase, + .K1l=general.K1l * general.K2l, + .K2l=identityGate, + .K1r=general.K1r * IPZ * general.K2l * IPZ, + .K2r=IPZ * k2l_dag * IPZ * general.K2r, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` @@ -1074,19 +1072,19 @@ struct GateDecompositionPattern final auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = angles_from_unitary(general.K2r, euler_basis); return TwoQubitWeylDecomposition{ - a, - 0., - 0., - global_phase + k2lphase + k2rphase, - general.K1l * rx_matrix(k2lphi), - ry_matrix(k2ltheta) * rx_matrix(k2llambda), - general.K1r * rx_matrix(k2rphi), - ry_matrix(k2rtheta) * rx_matrix(k2rlambda), - specialization, - euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=a, + .b=0., + .c=0., + .global_phase=global_phase + k2lphase + k2rphase, + .K1l=general.K1l * rx_matrix(k2lphi), + .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), + .K1r=general.K1r * rx_matrix(k2rphi), + .K2r=ry_matrix(k2rtheta) * rx_matrix(k2rlambda), + .specialization=specialization, + .default_euler_basis=euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot @@ -1102,19 +1100,19 @@ struct GateDecompositionPattern final auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = angles_from_unitary(general.K2r, EulerBasis::ZYZ); return TwoQubitWeylDecomposition{ - qc::PI_4, - qc::PI_4, - c, - global_phase + k2lphase + k2rphase, - general.K1l * rz_matrix(k2rphi), - ry_matrix(k2ltheta) * rz_matrix(k2llambda), - general.K1r * rz_matrix(k2lphi), - ry_matrix(k2rtheta) * rz_matrix(k2rlambda), - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=qc::PI_4, + .b=qc::PI_4, + .c=c, + .global_phase=global_phase + k2lphase + k2rphase, + .K1l=general.K1l * rz_matrix(k2rphi), + .K2l=ry_matrix(k2ltheta) * rz_matrix(k2llambda), + .K1r=general.K1r * rz_matrix(k2lphi), + .K2r=ry_matrix(k2rtheta) * rz_matrix(k2rlambda), + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` @@ -1126,19 +1124,19 @@ struct GateDecompositionPattern final auto [k2ltheta, k2lphi, k2llambda, k2lphase] = angles_from_unitary(general.K2l, EulerBasis::ZYZ); return TwoQubitWeylDecomposition{ - (a + b) / 2., - (a + b) / 2., - c, - global_phase + k2lphase, - general.K1l * rz_matrix(k2lphi), - ry_matrix(k2ltheta) * rz_matrix(k2llambda), - general.K1r * rz_matrix(k2lphi), - rz_matrix(-k2lphi) * general.K2r, - specialization, - general.default_euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=(a + b) / 2., + .b=(a + b) / 2., + .c=c, + .global_phase=global_phase + k2lphase, + .K1l=general.K1l * rz_matrix(k2lphi), + .K2l=ry_matrix(k2ltheta) * rz_matrix(k2llambda), + .K1r=general.K1r * rz_matrix(k2lphi), + .K2r=rz_matrix(-k2lphi) * general.K2r, + .specialization=specialization, + .default_euler_basis=general.default_euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` @@ -1151,19 +1149,19 @@ struct GateDecompositionPattern final auto [k2ltheta, k2lphi, k2llambda, k2lphase] = angles_from_unitary(general.K2l, euler_basis); return TwoQubitWeylDecomposition{ - a, - (b + c) / 2., - (b + c) / 2., - global_phase + k2lphase, - general.K1l * rx_matrix(k2lphi), - ry_matrix(k2ltheta) * rx_matrix(k2llambda), - general.K1r * rx_matrix(k2lphi), - rx_matrix(-k2lphi) * general.K2r, - specialization, - euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=a, + .b=(b + c) / 2., + .c=(b + c) / 2., + .global_phase=global_phase + k2lphase, + .K1l=general.K1l * rx_matrix(k2lphi), + .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), + .K1r=general.K1r * rx_matrix(k2lphi), + .K2r=rx_matrix(-k2lphi) * general.K2r, + .specialization=specialization, + .default_euler_basis=euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` @@ -1176,19 +1174,19 @@ struct GateDecompositionPattern final auto [k2ltheta, k2lphi, k2llambda, k2lphase] = angles_from_unitary(general.K2l, euler_basis); return TwoQubitWeylDecomposition{ - a, - (b - c) / 2., - -((b - c) / 2.), - global_phase + k2lphase, - general.K1l * rx_matrix(k2lphi), - ry_matrix(k2ltheta) * rx_matrix(k2llambda), - general.K1r * IPZ * rx_matrix(k2lphi) * IPZ, - IPZ * rx_matrix(-k2lphi) * IPZ * general.K2r, - specialization, - euler_basis, - general.requested_fidelity, - general.calculated_fidelity, - general.unitary_matrix, + .a=a, + .b=(b - c) / 2., + .c=-((b - c) / 2.), + .global_phase=global_phase + k2lphase, + .K1l=general.K1l * rx_matrix(k2lphi), + .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), + .K1r=general.K1r * IPZ * rx_matrix(k2lphi) * IPZ, + .K2r=IPZ * rx_matrix(-k2lphi) * IPZ * general.K2r, + .specialization=specialization, + .default_euler_basis=euler_basis, + .requested_fidelity=general.requested_fidelity, + .calculated_fidelity=general.calculated_fidelity, + .unitary_matrix=general.unitary_matrix, }; } // U has no special symmetry. @@ -1389,30 +1387,30 @@ struct GateDecompositionPattern final auto q2r = k2rd * K12R_ARR; return TwoQubitBasisDecomposer{ - basis_gate, - basis_fidelity, - euler_basis, - basis_decomposer, - super_controlled, - u0l, - u0r, - u1l, - u1ra, - u1rb, - u2la, - u2lb, - u2ra, - u2rb, - u3l, - u3r, - q0l, - q0r, - q1la, - q1lb, - q1ra, - q1rb, - q2l, - q2r, + .basis_gate=basis_gate, + .basis_fidelity=basis_fidelity, + .euler_basis=euler_basis, + .basis_decomposer=basis_decomposer, + .super_controlled=super_controlled, + .u0l=u0l, + .u0r=u0r, + .u1l=u1l, + .u1ra=u1ra, + .u1rb=u1rb, + .u2la=u2la, + .u2lb=u2lb, + .u2ra=u2ra, + .u2rb=u2rb, + .u3l=u3l, + .u3r=u3r, + .q0l=q0l, + .q0r=q0r, + .q1la=q1la, + .q1lb=q1lb, + .q1ra=q1ra, + .q1rb=q1rb, + .q2l=q2l, + .q2r=q2r, }; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 3a153248e..595ce2566 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -18,7 +18,7 @@ #include #include #include -#include +#include // TODO: unstable #include #include From c69618f77b77d267699454d0bcb58e55e9e9546a Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 11 Nov 2025 13:13:19 +0100 Subject: [PATCH 047/100] fix warnings, rename all variables --- .../Transforms/GateDecompositionPattern.cpp | 1140 ++++++++--------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 32 +- 2 files changed, 587 insertions(+), 585 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 85eca547f..e50cb5e5b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -16,12 +16,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include namespace mqt::ir::opt { @@ -60,19 +62,19 @@ struct GateDecompositionPattern final } matrix4x4 unitaryMatrix = - helpers::kroneckerProduct(identityGate, identityGate); + helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); int i{}; for (auto&& gate : series.gates) { auto gateMatrix = getTwoQubitMatrix({.type = helpers::getQcType(gate.op), .parameter = helpers::getParameters(gate.op), - .qubit_id = gate.qubitIds}); + .qubitId = gate.qubitIds}); unitaryMatrix = gateMatrix * unitaryMatrix; helpers::print(gateMatrix, "GATE MATRIX " + std::to_string(i++), true); } helpers::print(unitaryMatrix, "UNITARY MATRIX", true); - auto decomposer = TwoQubitBasisDecomposer::new_inner(); + auto decomposer = TwoQubitBasisDecomposer::newInner(); auto sequence = decomposer.twoQubitDecompose( unitaryMatrix, DEFAULT_FIDELITY, true, std::nullopt); if (!sequence) { @@ -147,12 +149,12 @@ struct GateDecompositionPattern final if (helpers::isSingleQubitOperation(initialOperation)) { inQubits = {in[0], mlir::Value{}}; outQubits = {out[0], mlir::Value{}}; - gates.push_back({initialOperation, {0}}); + gates.push_back({.op = initialOperation, .qubitIds = {0}}); complexity += 1; } else if (helpers::isTwoQubitOperation(initialOperation)) { inQubits = {in[0], in[1]}; outQubits = {out[0], out[1]}; - gates.push_back({initialOperation, {0, 1}}); + gates.push_back({.op = initialOperation, .qubitIds = {0, 1}}); complexity += 2; } } @@ -170,7 +172,7 @@ struct GateDecompositionPattern final std::size_t qubitId = std::distance(outQubits.begin(), it); *it = nextGate->getResult(0); - gates.push_back({nextGate, {qubitId}}); + gates.push_back({.op = nextGate, .qubitIds = {qubitId}}); complexity += 1; return true; } @@ -205,7 +207,8 @@ struct GateDecompositionPattern final std::size_t secondQubitId = std::distance(outQubits.begin(), secondQubitIt); - gates.push_back({nextGate, {firstQubitId, secondQubitId}}); + gates.push_back( + {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); complexity += 2; return true; } @@ -237,7 +240,7 @@ struct GateDecompositionPattern final static OpType createGate(mlir::PatternRewriter& rewriter, mlir::Location location, mlir::ValueRange inQubits, mlir::ValueRange ctrlQubits, - llvm::SmallVector parameters) { + const llvm::SmallVector& parameters) { mlir::SmallVector parameterValues; for (auto&& parameter : parameters) { auto parameterValue = rewriter.create( @@ -255,17 +258,17 @@ struct GateDecompositionPattern final struct Gate { qc::OpType type{qc::I}; llvm::SmallVector parameter; - llvm::SmallVector qubit_id = {0}; + llvm::SmallVector qubitId = {0}; }; std::vector gates; std::size_t complexity() { std::size_t c{}; for (auto&& gate : gates) { - c += gate.qubit_id.size(); + c += gate.qubitId.size(); } return c; } - fp globalPhase; + fp globalPhase{}; }; using OneQubitGateSequence = QubitGateSequence; using TwoQubitGateSequence = QubitGateSequence; @@ -287,11 +290,11 @@ struct GateDecompositionPattern final [&inQubits](const TwoQubitGateSequence::Gate& gateDescription, auto&& newGate) { auto results = newGate.getAllOutQubits(); - if (gateDescription.qubit_id.size() == 2) { - inQubits[gateDescription.qubit_id[0]] = results[0]; - inQubits[gateDescription.qubit_id[1]] = results[1]; - } else if (gateDescription.qubit_id.size() == 1) { - inQubits[gateDescription.qubit_id[0]] = results[0]; + if (gateDescription.qubitId.size() == 2) { + inQubits[gateDescription.qubitId[0]] = results[0]; + inQubits[gateDescription.qubitId[1]] = results[1]; + } else if (gateDescription.qubitId.size() == 1) { + inQubits[gateDescription.qubitId[0]] = results[0]; } else { throw std::logic_error{"Invalid number of qubit IDs!"}; } @@ -307,15 +310,15 @@ struct GateDecompositionPattern final std::cerr << qc::toString(gate.type) << ", "; if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; - if (gate.qubit_id.size() > 1) { - inCtrlQubits.push_back(inQubits[gate.qubit_id[1]]); + if (gate.qubitId.size() > 1) { + inCtrlQubits.push_back(inQubits[gate.qubitId[1]]); } auto newGate = createGate(rewriter, location, {inQubits[0]}, inCtrlQubits, gate.parameter); updateInQubits(gate, newGate); } else if (gate.type == qc::RX) { mlir::SmallVector qubits; - for (auto&& x : gate.qubit_id) { + for (auto&& x : gate.qubitId) { qubits.push_back(inQubits[x]); } auto newGate = @@ -323,7 +326,7 @@ struct GateDecompositionPattern final updateInQubits(gate, newGate); } else if (gate.type == qc::RY) { mlir::SmallVector qubits; - for (auto&& x : gate.qubit_id) { + for (auto&& x : gate.qubitId) { qubits.push_back(inQubits[x]); } auto newGate = @@ -331,7 +334,7 @@ struct GateDecompositionPattern final updateInQubits(gate, newGate); } else if (gate.type == qc::RZ) { mlir::SmallVector qubits; - for (auto&& x : gate.qubit_id) { + for (auto&& x : gate.qubitId) { qubits.push_back(inQubits[x]); } auto newGate = @@ -348,7 +351,7 @@ struct GateDecompositionPattern final } } - enum class Specialization { + enum class Specialization : std::uint8_t { General, IdEquiv, SWAPEquiv, @@ -358,17 +361,17 @@ struct GateDecompositionPattern final MirrorControlledEquiv, // These next 3 gates use the definition of fSim from eq (1) in: // https://arxiv.org/pdf/2001.08343.pdf - fSimaabEquiv, - fSimabbEquiv, - fSimabmbEquiv, + FSimaabEquiv, + FSimabbEquiv, + FSimabmbEquiv, }; - enum class MagicBasisTransform { + enum class MagicBasisTransform : std::uint8_t { Into, OutOf, }; - enum class EulerBasis { + enum class EulerBasis : std::uint8_t { U3 = 0, U321 = 1, U = 2, @@ -383,9 +386,9 @@ struct GateDecompositionPattern final ZSX = 11, }; - static constexpr auto sqrt2 = static_cast(1.4142135623730950488L); - static const matrix2x2 identityGate; - static const matrix2x2 hGate; + static constexpr auto SQRT2 = std::numbers::sqrt2_v; + static const matrix2x2 IDENTITY_GATE; + static const matrix2x2 H_GATE; static fp remEuclid(fp a, fp b) { auto r = std::fmod(a, b); @@ -408,14 +411,14 @@ struct GateDecompositionPattern final // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen static std::pair - self_adjoint_eigen_lower(rmatrix4x4 A) { + selfAdjointEigenLower(rmatrix4x4 a) { // rdiagonal4x4 S; // auto U = self_adjoint_evd(A, S); // rmatrix4x4 U; // jacobi_eigen_decomposition(A, U, S); - auto [U, S] = helpers::self_adjoint_evd(A); + auto [U, S] = helpers::selfAdjointEvd(a); // TODO: not in original code if (std::abs(U.determinant() + 1.0) < 1e-5) { @@ -429,31 +432,31 @@ struct GateDecompositionPattern final } static std::tuple - decompose_two_qubit_product_gate(matrix4x4 special_unitary) { - helpers::print(special_unitary, "SPECIAL_UNITARY"); + decomposeTwoQubitProductGate(matrix4x4 specialUnitary) { + helpers::print(specialUnitary, "SPECIAL_UNITARY"); // first quadrant - matrix2x2 r{{special_unitary(0, 0), special_unitary(0, 1)}, - {special_unitary(1, 0), special_unitary(1, 1)}}; - auto det_r = r.determinant(); - if (std::abs(det_r) < 0.1) { + matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, + {specialUnitary(1, 0), specialUnitary(1, 1)}}; + auto detR = r.determinant(); + if (std::abs(detR) < 0.1) { // third quadrant - r = matrix2x2{{special_unitary(2, 0), special_unitary(2, 1)}, - {special_unitary(3, 0), special_unitary(3, 1)}}; - det_r = r.determinant(); + r = matrix2x2{{specialUnitary(2, 0), specialUnitary(2, 1)}, + {specialUnitary(3, 0), specialUnitary(3, 1)}}; + detR = r.determinant(); } - std::cerr << "DET_R: " << det_r << '\n'; - if (std::abs(det_r) < 0.1) { + std::cerr << "DET_R: " << detR << '\n'; + if (std::abs(detR) < 0.1) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; } - r /= std::sqrt(det_r); + r /= std::sqrt(detR); helpers::print(r, "R"); // transpose with complex conjugate of each element - matrix2x2 r_t_conj = r.transpose().conjugate(); + matrix2x2 rTConj = r.transpose().conjugate(); - auto temp = helpers::kroneckerProduct(identityGate, r_t_conj); + auto temp = helpers::kroneckerProduct(IDENTITY_GATE, rTConj); helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 1)"); - temp = special_unitary * temp; + temp = specialUnitary * temp; helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 2)"); // [[a, b, c, d], @@ -461,27 +464,27 @@ struct GateDecompositionPattern final // [i, j, k, l], [i, k]] // [m, n, o, p]] matrix2x2 l{{temp(0, 0), temp(0, 2)}, {temp(2, 0), temp(2, 2)}}; - auto det_l = l.determinant(); - if (std::abs(det_l) < 0.9) { + auto detL = l.determinant(); + if (std::abs(detL) < 0.9) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: detL < 0.9"}; } - l /= std::sqrt(det_l); - auto phase = std::arg(det_l) / 2.; + l /= std::sqrt(detL); + auto phase = std::arg(detL) / 2.; return {l, r, phase}; } - static matrix4x4 magic_basis_transform(const matrix4x4& unitary, - MagicBasisTransform direction) { - const matrix4x4 B_NON_NORMALIZED{ + static matrix4x4 magicBasisTransform(const matrix4x4& unitary, + MagicBasisTransform direction) { + const matrix4x4 bNonNormalized{ {C_ONE, IM, C_ZERO, C_ZERO}, {C_ZERO, C_ZERO, IM, C_ONE}, {C_ZERO, C_ZERO, IM, C_M_ONE}, {C_ONE, M_IM, C_ZERO, C_ZERO}, }; - const matrix4x4 B_NON_NORMALIZED_DAGGER{ + const matrix4x4 bNonNormalizedDagger{ {qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.)}, {qfp(0., -0.5), C_ZERO, C_ZERO, qfp(0., 0.5)}, {C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO}, @@ -489,41 +492,41 @@ struct GateDecompositionPattern final }; helpers::print(unitary, "UNITARY in MAGIC BASIS TRANSFORM"); if (direction == MagicBasisTransform::OutOf) { - return B_NON_NORMALIZED_DAGGER * unitary * B_NON_NORMALIZED; + return bNonNormalizedDagger * unitary * bNonNormalized; } if (direction == MagicBasisTransform::Into) { - return B_NON_NORMALIZED * unitary * B_NON_NORMALIZED_DAGGER; + return bNonNormalized * unitary * bNonNormalizedDagger; } throw std::logic_error{"Unknown MagicBasisTransform direction!"}; } - static fp trace_to_fid(const qfp& x) { - auto x_abs = std::abs(x); - return (4.0 + x_abs * x_abs) / 20.0; + static fp traceToFid(const qfp& x) { + auto xAbs = std::abs(x); + return (4.0 + xAbs * xAbs) / 20.0; } - static fp closest_partial_swap(fp a, fp b, fp c) { + static fp closestPartialSwap(fp a, fp b, fp c) { auto m = (a + b + c) / 3.; auto [am, bm, cm] = std::array{a - m, b - m, c - m}; auto [ab, bc, ca] = std::array{a - b, b - c, c - a}; - return m + am * bm * cm * (6. + ab * ab + bc * bc + ca * ca) / 18.; + return m + (am * bm * cm * (6. + ab * ab + bc * bc + ca * ca) / 18.); } - static matrix2x2 rx_matrix(fp theta) { - auto half_theta = theta / 2.; - auto cos = qfp(std::cos(half_theta), 0.); - auto isin = qfp(0., -std::sin(half_theta)); + static matrix2x2 rxMatrix(fp theta) { + auto halfTheta = theta / 2.; + auto cos = qfp(std::cos(halfTheta), 0.); + auto isin = qfp(0., -std::sin(halfTheta)); return matrix2x2{{cos, isin}, {isin, cos}}; } - static matrix2x2 ry_matrix(fp theta) { - auto half_theta = theta / 2.; - auto cos = qfp(std::cos(half_theta), 0.); - auto sin = qfp(std::sin(half_theta), 0.); + static matrix2x2 ryMatrix(fp theta) { + auto halfTheta = theta / 2.; + auto cos = qfp(std::cos(halfTheta), 0.); + auto sin = qfp(std::sin(halfTheta), 0.); return matrix2x2{{cos, -sin}, {sin, cos}}; } - static matrix2x2 rz_matrix(fp theta) { + static matrix2x2 rzMatrix(fp theta) { return matrix2x2{{qfp{std::cos(theta / 2.), -std::sin(theta / 2.)}, 0}, {0, qfp{std::cos(theta / 2.), std::sin(theta / 2.)}}}; // auto ilam2 = qfp(0., 0.5 * theta); @@ -550,21 +553,21 @@ struct GateDecompositionPattern final {C_ZERO, C_ZERO, C_ZERO, {cosTheta, -sinTheta}}}; } - static std::array angles_from_unitary(const matrix2x2& matrix, - EulerBasis basis) { + static std::array anglesFromUnitary(const matrix2x2& matrix, + EulerBasis basis) { if (basis == EulerBasis::XYX) { - return params_xyx_inner(matrix); + return paramsXyxInner(matrix); } if (basis == EulerBasis::ZYZ) { - return params_zyz_inner(matrix); + return paramsZyzInner(matrix); } if (basis == EulerBasis::ZXZ) { - return params_zxz_inner(matrix); + return paramsZxzInner(matrix); } throw std::invalid_argument{"Unknown EulerBasis for angles_from_unitary"}; } - static std::array params_zyz_inner(const matrix2x2& matrix) { + static std::array paramsZyzInner(const matrix2x2& matrix) { auto detArg = std::arg(matrix.determinant()); auto phase = 0.5 * detArg; auto theta = @@ -576,13 +579,13 @@ struct GateDecompositionPattern final return {theta, phi, lam, phase}; } - static std::array params_zxz_inner(const matrix2x2& matrix) { - auto [theta, phi, lam, phase] = params_zyz_inner(matrix); - return {theta, phi + qc::PI / 2., lam - qc::PI / 2., phase}; + static std::array paramsZxzInner(const matrix2x2& matrix) { + auto [theta, phi, lam, phase] = paramsZyzInner(matrix); + return {theta, phi + (qc::PI / 2.), lam - (qc::PI / 2.), phase}; } - static std::array params_xyx_inner(const matrix2x2& matrix) { - auto mat_zyz = matrix2x2{ + static std::array paramsXyxInner(const matrix2x2& matrix) { + auto matZyz = matrix2x2{ {static_cast(0.5) * (matrix(0, 0) + matrix(0, 1) + matrix(1, 0) + matrix(1, 1)), static_cast(0.5) * @@ -592,14 +595,14 @@ struct GateDecompositionPattern final static_cast(0.5) * (matrix(0, 0) - matrix(0, 1) - matrix(1, 0) + matrix(1, 1))}, }; - auto [theta, phi, lam, phase] = params_zyz_inner(mat_zyz); - auto new_phi = mod2pi(phi + qc::PI, 0.); - auto new_lam = mod2pi(lam + qc::PI, 0.); + auto [theta, phi, lam, phase] = paramsZyzInner(matZyz); + auto newPhi = mod2pi(phi + qc::PI, 0.); + auto newLam = mod2pi(lam + qc::PI, 0.); return { theta, - new_phi, - new_lam, - phase + (new_phi + new_lam - phi - lam) / 2., + newPhi, + newLam, + phase + ((newPhi + newLam - phi - lam) / 2.), }; } @@ -613,19 +616,19 @@ struct GateDecompositionPattern final {qfp{0.5, -0.5}, qfp{0.5, 0.5}}}; } if (gate.type == qc::RX) { - return rx_matrix(gate.parameter[0]); + return rxMatrix(gate.parameter[0]); } if (gate.type == qc::RY) { - return ry_matrix(gate.parameter[0]); + return ryMatrix(gate.parameter[0]); } if (gate.type == qc::RZ) { - return rz_matrix(gate.parameter[0]); + return rzMatrix(gate.parameter[0]); } if (gate.type == qc::X) { return matrix2x2{{0, 1}, {1, 0}}; } if (gate.type == qc::I) { - return identityGate; + return IDENTITY_GATE; } if (gate.type == qc::H) { static constexpr fp SQRT2_2 = static_cast( @@ -640,26 +643,26 @@ struct GateDecompositionPattern final static matrix4x4 getTwoQubitMatrix(const QubitGateSequence::Gate& gate) { using helpers::kroneckerProduct; - if (gate.qubit_id.empty()) { - return kroneckerProduct(identityGate, identityGate); + if (gate.qubitId.empty()) { + return kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); } - if (gate.qubit_id.size() == 1) { - if (gate.qubit_id[0] == 0) { - return kroneckerProduct(identityGate, getSingleQubitMatrix(gate)); + if (gate.qubitId.size() == 1) { + if (gate.qubitId[0] == 0) { + return kroneckerProduct(IDENTITY_GATE, getSingleQubitMatrix(gate)); } - if (gate.qubit_id[0] == 1) { - return kroneckerProduct(getSingleQubitMatrix(gate), identityGate); + if (gate.qubitId[0] == 1) { + return kroneckerProduct(getSingleQubitMatrix(gate), IDENTITY_GATE); } throw std::logic_error{"Invalid qubit ID in getTwoQubitMatrix"}; } - if (gate.qubit_id.size() == 2) { + if (gate.qubitId.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubit_id == llvm::SmallVector{1, 0}) { + if (gate.qubitId == llvm::SmallVector{1, 0}) { return matrix4x4{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubit_id == llvm::SmallVector{0, 1}) { + if (gate.qubitId == llvm::SmallVector{0, 1}) { return matrix4x4{ {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } @@ -688,31 +691,31 @@ struct GateDecompositionPattern final fp a; fp b; fp c; - fp global_phase; - matrix2x2 K1l; - matrix2x2 K2l; - matrix2x2 K1r; - matrix2x2 K2r; + fp globalPhase; + matrix2x2 k1l; + matrix2x2 k2l; + matrix2x2 k1r; + matrix2x2 k2r; Specialization specialization; - EulerBasis default_euler_basis; - std::optional requested_fidelity; - fp calculated_fidelity; - matrix4x4 unitary_matrix; + EulerBasis defaultEulerBasis; + std::optional requestedFidelity; + fp calculatedFidelity; + matrix4x4 unitaryMatrix; static TwoQubitWeylDecomposition - new_inner(matrix4x4 unitary_matrix, std::optional fidelity, - std::optional _specialization) { - auto& u = unitary_matrix; - auto det_u = u.determinant(); - std::cerr << "DET_U: " << det_u << '\n'; - auto det_pow = std::pow(det_u, static_cast(-0.25)); - u *= det_pow; + newInner(matrix4x4 unitaryMatrix, std::optional fidelity, + std::optional specialization) { + auto& u = unitaryMatrix; + auto detU = u.determinant(); + std::cerr << "DET_U: " << detU << '\n'; + auto detPow = std::pow(detU, static_cast(-0.25)); + u *= detPow; helpers::print(u, "U", true); - auto global_phase = std::arg(det_u) / 4.; - auto u_p = magic_basis_transform(u, MagicBasisTransform::OutOf); - helpers::print(u_p, "U_P", true); - matrix4x4 m2 = u_p.transpose() * u_p; - auto default_euler_basis = EulerBasis::ZYZ; + auto globalPhase = std::arg(detU) / 4.; + auto uP = magicBasisTransform(u, MagicBasisTransform::OutOf); + helpers::print(uP, "U_P", true); + matrix4x4 m2 = uP.transpose() * uP; + auto defaultEulerBasis = EulerBasis::ZYZ; helpers::print(m2, "M2", true); std::cerr << "DET_U after division: " << u.determinant() << '\n'; @@ -738,35 +741,35 @@ struct GateDecompositionPattern final matrix4x4 p = matrix4x4::Zero(); for (int i = 0; i < 100; ++i) { - fp rand_a; - fp rand_b; + fp randA{}; + fp randB{}; // For debugging the algorithm use the same RNG values from the // previous Python implementation for the first random trial. // In most cases this loop only executes a single iteration and // using the same rng values rules out possible RNG differences // as the root cause of a test failure if (i == 0) { - rand_a = 1.2602066112249388; - rand_b = 0.22317849046722027; + randA = 1.2602066112249388; + randB = 0.22317849046722027; } else { - rand_a = dist(state); - rand_b = dist(state); + randA = dist(state); + randB = dist(state); } - rmatrix4x4 m2_real = rand_a * m2.real() + rand_b * m2.imag(); - rmatrix4x4 p_inner_real = self_adjoint_eigen_lower(m2_real).first; - matrix4x4 p_inner = p_inner_real; - diagonal4x4 d_inner = (p_inner.transpose() * m2 * p_inner).diagonal(); + rmatrix4x4 m2Real = randA * m2.real() + randB * m2.imag(); + rmatrix4x4 pInnerReal = selfAdjointEigenLower(m2Real).first; + matrix4x4 pInner = pInnerReal; + diagonal4x4 dInner = (pInner.transpose() * m2 * pInner).diagonal(); - helpers::print(d_inner, "D_INNER", true); - helpers::print(p_inner, "P_INNER", true); - matrix4x4 diag_d = d_inner.asDiagonal(); + helpers::print(dInner, "D_INNER", true); + helpers::print(pInner, "P_INNER", true); + matrix4x4 diagD = dInner.asDiagonal(); - matrix4x4 compare = p_inner * diag_d * p_inner.transpose(); + matrix4x4 compare = pInner * diagD * pInner.transpose(); helpers::print(compare, "COMPARE"); found = (compare - m2).cwiseAbs().cwiseLessOrEqual(1.0e-13).all(); if (found) { - p = p_inner; - d = d_inner; + p = pInner; + d = dInner; break; } } @@ -774,13 +777,13 @@ struct GateDecompositionPattern final throw std::runtime_error{ "TwoQubitWeylDecomposition: failed to diagonalize M2."}; } - rdiagonal4x4 d_real = -1.0 * d.cwiseArg() / 2.0; - helpers::print(d_real, "D_REAL", true); - d_real(3) = -d_real(0) - d_real(1) - d_real(2); - std::array cs; - for (std::size_t i = 0; i < cs.size(); ++i) { - assert(i < d_real.size()); - cs[i] = remEuclid((d_real(i) + d_real(3)) / 2.0, qc::TAU); + rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; + helpers::print(dReal, "D_REAL", true); + dReal(3) = -dReal(0) - dReal(1) - dReal(2); + std::array cs{}; + for (int i = 0; i < static_cast(cs.size()); ++i) { + assert(i < dReal.size()); + cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); } helpers::print(cs, "CS", true); decltype(cs) cstemp; @@ -788,24 +791,23 @@ struct GateDecompositionPattern final auto tmp = remEuclid(x, qc::PI_2); return std::min(tmp, qc::PI_2 - tmp); }); - std::array order{ + std::array order{ 2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector // order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); std::tie(order[0], order[1], order[2]) = std::tuple{order[1], order[2], order[0]}; - helpers::print(order, "ORDER", true); std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - std::tie(d_real(0), d_real(1), d_real(2)) = - std::tuple{d_real(order[0]), d_real(order[1]), d_real(order[2])}; - helpers::print(d_real, "D_REAL (sorted)", true); + std::tie(dReal(0), dReal(1), dReal(2)) = + std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; + helpers::print(dReal, "D_REAL (sorted)", true); // swap columns of p according to order - auto p_orig = p; - for (std::size_t i = 0; i < order.size(); ++i) { - p.col(i) = p_orig.col(order[i]); + auto pOrig = p; + for (int i = 0; i < static_cast(order.size()); ++i) { + p.col(i) = pOrig.col(order[i]); } if (p.determinant().real() < 0.0) { @@ -813,31 +815,30 @@ struct GateDecompositionPattern final p.col(lastColumnIndex) = -p.col(lastColumnIndex); } - matrix4x4 temp = d_real.asDiagonal(); + matrix4x4 temp = dReal.asDiagonal(); temp *= IM; temp = temp.exp(); helpers::print(temp, "TEMP"); helpers::print(p, "P", true); - auto k1 = - magic_basis_transform(u_p * p * temp, MagicBasisTransform::Into); - auto k2 = magic_basis_transform(p.transpose(), MagicBasisTransform::Into); + auto k1 = magicBasisTransform(uP * p * temp, MagicBasisTransform::Into); + auto k2 = magicBasisTransform(p.transpose(), MagicBasisTransform::Into); - auto [K1l, K1r, phase_l] = decompose_two_qubit_product_gate(k1); - auto [K2l, K2r, phase_r] = decompose_two_qubit_product_gate(k2); - global_phase += phase_l + phase_r; + auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); + auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); + globalPhase += phase_l + phase_r; // Flip into Weyl chamber if (cs[0] > qc::PI_2) { cs[0] -= 3.0 * qc::PI_2; K1l = K1l * IPY; K1r = K1r * IPY; - global_phase += qc::PI_2; + globalPhase += qc::PI_2; } if (cs[1] > qc::PI_2) { cs[1] -= 3.0 * qc::PI_2; K1l = K1l * IPX; K1r = K1r * IPX; - global_phase += qc::PI_2; + globalPhase += qc::PI_2; } auto conjs = 0; if (cs[0] > qc::PI_4) { @@ -845,41 +846,41 @@ struct GateDecompositionPattern final K1l = K1l * IPY; K2r = IPY * K2r; conjs += 1; - global_phase -= qc::PI_2; + globalPhase -= qc::PI_2; } if (cs[1] > qc::PI_4) { cs[1] = qc::PI_2 - cs[1]; K1l = K1l * IPX; K2r = IPX * K2r; conjs += 1; - global_phase += qc::PI_2; + globalPhase += qc::PI_2; if (conjs == 1) { - global_phase -= qc::PI; + globalPhase -= qc::PI; } } if (cs[2] > qc::PI_2) { cs[2] -= 3.0 * qc::PI_2; K1l = K1l * IPZ; K1r = K1r * IPZ; - global_phase += qc::PI_2; + globalPhase += qc::PI_2; if (conjs == 1) { - global_phase -= qc::PI; + globalPhase -= qc::PI; } } if (conjs == 1) { cs[2] = qc::PI_2 - cs[2]; K1l = K1l * IPZ; K2r = IPZ * K2r; - global_phase += qc::PI_2; + globalPhase += qc::PI_2; } if (cs[2] > qc::PI_4) { cs[2] -= qc::PI_2; K1l = K1l * IPZ; K1r = K1r * IPZ; - global_phase -= qc::PI_2; + globalPhase -= qc::PI_2; } auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); - auto is_close = [&](fp ap, fp bp, fp cp) -> bool { + auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; auto dc = c - cp; @@ -887,78 +888,84 @@ struct GateDecompositionPattern final qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); if (fidelity) { - return trace_to_fid(tr) >= *fidelity; + return traceToFid(tr) >= *fidelity; } return false; }; - auto closest_abc = closest_partial_swap(a, b, c); - auto closest_ab_minus_c = closest_partial_swap(a, b, -c); - auto flipped_from_original = false; + auto closestAbc = closestPartialSwap(a, b, c); + auto closestAbMinusC = closestPartialSwap(a, b, -c); + auto flippedFromOriginal = false; - auto get_default_specialzation = [&]() { - if (is_close(0., 0., 0.)) { + auto getDefaultSpecialzation = [&]() { + if (isClose(0., 0., 0.)) { return Specialization::IdEquiv; - } else if (is_close(qc::PI_4, qc::PI_4, qc::PI_4) || - is_close(qc::PI_4, qc::PI_4, -qc::PI_4)) { + } + if (isClose(qc::PI_4, qc::PI_4, qc::PI_4) || + isClose(qc::PI_4, qc::PI_4, -qc::PI_4)) { return Specialization::SWAPEquiv; - } else if (is_close(closest_abc, closest_abc, closest_abc)) { + } + if (isClose(closestAbc, closestAbc, closestAbc)) { return Specialization::PartialSWAPEquiv; - } else if (is_close(closest_ab_minus_c, closest_ab_minus_c, - -closest_ab_minus_c)) { + } + if (isClose(closestAbMinusC, closestAbMinusC, -closestAbMinusC)) { return Specialization::PartialSWAPFlipEquiv; - } else if (is_close(a, 0., 0.)) { + } + if (isClose(a, 0., 0.)) { return Specialization::ControlledEquiv; - } else if (is_close(qc::PI_4, qc::PI_4, c)) { + } + if (isClose(qc::PI_4, qc::PI_4, c)) { return Specialization::MirrorControlledEquiv; - } else if (is_close((a + b) / 2., (a + b) / 2., c)) { - return Specialization::fSimaabEquiv; - } else if (is_close(a, (b + c) / 2., (b + c) / 2.)) { - return Specialization::fSimabbEquiv; - } else if (is_close(a, (b - c) / 2., (c - b) / 2.)) { - return Specialization::fSimabmbEquiv; - } else { - return Specialization::General; } + if (isClose((a + b) / 2., (a + b) / 2., c)) { + return Specialization::FSimaabEquiv; + } + if (isClose(a, (b + c) / 2., (b + c) / 2.)) { + return Specialization::FSimabbEquiv; + } + if (isClose(a, (b - c) / 2., (c - b) / 2.)) { + return Specialization::FSimabmbEquiv; + } + return Specialization::General; }; - auto specialization = - _specialization.value_or(get_default_specialzation()); + auto actualSpecialization = + specialization.value_or(getDefaultSpecialzation()); TwoQubitWeylDecomposition general{ - .a=a, - .b=b, - .c=c, - .global_phase=global_phase, - .K1l=K1l, - .K2l=K2l, - .K1r=K1r, - .K2r=K2r, - .specialization=Specialization::General, - .default_euler_basis=default_euler_basis, - .requested_fidelity=fidelity, - .calculated_fidelity=-1.0, - .unitary_matrix=unitary_matrix, + .a = a, + .b = b, + .c = c, + .globalPhase = globalPhase, + .k1l = K1l, + .k2l = K2l, + .k1r = K1r, + .k2r = K2r, + .specialization = Specialization::General, + .defaultEulerBasis = defaultEulerBasis, + .requestedFidelity = fidelity, + .calculatedFidelity = -1.0, + .unitaryMatrix = unitaryMatrix, }; - auto get_specialized_decomposition = [&]() { + auto getSpecializedDecomposition = [&]() { // :math:`U \sim U_d(0,0,0) \sim Id` // // This gate binds 0 parameters, we make it canonical by // setting // :math:`K2_l = Id` , :math:`K2_r = Id`. - if (specialization == Specialization::IdEquiv) { + if (actualSpecialization == Specialization::IdEquiv) { return TwoQubitWeylDecomposition{ - .a=0., - .b=0., - .c=0., - .global_phase=general.global_phase, - .K1l=general.K1l * general.K2l, - .K2l=identityGate, - .K1r=general.K1r * general.K2r, - .K2r=identityGate, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = 0., + .b = 0., + .c = 0., + .globalPhase = general.globalPhase, + .k1l = general.k1l * general.k2l, + .k2l = IDENTITY_GATE, + .k1r = general.k1r * general.k2r, + .k2r = IDENTITY_GATE, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, @@ -967,41 +974,40 @@ struct GateDecompositionPattern final // This gate binds 0 parameters, we make it canonical by // setting // :math:`K2_l = Id` , :math:`K2_r = Id`. - if (specialization == Specialization::SWAPEquiv) { + if (actualSpecialization == Specialization::SWAPEquiv) { if (c > 0.) { return TwoQubitWeylDecomposition{ - .a=qc::PI_4, - .b=qc::PI_4, - .c=qc::PI_4, - .global_phase=general.global_phase, - .K1l=general.K1l * general.K2r, - .K2l=identityGate, - .K1r=general.K1r * general.K2l, - .K2r=identityGate, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, - }; - } else { - flipped_from_original = true; - return TwoQubitWeylDecomposition{ - .a=qc::PI_4, - .b=qc::PI_4, - .c=qc::PI_4, - .global_phase=global_phase + qc::PI_2, - .K1l=general.K1l * IPZ * general.K2r, - .K2l=identityGate, - .K1r=general.K1r * IPZ * general.K2l, - .K2r=identityGate, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = qc::PI_4, + .b = qc::PI_4, + .c = qc::PI_4, + .globalPhase = general.globalPhase, + .k1l = general.k1l * general.k2r, + .k2l = IDENTITY_GATE, + .k1r = general.k1r * general.k2l, + .k2r = IDENTITY_GATE, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } + flippedFromOriginal = true; + return TwoQubitWeylDecomposition{ + .a = qc::PI_4, + .b = qc::PI_4, + .c = qc::PI_4, + .globalPhase = globalPhase + qc::PI_2, + .k1l = general.k1l * IPZ * general.k2r, + .k2l = IDENTITY_GATE, + .k1r = general.k1r * IPZ * general.k2l, + .k2r = IDENTITY_GATE, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, + }; } // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim // \text{SWAP}^\alpha` @@ -1009,24 +1015,24 @@ struct GateDecompositionPattern final // This gate binds 3 parameters, we make it canonical by setting: // // :math:`K2_l = Id`. - if (specialization == Specialization::PartialSWAPEquiv) { - auto closest = closest_partial_swap(a, b, c); - auto k2l_dag = general.K2l.transpose().conjugate(); + if (actualSpecialization == Specialization::PartialSWAPEquiv) { + auto closest = closestPartialSwap(a, b, c); + auto k2lDag = general.k2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ - .a=closest, - .b=closest, - .c=closest, - .global_phase=general.global_phase, - .K1l=general.K1l * general.K2l, - .K2l=identityGate, - .K1r=general.K1r * general.K2l, - .K2r=k2l_dag * general.K2r, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = closest, + .b = closest, + .c = closest, + .globalPhase = general.globalPhase, + .k1l = general.k1l * general.k2l, + .k2l = IDENTITY_GATE, + .k1r = general.k1r * general.k2l, + .k2r = k2lDag * general.k2r, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim @@ -1039,24 +1045,24 @@ struct GateDecompositionPattern final // This gate binds 3 parameters, we make it canonical by setting: // // :math:`K2_l = Id` - if (specialization == Specialization::PartialSWAPFlipEquiv) { - auto closest = closest_partial_swap(a, b, -c); - auto k2l_dag = general.K2l.transpose().conjugate(); + if (actualSpecialization == Specialization::PartialSWAPFlipEquiv) { + auto closest = closestPartialSwap(a, b, -c); + auto k2lDag = general.k2l.transpose().conjugate(); return TwoQubitWeylDecomposition{ - .a=closest, - .b=closest, - .c=-closest, - .global_phase=general.global_phase, - .K1l=general.K1l * general.K2l, - .K2l=identityGate, - .K1r=general.K1r * IPZ * general.K2l * IPZ, - .K2r=IPZ * k2l_dag * IPZ * general.K2r, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = closest, + .b = closest, + .c = -closest, + .globalPhase = general.globalPhase, + .k1l = general.k1l * general.k2l, + .k2l = IDENTITY_GATE, + .k1r = general.k1r * IPZ * general.k2l * IPZ, + .k2r = IPZ * k2lDag * IPZ * general.k2r, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` @@ -1065,26 +1071,26 @@ struct GateDecompositionPattern final // // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . - if (specialization == Specialization::ControlledEquiv) { - auto euler_basis = EulerBasis::XYX; + if (actualSpecialization == Specialization::ControlledEquiv) { + auto eulerBasis = EulerBasis::XYX; auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l, euler_basis); + anglesFromUnitary(general.k2l, eulerBasis); auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r, euler_basis); + anglesFromUnitary(general.k2r, eulerBasis); return TwoQubitWeylDecomposition{ - .a=a, - .b=0., - .c=0., - .global_phase=global_phase + k2lphase + k2rphase, - .K1l=general.K1l * rx_matrix(k2lphi), - .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), - .K1r=general.K1r * rx_matrix(k2rphi), - .K2r=ry_matrix(k2rtheta) * rx_matrix(k2rlambda), - .specialization=specialization, - .default_euler_basis=euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = a, + .b = 0., + .c = 0., + .globalPhase = globalPhase + k2lphase + k2rphase, + .k1l = general.k1l * rxMatrix(k2lphi), + .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), + .k1r = general.k1r * rxMatrix(k2rphi), + .k2r = ryMatrix(k2rtheta) * rxMatrix(k2rlambda), + .specialization = actualSpecialization, + .defaultEulerBasis = eulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot @@ -1094,25 +1100,25 @@ struct GateDecompositionPattern final // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = // Ry(\theta_r)\cdot Rz(\lambda_r)` - if (specialization == Specialization::MirrorControlledEquiv) { + if (actualSpecialization == Specialization::MirrorControlledEquiv) { auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l, EulerBasis::ZYZ); + anglesFromUnitary(general.k2l, EulerBasis::ZYZ); auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = - angles_from_unitary(general.K2r, EulerBasis::ZYZ); + anglesFromUnitary(general.k2r, EulerBasis::ZYZ); return TwoQubitWeylDecomposition{ - .a=qc::PI_4, - .b=qc::PI_4, - .c=c, - .global_phase=global_phase + k2lphase + k2rphase, - .K1l=general.K1l * rz_matrix(k2rphi), - .K2l=ry_matrix(k2ltheta) * rz_matrix(k2llambda), - .K1r=general.K1r * rz_matrix(k2lphi), - .K2r=ry_matrix(k2rtheta) * rz_matrix(k2rlambda), - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = qc::PI_4, + .b = qc::PI_4, + .c = c, + .globalPhase = globalPhase + k2lphase + k2rphase, + .k1l = general.k1l * rzMatrix(k2rphi), + .k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda), + .k1r = general.k1r * rzMatrix(k2lphi), + .k2r = ryMatrix(k2rtheta) * rzMatrix(k2rlambda), + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` @@ -1120,23 +1126,23 @@ struct GateDecompositionPattern final // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. - if (specialization == Specialization::fSimaabEquiv) { + if (actualSpecialization == Specialization::FSimaabEquiv) { auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l, EulerBasis::ZYZ); + anglesFromUnitary(general.k2l, EulerBasis::ZYZ); return TwoQubitWeylDecomposition{ - .a=(a + b) / 2., - .b=(a + b) / 2., - .c=c, - .global_phase=global_phase + k2lphase, - .K1l=general.K1l * rz_matrix(k2lphi), - .K2l=ry_matrix(k2ltheta) * rz_matrix(k2llambda), - .K1r=general.K1r * rz_matrix(k2lphi), - .K2r=rz_matrix(-k2lphi) * general.K2r, - .specialization=specialization, - .default_euler_basis=general.default_euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = (a + b) / 2., + .b = (a + b) / 2., + .c = c, + .globalPhase = globalPhase + k2lphase, + .k1l = general.k1l * rzMatrix(k2lphi), + .k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda), + .k1r = general.k1r * rzMatrix(k2lphi), + .k2r = rzMatrix(-k2lphi) * general.k2r, + .specialization = actualSpecialization, + .defaultEulerBasis = general.defaultEulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` @@ -1144,24 +1150,24 @@ struct GateDecompositionPattern final // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - if (specialization == Specialization::fSimabbEquiv) { - auto euler_basis = EulerBasis::XYX; + if (actualSpecialization == Specialization::FSimabbEquiv) { + auto eulerBasis = EulerBasis::XYX; auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l, euler_basis); + anglesFromUnitary(general.k2l, eulerBasis); return TwoQubitWeylDecomposition{ - .a=a, - .b=(b + c) / 2., - .c=(b + c) / 2., - .global_phase=global_phase + k2lphase, - .K1l=general.K1l * rx_matrix(k2lphi), - .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), - .K1r=general.K1r * rx_matrix(k2lphi), - .K2r=rx_matrix(-k2lphi) * general.K2r, - .specialization=specialization, - .default_euler_basis=euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = a, + .b = (b + c) / 2., + .c = (b + c) / 2., + .globalPhase = globalPhase + k2lphase, + .k1l = general.k1l * rxMatrix(k2lphi), + .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), + .k1r = general.k1r * rxMatrix(k2lphi), + .k2r = rxMatrix(-k2lphi) * general.k2r, + .specialization = actualSpecialization, + .defaultEulerBasis = eulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` @@ -1169,40 +1175,40 @@ struct GateDecompositionPattern final // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - if (specialization == Specialization::fSimabmbEquiv) { - auto euler_basis = EulerBasis::XYX; + if (actualSpecialization == Specialization::FSimabmbEquiv) { + auto eulerBasis = EulerBasis::XYX; auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - angles_from_unitary(general.K2l, euler_basis); + anglesFromUnitary(general.k2l, eulerBasis); return TwoQubitWeylDecomposition{ - .a=a, - .b=(b - c) / 2., - .c=-((b - c) / 2.), - .global_phase=global_phase + k2lphase, - .K1l=general.K1l * rx_matrix(k2lphi), - .K2l=ry_matrix(k2ltheta) * rx_matrix(k2llambda), - .K1r=general.K1r * IPZ * rx_matrix(k2lphi) * IPZ, - .K2r=IPZ * rx_matrix(-k2lphi) * IPZ * general.K2r, - .specialization=specialization, - .default_euler_basis=euler_basis, - .requested_fidelity=general.requested_fidelity, - .calculated_fidelity=general.calculated_fidelity, - .unitary_matrix=general.unitary_matrix, + .a = a, + .b = (b - c) / 2., + .c = -((b - c) / 2.), + .globalPhase = globalPhase + k2lphase, + .k1l = general.k1l * rxMatrix(k2lphi), + .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), + .k1r = general.k1r * IPZ * rxMatrix(k2lphi) * IPZ, + .k2r = IPZ * rxMatrix(-k2lphi) * IPZ * general.k2r, + .specialization = actualSpecialization, + .defaultEulerBasis = eulerBasis, + .requestedFidelity = general.requestedFidelity, + .calculatedFidelity = general.calculatedFidelity, + .unitaryMatrix = general.unitaryMatrix, }; } // U has no special symmetry. // // This gate binds all 6 possible parameters, so there is no need to // make the single-qubit pre-/post-gates canonical. - if (specialization == Specialization::General) { + if (actualSpecialization == Specialization::General) { return general; } throw std::logic_error{"Unknown specialization"}; }; - TwoQubitWeylDecomposition specialized = get_specialized_decomposition(); + TwoQubitWeylDecomposition specialized = getSpecializedDecomposition(); - auto get_tr = [&]() { - if (flipped_from_original) { + auto getTr = [&]() { + if (flippedFromOriginal) { auto [da, db, dc] = std::array{ qc::PI_2 - a - specialized.a, b - specialized.b, @@ -1211,26 +1217,25 @@ struct GateDecompositionPattern final return static_cast(4.) * qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); - } else { - auto [da, db, dc] = std::array{a - specialized.a, b - specialized.b, - c - specialized.c}; - return static_cast(4.) * - qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); } + auto [da, db, dc] = + std::array{a - specialized.a, b - specialized.b, c - specialized.c}; + return static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); }; - auto tr = get_tr(); - specialized.calculated_fidelity = trace_to_fid(tr); - if (specialized.requested_fidelity) { - if (specialized.calculated_fidelity + 1.0e-13 < - *specialized.requested_fidelity) { + auto tr = getTr(); + specialized.calculatedFidelity = traceToFid(tr); + if (specialized.requestedFidelity) { + if (specialized.calculatedFidelity + 1.0e-13 < + *specialized.requestedFidelity) { throw std::runtime_error{ "Specialization: {:?} calculated fidelity: {} is worse than " "requested fidelity: {}", }; } } - specialized.global_phase += std::arg(tr); + specialized.globalPhase += std::arg(tr); return specialized; } }; @@ -1238,11 +1243,11 @@ struct GateDecompositionPattern final static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; struct TwoQubitBasisDecomposer { - QubitGateSequence::Gate basis_gate; - fp basis_fidelity; - EulerBasis euler_basis; - TwoQubitWeylDecomposition basis_decomposer; - bool super_controlled; + QubitGateSequence::Gate basisGate; + fp basisFidelity; + EulerBasis eulerBasis; + TwoQubitWeylDecomposition basisDecomposer; + bool superControlled; matrix2x2 u0l; matrix2x2 u0r; matrix2x2 u1l; @@ -1265,12 +1270,12 @@ struct GateDecompositionPattern final public: static TwoQubitBasisDecomposer - new_inner(OneQubitGateSequence::Gate basis_gate = {.type = qc::X, - .parameter = {}, - .qubit_id = {0, 1}}, - fp basis_fidelity = 1.0, EulerBasis euler_basis = EulerBasis::ZYZ) { - auto relative_eq = [](auto&& lhs, auto&& rhs, auto&& epsilon, - auto&& max_relative) { + newInner(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, + .parameter = {}, + .qubitId = {0, 1}}, + fp basisFidelity = 1.0, EulerBasis eulerBasis = EulerBasis::ZYZ) { + auto relativeEq = [](auto&& lhs, auto&& rhs, auto&& epsilon, + auto&& maxRelative) { // Handle same infinities if (lhs == rhs) { return true; @@ -1281,55 +1286,55 @@ struct GateDecompositionPattern final return false; } - auto abs_diff = std::abs(lhs - rhs); + auto absDiff = std::abs(lhs - rhs); // For when the numbers are really close together - if (abs_diff <= epsilon) { + if (absDiff <= epsilon) { return true; } - auto abs_lhs = std::abs(lhs); - auto abs_rhs = std::abs(rhs); - if (abs_rhs > abs_lhs) { - return abs_diff <= abs_rhs * max_relative; + auto absLhs = std::abs(lhs); + auto absRhs = std::abs(rhs); + if (absRhs > absLhs) { + return absDiff <= absRhs * maxRelative; } - return abs_diff <= abs_lhs * max_relative; + return absDiff <= absLhs * maxRelative; }; - constexpr auto FRAC_1_SQRT_2 = + constexpr auto frac1Sqrt2 = static_cast(0.707106781186547524400844362104849039); - const auto K12R_ARR = matrix2x2{ - {qfp(0., FRAC_1_SQRT_2), qfp(FRAC_1_SQRT_2, 0.)}, - {qfp(-FRAC_1_SQRT_2, 0.), qfp(0., -FRAC_1_SQRT_2)}, + const auto k12RArr = matrix2x2{ + {qfp(0., frac1Sqrt2), qfp(frac1Sqrt2, 0.)}, + {qfp(-frac1Sqrt2, 0.), qfp(0., -frac1Sqrt2)}, }; - const auto K12L_ARR = matrix2x2{ + const auto k12LArr = matrix2x2{ {qfp(0.5, 0.5), qfp(0.5, 0.5)}, {qfp(-0.5, 0.5), qfp(0.5, -0.5)}, }; - auto basis_decomposer = TwoQubitWeylDecomposition::new_inner( - getTwoQubitMatrix(basis_gate), DEFAULT_FIDELITY, std::nullopt); - auto super_controlled = - relative_eq(basis_decomposer.a, qc::PI_4, - std::numeric_limits::epsilon(), 1e-09) && - relative_eq(basis_decomposer.c, 0.0, - std::numeric_limits::epsilon(), 1e-09); + auto basisDecomposer = TwoQubitWeylDecomposition::newInner( + getTwoQubitMatrix(basisGate), DEFAULT_FIDELITY, std::nullopt); + auto superControlled = + relativeEq(basisDecomposer.a, qc::PI_4, + std::numeric_limits::epsilon(), 1e-09) && + relativeEq(basisDecomposer.c, 0.0, std::numeric_limits::epsilon(), + 1e-09); // Create some useful matrices U1, U2, U3 are equivalent to the basis, // expand as Ui = Ki1.Ubasis.Ki2 - auto b = basis_decomposer.b; + auto b = basisDecomposer.b; auto temp = qfp(0.5, -0.5); auto k11l = matrix2x2{ {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * -(std::exp(qfp(0., b)))}}; - auto k11r = matrix2x2{{FRAC_1_SQRT_2 * std::exp((IM * qfp(0., -b))), - FRAC_1_SQRT_2 * -std::exp(qfp(0., -b))}, - {FRAC_1_SQRT_2 * std::exp(qfp(0., b)), - FRAC_1_SQRT_2 * (M_IM * std::exp(qfp(0., b)))}}; - auto k32l_k21l = matrix2x2{{FRAC_1_SQRT_2 * std::cos(qfp(1., (2. * b))), - FRAC_1_SQRT_2 * (IM * std::sin((2. * b)))}, - {FRAC_1_SQRT_2 * (IM * std::sin(2. * b)), - FRAC_1_SQRT_2 * qfp(1., -std::cos(2. * b))}}; + auto k11r = matrix2x2{{frac1Sqrt2 * std::exp((IM * qfp(0., -b))), + frac1Sqrt2 * -std::exp(qfp(0., -b))}, + {frac1Sqrt2 * std::exp(qfp(0., b)), + frac1Sqrt2 * (M_IM * std::exp(qfp(0., b)))}}; + auto k32lK21l = matrix2x2{{frac1Sqrt2 * std::cos(qfp(1., (2. * b))), + frac1Sqrt2 * (IM * std::sin((2. * b)))}, + {frac1Sqrt2 * (IM * std::sin(2. * b)), + frac1Sqrt2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); auto k21r = matrix2x2{ {temp * (M_IM * std::exp(qfp(0., -2. * b))), @@ -1337,16 +1342,16 @@ struct GateDecompositionPattern final {temp * (IM * std::exp(qfp(0., 2. * b))), temp * std::exp(qfp(0., 2. * b))}, }; - const auto K22L_ARR = matrix2x2{ - {qfp(FRAC_1_SQRT_2, 0.), qfp(-FRAC_1_SQRT_2, 0.)}, - {qfp(FRAC_1_SQRT_2, 0.), qfp(FRAC_1_SQRT_2, 0.)}, + const auto k22LArr = matrix2x2{ + {qfp(frac1Sqrt2, 0.), qfp(-frac1Sqrt2, 0.)}, + {qfp(frac1Sqrt2, 0.), qfp(frac1Sqrt2, 0.)}, }; - const auto K22R_ARR = matrix2x2{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; + const auto k22RArr = matrix2x2{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; auto k31l = matrix2x2{ - {FRAC_1_SQRT_2 * std::exp(qfp(0., -b)), - FRAC_1_SQRT_2 * std::exp(qfp(0., -b))}, - {FRAC_1_SQRT_2 * -std::exp(qfp(0., b)), - FRAC_1_SQRT_2 * std::exp(qfp(0., b))}, + {frac1Sqrt2 * std::exp(qfp(0., -b)), + frac1Sqrt2 * std::exp(qfp(0., -b))}, + {frac1Sqrt2 * -std::exp(qfp(0., b)), + frac1Sqrt2 * std::exp(qfp(0., b))}, }; auto k31r = matrix2x2{ {IM * std::exp(qfp(0., b)), C_ZERO}, @@ -1358,81 +1363,81 @@ struct GateDecompositionPattern final {temp * (M_IM * std::exp(qfp(0., b))), temp * (M_IM * std::exp(qfp(0., -b)))}, }; - auto k1ld = basis_decomposer.K1l.transpose().conjugate(); - auto k1rd = basis_decomposer.K1r.transpose().conjugate(); - auto k2ld = basis_decomposer.K2l.transpose().conjugate(); - auto k2rd = basis_decomposer.K2r.transpose().conjugate(); + auto k1ld = basisDecomposer.k1l.transpose().conjugate(); + auto k1rd = basisDecomposer.k1r.transpose().conjugate(); + auto k2ld = basisDecomposer.k2l.transpose().conjugate(); + auto k2rd = basisDecomposer.k2r.transpose().conjugate(); // Pre-build the fixed parts of the matrices used in 3-part // decomposition auto u0l = k31l * k1ld; auto u0r = k31r * k1rd; - auto u1l = k2ld * k32l_k21l * k1ld; + auto u1l = k2ld * k32lK21l * k1ld; auto u1ra = k2rd * k32r; auto u1rb = k21r * k1rd; - auto u2la = k2ld * K22L_ARR; + auto u2la = k2ld * k22LArr; auto u2lb = k11l * k1ld; - auto u2ra = k2rd * K22R_ARR; + auto u2ra = k2rd * k22RArr; auto u2rb = k11r * k1rd; - auto u3l = k2ld * K12L_ARR; - auto u3r = k2rd * K12R_ARR; + auto u3l = k2ld * k12LArr; + auto u3r = k2rd * k12RArr; // Pre-build the fixed parts of the matrices used in the 2-part // decomposition - auto q0l = K12L_ARR.transpose().conjugate() * k1ld; - auto q0r = K12R_ARR.transpose().conjugate() * IPZ * k1rd; + auto q0l = k12LArr.transpose().conjugate() * k1ld; + auto q0r = k12RArr.transpose().conjugate() * IPZ * k1rd; auto q1la = k2ld * k11l.transpose().conjugate(); auto q1lb = k11l * k1ld; auto q1ra = k2rd * IPZ * k11r.transpose().conjugate(); auto q1rb = k11r * k1rd; - auto q2l = k2ld * K12L_ARR; - auto q2r = k2rd * K12R_ARR; + auto q2l = k2ld * k12LArr; + auto q2r = k2rd * k12RArr; return TwoQubitBasisDecomposer{ - .basis_gate=basis_gate, - .basis_fidelity=basis_fidelity, - .euler_basis=euler_basis, - .basis_decomposer=basis_decomposer, - .super_controlled=super_controlled, - .u0l=u0l, - .u0r=u0r, - .u1l=u1l, - .u1ra=u1ra, - .u1rb=u1rb, - .u2la=u2la, - .u2lb=u2lb, - .u2ra=u2ra, - .u2rb=u2rb, - .u3l=u3l, - .u3r=u3r, - .q0l=q0l, - .q0r=q0r, - .q1la=q1la, - .q1lb=q1lb, - .q1ra=q1ra, - .q1rb=q1rb, - .q2l=q2l, - .q2r=q2r, + .basisGate = basisGate, + .basisFidelity = basisFidelity, + .eulerBasis = eulerBasis, + .basisDecomposer = basisDecomposer, + .superControlled = superControlled, + .u0l = u0l, + .u0r = u0r, + .u1l = u1l, + .u1ra = u1ra, + .u1rb = u1rb, + .u2la = u2la, + .u2lb = u2lb, + .u2ra = u2ra, + .u2rb = u2rb, + .u3l = u3l, + .u3r = u3r, + .q0l = q0l, + .q0r = q0r, + .q1la = q1la, + .q1lb = q1lb, + .q1ra = q1ra, + .q1rb = q1rb, + .q2l = q2l, + .q2r = q2r, }; } std::optional twoQubitDecompose(const matrix4x4& unitaryMatrix, - std::optional _basis_fidelity, bool approximate, - std::optional _num_basis_uses) { - auto get_basis_fidelity = [&]() { + std::optional basisFidelity, bool approximate, + std::optional numBasisUses) { + auto getBasisFidelity = [&]() { if (approximate) { - return _basis_fidelity.value_or(this->basis_fidelity); + return basisFidelity.value_or(this->basisFidelity); } return static_cast(1.0); }; - fp basis_fidelity = get_basis_fidelity(); - auto target_decomposed = TwoQubitWeylDecomposition::new_inner( + fp actualBasisFidelity = getBasisFidelity(); + auto targetDecomposed = TwoQubitWeylDecomposition::newInner( unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); - auto traces = this->traces(target_decomposed); - auto get_default_nbasis = [&]() { + auto traces = this->traces(targetDecomposed); + auto getDefaultNbasis = [&]() { auto minValue = std::numeric_limits::min(); auto minIndex = -1; - for (std::size_t i = 0; i < traces.size(); ++i) { - auto value = trace_to_fid(traces[i]) * std::pow(basis_fidelity, i); + for (int i = 0; i < static_cast(traces.size()); ++i) { + auto value = traceToFid(traces[i]) * std::pow(actualBasisFidelity, i); if (value > minValue) { minIndex = i; minValue = value; @@ -1440,138 +1445,136 @@ struct GateDecompositionPattern final } return minIndex; }; - auto best_nbasis = _num_basis_uses.value_or(get_default_nbasis()); - auto choose_decomposition = [&]() { - if (best_nbasis == 0) { - return decomp0_inner(target_decomposed); + auto bestNbasis = numBasisUses.value_or(getDefaultNbasis()); + auto chooseDecomposition = [&]() { + if (bestNbasis == 0) { + return decomp0Inner(targetDecomposed); } - if (best_nbasis == 1) { - return decomp1_inner(target_decomposed); + if (bestNbasis == 1) { + return decomp1Inner(targetDecomposed); } - if (best_nbasis == 2) { - return decomp2_supercontrolled_inner(target_decomposed); + if (bestNbasis == 2) { + return decomp2SupercontrolledInner(targetDecomposed); } - if (best_nbasis == 3) { - return decomp3_supercontrolled_inner(target_decomposed); + if (bestNbasis == 3) { + return decomp3SupercontrolledInner(targetDecomposed); } throw std::logic_error{"Invalid basis to use"}; }; - auto decomposition = choose_decomposition(); - std::cerr << "NBasis: " << (int)best_nbasis - << "; basis_fid: " << basis_fidelity + auto decomposition = chooseDecomposition(); + std::cerr << "NBasis: " << static_cast(bestNbasis) + << "; basis_fid: " << actualBasisFidelity << "; Traces: " << traces[0] << ", " << traces[1] << ", " << traces[2] << ", " << traces[3]; std::cerr << "\nDecomposition:\n"; for (auto x : decomposition) { helpers::print(x, "", true); } - std::vector - target_1q_basis_list; // TODO: simplify because list only has one - // element? - target_1q_basis_list.push_back(euler_basis); + std::vector target1qBasisList; // TODO: simplify because list + // only has one element? + target1qBasisList.push_back(eulerBasis); llvm::SmallVector, 8> - euler_decompositions; + eulerDecompositions; for (auto&& decomp : decomposition) { - auto euler_decomp = unitary_to_gate_sequence_inner( - decomp, target_1q_basis_list, 0, {}, true, std::nullopt); - euler_decompositions.push_back(euler_decomp); + auto eulerDecomp = unitaryToGateSequenceInner( + decomp, target1qBasisList, 0, {}, true, std::nullopt); + eulerDecompositions.push_back(eulerDecomp); } - TwoQubitGateSequence gates{.globalPhase = target_decomposed.global_phase}; + TwoQubitGateSequence gates{.globalPhase = targetDecomposed.globalPhase}; // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q // gate We might overallocate a bit if the euler basis is different but // the worst case is just 16 extra elements with just a String and 2 // smallvecs each. This is only transient though as the circuit // sequences aren't long lived and are just used to create a // QuantumCircuit or DAGCircuit when we return to Python space. - constexpr auto TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY = 21; - gates.gates.reserve(TWO_QUBIT_SEQUENCE_DEFAULT_CAPACITY); - gates.globalPhase -= best_nbasis * basis_decomposer.global_phase; - if (best_nbasis == 2) { + constexpr auto twoQubitSequenceDefaultCapacity = 21; + gates.gates.reserve(twoQubitSequenceDefaultCapacity); + gates.globalPhase -= bestNbasis * basisDecomposer.globalPhase; + if (bestNbasis == 2) { gates.globalPhase += qc::PI; } - auto add_euler_decomposition = [&](std::size_t index, - std::size_t qubit_id) { - if (auto&& euler_decomp = euler_decompositions[index]) { - for (auto&& gate : euler_decomp->gates) { + auto addEulerDecomposition = [&](std::size_t index, std::size_t qubitId) { + if (auto&& eulerDecomp = eulerDecompositions[index]) { + for (auto&& gate : eulerDecomp->gates) { gates.gates.push_back({.type = gate.type, .parameter = gate.parameter, - .qubit_id = {qubit_id}}); - gates.globalPhase += euler_decomp->globalPhase; + .qubitId = {qubitId}}); + gates.globalPhase += eulerDecomp->globalPhase; } } }; - for (std::size_t i = 0; i < best_nbasis; ++i) { - add_euler_decomposition(2 * i, 0); - add_euler_decomposition(2 * i + 1, 1); + for (std::size_t i = 0; i < bestNbasis; ++i) { + addEulerDecomposition(2 * i, 0); + addEulerDecomposition((2 * i) + 1, 1); - gates.gates.push_back(basis_gate); + gates.gates.push_back(basisGate); } - add_euler_decomposition(2 * best_nbasis, 0); - add_euler_decomposition(2 * best_nbasis + 1, 1); + addEulerDecomposition(2UL * bestNbasis, 0); + addEulerDecomposition((2UL * bestNbasis) + 1, 1); return gates; } private: - [[nodiscard]] std::vector - decomp0_inner(const TwoQubitWeylDecomposition& target) const { + [[nodiscard]] static std::vector + decomp0Inner(const TwoQubitWeylDecomposition& target) { return { - target.K1r * target.K2r, - target.K1l * target.K2l, + target.k1r * target.k2r, + target.k1l * target.k2l, }; } [[nodiscard]] std::vector - decomp1_inner(const TwoQubitWeylDecomposition& target) const { + decomp1Inner(const TwoQubitWeylDecomposition& target) const { // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) return { - basis_decomposer.K2r.transpose().conjugate() * target.K2r, - basis_decomposer.K2l.transpose().conjugate() * target.K2l, - target.K1r * basis_decomposer.K1r.transpose().conjugate(), - target.K1l * basis_decomposer.K1l.transpose().conjugate(), + basisDecomposer.k2r.transpose().conjugate() * target.k2r, + basisDecomposer.k2l.transpose().conjugate() * target.k2l, + target.k1r * basisDecomposer.k1r.transpose().conjugate(), + target.k1l * basisDecomposer.k1l.transpose().conjugate(), }; } - [[nodiscard]] std::vector decomp2_supercontrolled_inner( - const TwoQubitWeylDecomposition& target) const { + [[nodiscard]] std::vector + decomp2SupercontrolledInner(const TwoQubitWeylDecomposition& target) const { return { - q2r * target.K2r, - q2l * target.K2l, - q1ra * rz_matrix(2. * target.b) * q1rb, - q1la * rz_matrix(-2. * target.a) * q1lb, - target.K1r * q0r, - target.K1l * q0l, + q2r * target.k2r, + q2l * target.k2l, + q1ra * rzMatrix(2. * target.b) * q1rb, + q1la * rzMatrix(-2. * target.a) * q1lb, + target.k1r * q0r, + target.k1l * q0l, }; } - [[nodiscard]] std::vector decomp3_supercontrolled_inner( - const TwoQubitWeylDecomposition& target) const { + [[nodiscard]] std::vector + decomp3SupercontrolledInner(const TwoQubitWeylDecomposition& target) const { return { - u3r * target.K2r, - u3l * target.K2l, - u2ra * rz_matrix(2. * target.b) * u2rb, - u2la * rz_matrix(-2. * target.a) * u2lb, - u1ra * rz_matrix(-2. * target.c) * u1rb, + u3r * target.k2r, + u3l * target.k2l, + u2ra * rzMatrix(2. * target.b) * u2rb, + u2la * rzMatrix(-2. * target.a) * u2lb, + u1ra * rzMatrix(-2. * target.c) * u1rb, u1l, - target.K1r * u0r, - target.K1l * u0l, + target.k1r * u0r, + target.k1l * u0l, }; } - matrix4x4 compute_unitary(const TwoQubitGateSequence& sequence, - fp global_phase) { - auto phase = std::exp(std::complex{0, global_phase}); + static matrix4x4 computeUnitary(const TwoQubitGateSequence& sequence, + fp globalPhase) { + auto phase = std::exp(std::complex{0, globalPhase}); matrix4x4 matrix{}; matrix.diagonal().setConstant(phase); for (auto&& gate : sequence.gates) { - matrix4x4 gate_matrix = getTwoQubitMatrix(gate); + matrix4x4 gateMatrix = getTwoQubitMatrix(gate); - matrix = gate_matrix * matrix; + matrix = gateMatrix * matrix; } return matrix; } @@ -1582,26 +1585,25 @@ struct GateDecompositionPattern final static_cast(4.) * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), std::sin(target.a) * std::sin(target.b) * std::sin(target.c)), - static_cast(4.) * - qfp(std::cos(qc::PI_4 - target.a) * - std::cos(basis_decomposer.b - target.b) * - std::cos(target.c), - std::sin(qc::PI_4 - target.a) * - std::sin(basis_decomposer.b - target.b) * - std::sin(target.c)), + static_cast(4.) * qfp(std::cos(qc::PI_4 - target.a) * + std::cos(basisDecomposer.b - target.b) * + std::cos(target.c), + std::sin(qc::PI_4 - target.a) * + std::sin(basisDecomposer.b - target.b) * + std::sin(target.c)), qfp(4. * std::cos(target.c), 0.), qfp(4., 0.), }; } - OneQubitGateSequence generate_circuit(EulerBasis target_basis, - const matrix2x2& unitaryMatrix, - bool simplify, - std::optional atol) { + static OneQubitGateSequence generateCircuit(EulerBasis targetBasis, + const matrix2x2& unitaryMatrix, + bool simplify, + std::optional atol) { auto [theta, phi, lambda, phase] = - angles_from_unitary(unitaryMatrix, target_basis); + anglesFromUnitary(unitaryMatrix, targetBasis); - switch (target_basis) { + switch (targetBasis) { case EulerBasis::ZYZ: return calculateRotationGates(theta, phi, lambda, phase, qc::RZ, qc::RY, simplify, atol); @@ -1620,23 +1622,21 @@ struct GateDecompositionPattern final } } - OneQubitGateSequence unitary_to_gate_sequence_inner( - matrix2x2 unitary_mat, const std::vector& target_basis_list, - std::size_t qubit, + static OneQubitGateSequence unitaryToGateSequenceInner( + matrix2x2 unitaryMat, const std::vector& targetBasisList, + std::size_t /*qubit*/, const std::vector>& - error_map, // TODO: remove error_map+qubit for platform + /*error_map*/, // TODO: remove error_map+qubit for platform // independence bool simplify, std::optional atol) { - auto calculateError = [](const OneQubitGateSequence& sequence) { - return sequence.gates.size(); + auto calculateError = [](const OneQubitGateSequence& sequence) -> fp { + return static_cast(sequence.gates.size()); }; auto minError = std::numeric_limits::max(); OneQubitGateSequence bestCircuit; - for (std::size_t i = 0; i < target_basis_list.size(); ++i) { - auto& target_basis = target_basis_list[i]; - auto circuit = - generate_circuit(target_basis, unitary_mat, simplify, atol); + for (auto targetBasis : targetBasisList) { + auto circuit = generateCircuit(targetBasis, unitaryMat, simplify, atol); auto error = calculateError(circuit); if (error < minError) { bestCircuit = circuit; @@ -1709,9 +1709,9 @@ struct GateDecompositionPattern final }; // namespace mqt::ir::opt }; -const matrix2x2 GateDecompositionPattern::identityGate = matrix2x2::Identity(); -const matrix2x2 GateDecompositionPattern::hGate{{1.0 / sqrt2, 1.0 / sqrt2}, - {1.0 / sqrt2, -1.0 / sqrt2}}; +const matrix2x2 GateDecompositionPattern::IDENTITY_GATE = matrix2x2::Identity(); +const matrix2x2 GateDecompositionPattern::H_GATE{{1.0 / SQRT2, 1.0 / SQRT2}, + {1.0 / SQRT2, -1.0 / SQRT2}}; const matrix2x2 GateDecompositionPattern::IPZ{{IM, C_ZERO}, {C_ZERO, M_IM}}; const matrix2x2 GateDecompositionPattern::IPY{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 595ce2566..46d314bcb 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -10,15 +10,15 @@ #pragma once -#include "dd/GateMatrixDefinitions.hpp" -#include "dd/Package.hpp" -#include "ir/Definitions.hpp" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" +#include "ir/operations/OpType.hpp" #include #include #include #include // TODO: unstable +#include // TODO: remove +#include // TODO: remove #include #include @@ -42,19 +42,20 @@ constexpr qfp M_IM{0., -1.}; namespace mqt::ir::opt::helpers { -void print(std::size_t x) { std::cerr << x; } -void print(fp x) { std::cerr << x; } +inline void print(std::size_t x) { std::cerr << x; } +inline void print(fp x) { std::cerr << x; } -void print(qfp x) { +inline void print(qfp x) { std::cerr << std::setprecision(17) << x.real() << 'i' << x.imag(); } // TODO: remove template -void print(Eigen::Matrix matrix, std::string s = "", +void print(Eigen::Matrix matrix, const std::string& s = "", bool force = false) { - if (!force) + if (!force) { return; +} if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } @@ -69,9 +70,10 @@ void print(Eigen::Matrix matrix, std::string s = "", } template -void print(T&& matrix, std::string s = "", bool force = false) { - if (!force) +void print(T matrix, const std::string& s = "", bool force = false) { + if (!force) { return; +} if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } @@ -217,14 +219,14 @@ inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, return result; } -auto self_adjoint_evd(rmatrix4x4 A) { +inline auto selfAdjointEvd(rmatrix4x4 a) { Eigen::SelfAdjointEigenSolver s; - std::cerr << "=EigIN==\n" << A << "\n========\n" << std::endl; - s.compute(A); // TODO: computeDirect is faster + std::cerr << "=EigIN==\n" << a << "\n========\n" << '\n'; + s.compute(a); // TODO: computeDirect is faster auto vecs = s.eigenvectors().eval(); auto vals = s.eigenvalues(); - std::cerr << "=Eigen==\n" << vecs << "\n========\n" << std::endl; - std::cerr << "=Eigen==\n" << vals << "\n========\n" << std::endl; + std::cerr << "=Eigen==\n" << vecs << "\n========\n" << '\n'; + std::cerr << "=Eigen==\n" << vals << "\n========\n" << '\n'; return std::make_pair(vecs, vals); } From f055925c7f549906a3571fd1c825093cf4557b37 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 11 Nov 2025 14:08:10 +0100 Subject: [PATCH 048/100] test normalized magic basis, allow complex eigenvector decomposition --- .../Transforms/GateDecompositionPattern.cpp | 52 +++++++++++-------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 5 +- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e50cb5e5b..d1a0ae8bc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -13,10 +13,10 @@ #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include +#include #include #include #include -#include #include #include #include @@ -281,6 +281,8 @@ struct GateDecompositionPattern final rewriter.setInsertionPointAfter(lastSeriesOp); if (sequence.globalPhase != 0.0) { + // TODO: use POp instead and apply negative globalPhase after insertion of + // new gates to "undo" the phase shift createOneParameterGate(rewriter, location, sequence.globalPhase, {}); } @@ -387,6 +389,8 @@ struct GateDecompositionPattern final }; static constexpr auto SQRT2 = std::numbers::sqrt2_v; + static constexpr auto FRAC1_SQRT2 = + static_cast(0.707106781186547524400844362104849039); static const matrix2x2 IDENTITY_GATE; static const matrix2x2 H_GATE; @@ -410,15 +414,14 @@ struct GateDecompositionPattern final } // https://docs.rs/faer/latest/faer/mat/generic/struct.Mat.html#method.self_adjoint_eigen - static std::pair - selfAdjointEigenLower(rmatrix4x4 a) { + template static auto selfAdjointEigenLower(T&& a) { // rdiagonal4x4 S; // auto U = self_adjoint_evd(A, S); // rmatrix4x4 U; // jacobi_eigen_decomposition(A, U, S); - auto [U, S] = helpers::selfAdjointEvd(a); + auto [U, S] = helpers::selfAdjointEvd(std::forward(a)); // TODO: not in original code if (std::abs(U.determinant() + 1.0) < 1e-5) { @@ -483,6 +486,10 @@ struct GateDecompositionPattern final {C_ZERO, C_ZERO, IM, C_M_ONE}, {C_ONE, M_IM, C_ZERO, C_ZERO}, }; + const matrix4x4 b = FRAC1_SQRT2 * matrix4x4{{C_ONE, C_ZERO, C_ZERO, IM}, + {C_ZERO, IM, C_ONE, C_ZERO}, + {C_ZERO, IM, -C_ONE, C_ZERO}, + {C_ONE, C_ZERO, C_ZERO, -IM}}; const matrix4x4 bNonNormalizedDagger{ {qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.)}, @@ -490,11 +497,14 @@ struct GateDecompositionPattern final {C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO}, {C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO}, }; + const matrix4x4 bDagger = b.conjugate().transpose(); helpers::print(unitary, "UNITARY in MAGIC BASIS TRANSFORM"); if (direction == MagicBasisTransform::OutOf) { + // return bDagger * unitary * b; // TODO: same result? return bNonNormalizedDagger * unitary * bNonNormalized; } if (direction == MagicBasisTransform::Into) { + // return b * unitary * bDagger; return bNonNormalized * unitary * bNonNormalizedDagger; } throw std::logic_error{"Unknown MagicBasisTransform direction!"}; @@ -1300,11 +1310,9 @@ struct GateDecompositionPattern final } return absDiff <= absLhs * maxRelative; }; - constexpr auto frac1Sqrt2 = - static_cast(0.707106781186547524400844362104849039); const auto k12RArr = matrix2x2{ - {qfp(0., frac1Sqrt2), qfp(frac1Sqrt2, 0.)}, - {qfp(-frac1Sqrt2, 0.), qfp(0., -frac1Sqrt2)}, + {qfp(0., FRAC1_SQRT2), qfp(FRAC1_SQRT2, 0.)}, + {qfp(-FRAC1_SQRT2, 0.), qfp(0., -FRAC1_SQRT2)}, }; const auto k12LArr = matrix2x2{ {qfp(0.5, 0.5), qfp(0.5, 0.5)}, @@ -1327,14 +1335,14 @@ struct GateDecompositionPattern final {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * -(std::exp(qfp(0., b)))}}; - auto k11r = matrix2x2{{frac1Sqrt2 * std::exp((IM * qfp(0., -b))), - frac1Sqrt2 * -std::exp(qfp(0., -b))}, - {frac1Sqrt2 * std::exp(qfp(0., b)), - frac1Sqrt2 * (M_IM * std::exp(qfp(0., b)))}}; - auto k32lK21l = matrix2x2{{frac1Sqrt2 * std::cos(qfp(1., (2. * b))), - frac1Sqrt2 * (IM * std::sin((2. * b)))}, - {frac1Sqrt2 * (IM * std::sin(2. * b)), - frac1Sqrt2 * qfp(1., -std::cos(2. * b))}}; + auto k11r = matrix2x2{{FRAC1_SQRT2 * std::exp((IM * qfp(0., -b))), + FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, + {FRAC1_SQRT2 * std::exp(qfp(0., b)), + FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; + auto k32lK21l = matrix2x2{{FRAC1_SQRT2 * std::cos(qfp(1., (2. * b))), + FRAC1_SQRT2 * (IM * std::sin((2. * b)))}, + {FRAC1_SQRT2 * (IM * std::sin(2. * b)), + FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); auto k21r = matrix2x2{ {temp * (M_IM * std::exp(qfp(0., -2. * b))), @@ -1343,15 +1351,15 @@ struct GateDecompositionPattern final temp * std::exp(qfp(0., 2. * b))}, }; const auto k22LArr = matrix2x2{ - {qfp(frac1Sqrt2, 0.), qfp(-frac1Sqrt2, 0.)}, - {qfp(frac1Sqrt2, 0.), qfp(frac1Sqrt2, 0.)}, + {qfp(FRAC1_SQRT2, 0.), qfp(-FRAC1_SQRT2, 0.)}, + {qfp(FRAC1_SQRT2, 0.), qfp(FRAC1_SQRT2, 0.)}, }; const auto k22RArr = matrix2x2{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; auto k31l = matrix2x2{ - {frac1Sqrt2 * std::exp(qfp(0., -b)), - frac1Sqrt2 * std::exp(qfp(0., -b))}, - {frac1Sqrt2 * -std::exp(qfp(0., b)), - frac1Sqrt2 * std::exp(qfp(0., b))}, + {FRAC1_SQRT2 * std::exp(qfp(0., -b)), + FRAC1_SQRT2 * std::exp(qfp(0., -b))}, + {FRAC1_SQRT2 * -std::exp(qfp(0., b)), + FRAC1_SQRT2 * std::exp(qfp(0., b))}, }; auto k31r = matrix2x2{ {IM * std::exp(qfp(0., b)), C_ZERO}, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 46d314bcb..875f8a3d0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -219,8 +219,9 @@ inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, return result; } -inline auto selfAdjointEvd(rmatrix4x4 a) { - Eigen::SelfAdjointEigenSolver s; +template +inline auto selfAdjointEvd(Eigen::Matrix a) { + Eigen::SelfAdjointEigenSolver s; std::cerr << "=EigIN==\n" << a << "\n========\n" << '\n'; s.compute(a); // TODO: computeDirect is faster auto vecs = s.eigenvectors().eval(); From be2feca680f9c5509cb73b76f53c444a53735e8b Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 00:19:39 +0100 Subject: [PATCH 049/100] finally figured some stuff out --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index d1a0ae8bc..dba3f0e5e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -702,6 +702,11 @@ struct GateDecompositionPattern final fp b; fp c; fp globalPhase; + /** + * - k2r - X - k1r - + * X + * - k2l - X - k1l - + */ matrix2x2 k1l; matrix2x2 k2l; matrix2x2 k1r; @@ -830,8 +835,10 @@ struct GateDecompositionPattern final temp = temp.exp(); helpers::print(temp, "TEMP"); helpers::print(p, "P", true); + // https://threeplusone.com/pubs/on_gates.pdf + // uP = V, m2 = V^T*V, temp = D, p = Q1 auto k1 = magicBasisTransform(uP * p * temp, MagicBasisTransform::Into); - auto k2 = magicBasisTransform(p.transpose(), MagicBasisTransform::Into); + auto k2 = magicBasisTransform(p.transpose().conjugate(), MagicBasisTransform::Into); auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); From d39d2ec405c63472eadcfbe18cac5dc541083532 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 14:52:57 +0100 Subject: [PATCH 050/100] try out lauging-umbrella weyl-chamber --- .../Transforms/GateDecompositionPattern.cpp | 109 ++++++++++++------ 1 file changed, 74 insertions(+), 35 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index dba3f0e5e..b454960f1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -12,6 +12,7 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include #include #include #include @@ -792,53 +793,91 @@ struct GateDecompositionPattern final throw std::runtime_error{ "TwoQubitWeylDecomposition: failed to diagonalize M2."}; } - rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; - helpers::print(dReal, "D_REAL", true); - dReal(3) = -dReal(0) - dReal(1) - dReal(2); - std::array cs{}; - for (int i = 0; i < static_cast(cs.size()); ++i) { - assert(i < dReal.size()); - cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); + // check that p is in SO(4) + assert((p.transpose() * p).isIdentity()); + assert(std::abs(p.determinant() - 1.0) < 1e-13); + // make sure de4terminant of sqrt(eigenvalues) is 1.0 + assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < 1e-13); + + // see https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, Step 7 + Eigen::Matrix coefficientMatrix {{1, 1, 1}, {-1, 1, -1}, {1, -1, -1}}; + auto theta_vec = d.cwiseArg().head(3); + Eigen::Vector tmp = coefficientMatrix.inverse() * theta_vec; + Eigen::Vector cs{ 2.0 * tmp(0), -2.0 * tmp(1), 2.0 * tmp(2) }; + // bring cs into weyl chamber + cs /= qc::PI; + cs -= cs.unaryExpr([](auto&& x) { return std::floor(x); }); + std::ranges::sort(cs, std::greater<>()); + if (cs[0] + cs[1] >= 1.0) { + cs = decltype(cs){ 1 - cs[0], cs[1], 0.0 }; + std::ranges::sort(cs, std::greater<>()); } - helpers::print(cs, "CS", true); - decltype(cs) cstemp; - llvm::transform(cs, cstemp.begin(), [](auto&& x) { - auto tmp = remEuclid(x, qc::PI_2); - return std::min(tmp, qc::PI_2 - tmp); - }); - std::array order{ - 2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector - // order in eigen decomposition algorithm? - llvm::stable_sort(order, - [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - std::tie(order[0], order[1], order[2]) = - std::tuple{order[1], order[2], order[0]}; - std::tie(cs[0], cs[1], cs[2]) = - std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - std::tie(dReal(0), dReal(1), dReal(2)) = - std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - helpers::print(dReal, "D_REAL (sorted)", true); - - // swap columns of p according to order - auto pOrig = p; - for (int i = 0; i < static_cast(order.size()); ++i) { - p.col(i) = pOrig.col(order[i]); + if (cs[0] > 0.5 && std::abs(cs[2]) < 1e-13) { + cs = decltype(cs) { 1 - cs[0], cs[1], 0.0 }; } + cs *= qc::PI; + + // rdiagonal4x4 dReal = d.cwiseArg() / 2.0; + // helpers::print(dReal, "D_REAL", true); + // dReal(3) = -dReal(0) - dReal(1) - dReal(2); + // std::array cs{}; + // for (int i = 0; i < static_cast(cs.size()); ++i) { + // assert(i < dReal.size()); + // cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); + // } + // helpers::print(cs, "CS", true); + + // decltype(cs) cstemp; + // llvm::transform(cs, cstemp.begin(), [](auto&& x) { + // auto tmp = remEuclid(x, qc::PI_2); + // return std::min(tmp, qc::PI_2 - tmp); + // }); + // std::array order{ + // 2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector + // // order in eigen decomposition algorithm? + // llvm::stable_sort(order, + // [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); + // std::tie(order[0], order[1], order[2]) = + // std::tuple{order[1], order[2], order[0]}; + // std::tie(cs[0], cs[1], cs[2]) = + // std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; + // std::tie(dReal(0), dReal(1), dReal(2)) = + // std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; + // helpers::print(dReal, "D_REAL (sorted)", true); + + // // swap columns of p according to order + // auto pOrig = p; + // for (int i = 0; i < static_cast(order.size()); ++i) { + // p.col(i) = pOrig.col(order[i]); + // } + + // std::tie(p, d) = selfAdjointEigenLower(m2); + + // dReal = -1.0 * d.cwiseArg() / 2.0; + // helpers::print(dReal, "D_REAL", true); + // dReal(3) = -dReal(0) - dReal(1) - dReal(2); + // for (int i = 0; i < static_cast(cs.size()); ++i) { + // assert(i < dReal.size()); + // cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); + // } + // matrix4x4 temp = d.asDiagonal(); if (p.determinant().real() < 0.0) { + std::cerr << "SECOND CORRECTION?\n"; auto lastColumnIndex = p.cols() - 1; - p.col(lastColumnIndex) = -p.col(lastColumnIndex); + p.col(lastColumnIndex) *= -1.0; } - matrix4x4 temp = dReal.asDiagonal(); - temp *= IM; - temp = temp.exp(); + matrix4x4 temp = d.asDiagonal(); + // temp *= IM; + // temp = temp.exp(); helpers::print(temp, "TEMP"); helpers::print(p, "P", true); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 auto k1 = magicBasisTransform(uP * p * temp, MagicBasisTransform::Into); - auto k2 = magicBasisTransform(p.transpose().conjugate(), MagicBasisTransform::Into); + auto k2 = magicBasisTransform(p.transpose().conjugate(), + MagicBasisTransform::Into); auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); From 0013723a0a903490bb9d7b9987e71792f5902127 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 16:59:32 +0100 Subject: [PATCH 051/100] try to implement weylchamber.c1c2c3 algorithm --- .../Transforms/GateDecompositionPattern.cpp | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index b454960f1..7aa498773 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -799,28 +799,13 @@ struct GateDecompositionPattern final // make sure de4terminant of sqrt(eigenvalues) is 1.0 assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < 1e-13); - // see https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, Step 7 - Eigen::Matrix coefficientMatrix {{1, 1, 1}, {-1, 1, -1}, {1, -1, -1}}; - auto theta_vec = d.cwiseArg().head(3); - Eigen::Vector tmp = coefficientMatrix.inverse() * theta_vec; - Eigen::Vector cs{ 2.0 * tmp(0), -2.0 * tmp(1), 2.0 * tmp(2) }; - // bring cs into weyl chamber - cs /= qc::PI; - cs -= cs.unaryExpr([](auto&& x) { return std::floor(x); }); - std::ranges::sort(cs, std::greater<>()); - if (cs[0] + cs[1] >= 1.0) { - cs = decltype(cs){ 1 - cs[0], cs[1], 0.0 }; - std::ranges::sort(cs, std::greater<>()); - } - if (cs[0] > 0.5 && std::abs(cs[2]) < 1e-13) { - cs = decltype(cs) { 1 - cs[0], cs[1], 0.0 }; - } - cs *= qc::PI; - - // rdiagonal4x4 dReal = d.cwiseArg() / 2.0; + // see + // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, + // Step 7 + // rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; // helpers::print(dReal, "D_REAL", true); // dReal(3) = -dReal(0) - dReal(1) - dReal(2); - // std::array cs{}; + // Eigen::Vector cs{}; // for (int i = 0; i < static_cast(cs.size()); ++i) { // assert(i < dReal.size()); // cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); @@ -851,33 +836,53 @@ struct GateDecompositionPattern final // p.col(i) = pOrig.col(order[i]); // } - // std::tie(p, d) = selfAdjointEigenLower(m2); - - // dReal = -1.0 * d.cwiseArg() / 2.0; - // helpers::print(dReal, "D_REAL", true); - // dReal(3) = -dReal(0) - dReal(1) - dReal(2); - // for (int i = 0; i < static_cast(cs.size()); ++i) { - // assert(i < dReal.size()); - // cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); + // if (p.determinant().real() < 0.0) { + // std::cerr << "SECOND CORRECTION?\n"; + // auto lastColumnIndex = p.cols() - 1; + // p.col(lastColumnIndex) *= -1.0; // } - // matrix4x4 temp = d.asDiagonal(); - if (p.determinant().real() < 0.0) { - std::cerr << "SECOND CORRECTION?\n"; - auto lastColumnIndex = p.cols() - 1; - p.col(lastColumnIndex) *= -1.0; + // https://weylchamber.readthedocs.io/en/latest/_modules/weylchamber/coordinates.html#c1c2c3 + Eigen::Vector cs; + Eigen::Vector dReal = d.cwiseArg() / qc::PI; + for (auto& x : dReal) { + if (x <= -0.5) { + x += 2.0; + } + } + dReal /= 2.0; + llvm::stable_sort(dReal, std::greater<>()); + int n = std::round(dReal.sum()); + for (int i = 0; i < n; ++i) { + dReal[i] -= 1.0; } + decltype(dReal) origDReal = dReal; + for (int i = 0; i < dReal.size(); ++i) { + // roll(-n) + dReal[i] = origDReal[i >= n ? i - n : dReal.size() - (i - n)]; + } + Eigen::Matrix M {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; + cs = M * dReal.head(3); + if (cs[2] < 0.0) { + cs[0] = 1.0 - cs[0]; + cs[2] *= -1.0; + } + dReal *= qc::PI; + cs *= qc::PI; - matrix4x4 temp = d.asDiagonal(); - // temp *= IM; - // temp = temp.exp(); + matrix4x4 temp = dReal.asDiagonal(); + temp *= IM; + temp = temp.exp(); helpers::print(temp, "TEMP"); helpers::print(p, "P", true); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 - auto k1 = magicBasisTransform(uP * p * temp, MagicBasisTransform::Into); - auto k2 = magicBasisTransform(p.transpose().conjugate(), - MagicBasisTransform::Into); + matrix4x4 k1 = uP * p * temp; + assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal + k1 = magicBasisTransform(k1, MagicBasisTransform::Into); + matrix4x4 k2 = p.transpose().conjugate(); + assert((k2.transpose() * k2).isIdentity()); // k2 must be orthogonal + k2 = magicBasisTransform(k2, MagicBasisTransform::Into); auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); From d28ab9d21f81c959f644cd0d9eaef23a93673217 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 22:52:38 +0100 Subject: [PATCH 052/100] on_gates two-qubit product decompose --- .../Transforms/GateDecompositionPattern.cpp | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 7aa498773..99a11320b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -437,7 +438,43 @@ struct GateDecompositionPattern final static std::tuple decomposeTwoQubitProductGate(matrix4x4 specialUnitary) { + // see pennylane math.decomposition.su2su2_to_tensor_products + // or "7.1 Kronecker decomposition" in on_gates.pdf helpers::print(specialUnitary, "SPECIAL_UNITARY"); + + // Step 1: Reshape and permute C like in Python + // C (4x4) is considered as (2,2,2,2) tensor, transpose(0,2,1,3) + matrix4x4 C; + + // Perform the same rearrangement as Python's transpose(0,2,1,3) + // This swaps certain 2x2 blocks in C_in. + // The pattern is determined by the tensor index mapping. + for (int i = 0; i < 2; ++i) + for (int j = 0; j < 2; ++j) + for (int k = 0; k < 2; ++k) + for (int l = 0; l < 2; ++l) { + int row_in = 2 * i + k; + int col_in = 2 * j + l; + int row_out = 2 * i + j; + int col_out = 2 * k + l; + C(row_out, col_out) = specialUnitary(row_in, col_in); + } + + // Step 2: SVD + Eigen::JacobiSVD svd(C, Eigen::ComputeFullU | Eigen::ComputeFullV); + Eigen::Vector4d singularValues = svd.singularValues(); + matrix4x4 U = svd.matrixU(); + matrix4x4 V = svd.matrixV(); + + // Step 3: Extract first singular component + double s = std::sqrt(singularValues(0)); + + matrix2x2 A = s * U.col(0).reshaped(2, 2); + matrix2x2 B = s * V.col(0).reshaped(2, 2).transpose(); // vh[0, :] in numpy == V^T.row(0) + + // return {A, B, s}; + + // first quadrant matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, {specialUnitary(1, 0), specialUnitary(1, 1)}}; From d6c6a8be7d0b1ec2e04736145b538f2751d7e415 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 22:54:03 +0100 Subject: [PATCH 053/100] minor additions --- .../Transforms/GateDecompositionPattern.cpp | 23 +++++++++++++++---- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 8 +------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 99a11320b..405cd9a94 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -426,11 +426,14 @@ struct GateDecompositionPattern final auto [U, S] = helpers::selfAdjointEvd(std::forward(a)); // TODO: not in original code - if (std::abs(U.determinant() + 1.0) < 1e-5) { + if (std::real(U.determinant()) < 0.0) { std::cerr << "CORRECTION!\n"; // if determinant of eigenvector matrix is -1.0, multiply first // eigenvector by -1.0 U.col(0) *= -1.0; + // U.col(U.cols() - 1) *= -1.0; + // U *= -1.0; + // U += std::remove_cvref_t::Constant(0.0); // ensure no -0.0 exists } return std::make_pair(U, S); @@ -479,13 +482,14 @@ struct GateDecompositionPattern final matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, {specialUnitary(1, 0), specialUnitary(1, 1)}}; auto detR = r.determinant(); + std::cerr << "DET_R: " << detR << '\n'; if (std::abs(detR) < 0.1) { // third quadrant r = matrix2x2{{specialUnitary(2, 0), specialUnitary(2, 1)}, {specialUnitary(3, 0), specialUnitary(3, 1)}}; detR = r.determinant(); + std::cerr << "DET_R CORRECTION: " << detR << '\n'; } - std::cerr << "DET_R: " << detR << '\n'; if (std::abs(detR) < 0.1) { throw std::runtime_error{ "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; @@ -729,6 +733,9 @@ struct GateDecompositionPattern final // TODO: check qubit order return rzzMatrix(gate.parameter[0]); } + if (gate.type == qc::I) { + return kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); + } throw std::invalid_argument{ "unsupported gate type for two qubit matrix "}; } @@ -821,7 +828,11 @@ struct GateDecompositionPattern final helpers::print(compare, "COMPARE"); found = (compare - m2).cwiseAbs().cwiseLessOrEqual(1.0e-13).all(); if (found) { + // p are the eigenvectors which are decomposed into the + // single-qubit gates surrounding the canonical gate p = pInner; + // d is the sqrt of the eigenvalues that are used to determine the + // weyl coordinates and thus the parameters of the canonical gate d = dInner; break; } @@ -832,8 +843,7 @@ struct GateDecompositionPattern final } // check that p is in SO(4) assert((p.transpose() * p).isIdentity()); - assert(std::abs(p.determinant() - 1.0) < 1e-13); - // make sure de4terminant of sqrt(eigenvalues) is 1.0 + // make sure determinant of sqrt(eigenvalues) is 1.0 assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < 1e-13); // see @@ -906,18 +916,21 @@ struct GateDecompositionPattern final } dReal *= qc::PI; cs *= qc::PI; + assert(std::abs(p.determinant() - 1.0) < 1e-13); matrix4x4 temp = dReal.asDiagonal(); temp *= IM; temp = temp.exp(); - helpers::print(temp, "TEMP"); + helpers::print(temp, "TEMP", true); helpers::print(p, "P", true); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 matrix4x4 k1 = uP * p * temp; + helpers::print(k1, "K1 (1)", true); assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal k1 = magicBasisTransform(k1, MagicBasisTransform::Into); matrix4x4 k2 = p.transpose().conjugate(); + helpers::print(k2, "K2 (1)", true); assert((k2.transpose() * k2).isIdentity()); // k2 must be orthogonal k2 = magicBasisTransform(k2, MagicBasisTransform::Into); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 875f8a3d0..3849f66cc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -59,13 +59,7 @@ void print(Eigen::Matrix matrix, const std::string& s = "", if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } - for (int i = 0; i < matrix.cols(); ++i) { - for (int j = 0; j < matrix.rows(); ++j) { - print(matrix(j, i)); - std::cerr << ' '; - } - llvm::errs() << '\n'; - } + std::cerr << matrix; llvm::errs() << '\n'; } From d29463500c2e07d1ddf02fb838acb07742022c1d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 12 Nov 2025 22:55:00 +0100 Subject: [PATCH 054/100] try ordering all four eigenvectors --- .../Transforms/GateDecompositionPattern.cpp | 115 ++++++++---------- 1 file changed, 50 insertions(+), 65 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 405cd9a94..c1e4d003c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -849,78 +849,62 @@ struct GateDecompositionPattern final // see // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, // Step 7 - // rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; - // helpers::print(dReal, "D_REAL", true); - // dReal(3) = -dReal(0) - dReal(1) - dReal(2); - // Eigen::Vector cs{}; - // for (int i = 0; i < static_cast(cs.size()); ++i) { - // assert(i < dReal.size()); - // cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); - // } - // helpers::print(cs, "CS", true); - - // decltype(cs) cstemp; - // llvm::transform(cs, cstemp.begin(), [](auto&& x) { - // auto tmp = remEuclid(x, qc::PI_2); - // return std::min(tmp, qc::PI_2 - tmp); - // }); - // std::array order{ - // 2, 1, 0}; // TODO: needs to be adjusted depending on eigenvector - // // order in eigen decomposition algorithm? - // llvm::stable_sort(order, - // [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - // std::tie(order[0], order[1], order[2]) = - // std::tuple{order[1], order[2], order[0]}; - // std::tie(cs[0], cs[1], cs[2]) = - // std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - // std::tie(dReal(0), dReal(1), dReal(2)) = - // std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - // helpers::print(dReal, "D_REAL (sorted)", true); - - // // swap columns of p according to order - // auto pOrig = p; - // for (int i = 0; i < static_cast(order.size()); ++i) { - // p.col(i) = pOrig.col(order[i]); - // } - - // if (p.determinant().real() < 0.0) { - // std::cerr << "SECOND CORRECTION?\n"; - // auto lastColumnIndex = p.cols() - 1; - // p.col(lastColumnIndex) *= -1.0; - // } - - // https://weylchamber.readthedocs.io/en/latest/_modules/weylchamber/coordinates.html#c1c2c3 - Eigen::Vector cs; - Eigen::Vector dReal = d.cwiseArg() / qc::PI; - for (auto& x : dReal) { - if (x <= -0.5) { - x += 2.0; - } - } - dReal /= 2.0; - llvm::stable_sort(dReal, std::greater<>()); - int n = std::round(dReal.sum()); - for (int i = 0; i < n; ++i) { - dReal[i] -= 1.0; + rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; + helpers::print(dReal, "D_REAL", true); + dReal(3) = -dReal(0) - dReal(1) - dReal(2); + Eigen::Vector cs{}; + for (int i = 0; i < static_cast(cs.size()); ++i) { + assert(i < dReal.size()); + cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); } - decltype(dReal) origDReal = dReal; - for (int i = 0; i < dReal.size(); ++i) { - // roll(-n) - dReal[i] = origDReal[i >= n ? i - n : dReal.size() - (i - n)]; + helpers::print(cs, "CS (1)", true); + + decltype(cs) cstemp; + llvm::transform(cs, cstemp.begin(), [](auto&& x) { + auto tmp = remEuclid(x, qc::PI_2); + return std::min(tmp, qc::PI_2 - tmp); + }); + std::array order{ + 0, 1, 2, 3}; // TODO: needs to be adjusted depending on eigenvector + // order in eigen decomposition algorithm? + llvm::stable_sort(order, + [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); + // llvm::stable_sort(order, [&](fp a, fp b) { + // auto tmp1 = remEuclid(cs[a], qc::PI_2); + // tmp1 = std::min(tmp1, qc::PI_2 - tmp1); + // auto tmp2 = remEuclid(cs[b], qc::PI_2); + // tmp2 = std::min(tmp2, qc::PI_2 - tmp2); + // return tmp1 < tmp2; + // }); + std::tie(order[0], order[1], order[2], order[3]) = + std::tuple{order[3], order[1], order[2], order[0]}; + std::tie(cs[0], cs[1], cs[2], cs[3]) = + std::tuple{cs[order[0]], cs[order[1]], cs[order[2]], cs[order[3]]}; + std::tie(dReal(0), dReal(1), dReal(2), dReal(3)) = + std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2]), dReal(order[3])}; + helpers::print(dReal, "D_REAL (sorted)", true); + + // swap columns of p according to order + matrix4x4 pOrig = p; + for (int i = 0; i < static_cast(order.size()); ++i) { + p.col(i) = pOrig.col(order[i]); } - Eigen::Matrix M {{1, 1, 0}, {1, 0, 1}, {0, 1, 1}}; - cs = M * dReal.head(3); - if (cs[2] < 0.0) { - cs[0] = 1.0 - cs[0]; - cs[2] *= -1.0; + + if (p.determinant().real() < 0.0) { + std::cerr << "SECOND CORRECTION?\n"; + auto lastColumnIndex = p.cols() - 1; + p.col(lastColumnIndex) *= -1.0; + } else { + // p = -1.0 * p; + // p += matrix4x4::Constant(0.0); // ensure no -0.0 exists } - dReal *= qc::PI; - cs *= qc::PI; assert(std::abs(p.determinant() - 1.0) < 1e-13); matrix4x4 temp = dReal.asDiagonal(); temp *= IM; temp = temp.exp(); + // temp = temp.conjugate(); + // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); helpers::print(p, "P", true); // https://threeplusone.com/pubs/on_gates.pdf @@ -990,7 +974,8 @@ struct GateDecompositionPattern final K1r = K1r * IPZ; globalPhase -= qc::PI_2; } - auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); + helpers::print(cs, "CS (2)", true); + auto [a, b, c] = std::tie(cs[2], cs[1], cs[3]); auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; From 5c1d56b9fa88a02a70201549a31b5f43e3a38228 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 13 Nov 2025 00:33:03 +0100 Subject: [PATCH 055/100] undo ordering of all columns --- .../Transforms/GateDecompositionPattern.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index c1e4d003c..24e02c46e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -852,7 +852,7 @@ struct GateDecompositionPattern final rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; helpers::print(dReal, "D_REAL", true); dReal(3) = -dReal(0) - dReal(1) - dReal(2); - Eigen::Vector cs{}; + Eigen::Vector cs{}; for (int i = 0; i < static_cast(cs.size()); ++i) { assert(i < dReal.size()); cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); @@ -865,7 +865,7 @@ struct GateDecompositionPattern final return std::min(tmp, qc::PI_2 - tmp); }); std::array order{ - 0, 1, 2, 3}; // TODO: needs to be adjusted depending on eigenvector + 0, 1, 2}; // TODO: needs to be adjusted depending on eigenvector // order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); @@ -876,12 +876,12 @@ struct GateDecompositionPattern final // tmp2 = std::min(tmp2, qc::PI_2 - tmp2); // return tmp1 < tmp2; // }); - std::tie(order[0], order[1], order[2], order[3]) = - std::tuple{order[3], order[1], order[2], order[0]}; - std::tie(cs[0], cs[1], cs[2], cs[3]) = - std::tuple{cs[order[0]], cs[order[1]], cs[order[2]], cs[order[3]]}; - std::tie(dReal(0), dReal(1), dReal(2), dReal(3)) = - std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2]), dReal(order[3])}; + std::tie(order[0], order[1], order[2]) = + std::tuple{order[1], order[2], order[0]}; + std::tie(cs[0], cs[1], cs[2]) = + std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; + std::tie(dReal(0), dReal(1), dReal(2)) = + std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; helpers::print(dReal, "D_REAL (sorted)", true); // swap columns of p according to order @@ -898,7 +898,6 @@ struct GateDecompositionPattern final // p = -1.0 * p; // p += matrix4x4::Constant(0.0); // ensure no -0.0 exists } - assert(std::abs(p.determinant() - 1.0) < 1e-13); matrix4x4 temp = dReal.asDiagonal(); temp *= IM; @@ -907,6 +906,7 @@ struct GateDecompositionPattern final // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); helpers::print(p, "P", true); + assert(std::abs(p.determinant() - 1.0) < 1e-13); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 matrix4x4 k1 = uP * p * temp; @@ -975,7 +975,7 @@ struct GateDecompositionPattern final globalPhase -= qc::PI_2; } helpers::print(cs, "CS (2)", true); - auto [a, b, c] = std::tie(cs[2], cs[1], cs[3]); + auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; From 388ca6baf38bff2c09e2c9148cc36b57e2216300 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 13 Nov 2025 11:12:04 +0100 Subject: [PATCH 056/100] implement algorithm based on quantumflow.canonical_decomposition --- .../Transforms/GateDecompositionPattern.cpp | 155 ++++++++++++------ 1 file changed, 102 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 24e02c46e..1dd3b11bc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -16,8 +16,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -846,62 +846,109 @@ struct GateDecompositionPattern final // make sure determinant of sqrt(eigenvalues) is 1.0 assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < 1e-13); - // see - // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, - // Step 7 - rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; - helpers::print(dReal, "D_REAL", true); - dReal(3) = -dReal(0) - dReal(1) - dReal(2); - Eigen::Vector cs{}; - for (int i = 0; i < static_cast(cs.size()); ++i) { - assert(i < dReal.size()); - cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); - } - helpers::print(cs, "CS (1)", true); - - decltype(cs) cstemp; - llvm::transform(cs, cstemp.begin(), [](auto&& x) { - auto tmp = remEuclid(x, qc::PI_2); - return std::min(tmp, qc::PI_2 - tmp); - }); - std::array order{ - 0, 1, 2}; // TODO: needs to be adjusted depending on eigenvector - // order in eigen decomposition algorithm? - llvm::stable_sort(order, - [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - // llvm::stable_sort(order, [&](fp a, fp b) { - // auto tmp1 = remEuclid(cs[a], qc::PI_2); - // tmp1 = std::min(tmp1, qc::PI_2 - tmp1); - // auto tmp2 = remEuclid(cs[b], qc::PI_2); - // tmp2 = std::min(tmp2, qc::PI_2 - tmp2); - // return tmp1 < tmp2; - // }); - std::tie(order[0], order[1], order[2]) = - std::tuple{order[1], order[2], order[0]}; - std::tie(cs[0], cs[1], cs[2]) = - std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; - std::tie(dReal(0), dReal(1), dReal(2)) = - std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - helpers::print(dReal, "D_REAL (sorted)", true); - - // swap columns of p according to order - matrix4x4 pOrig = p; - for (int i = 0; i < static_cast(order.size()); ++i) { - p.col(i) = pOrig.col(order[i]); + diagonal4x4 q = d.cwiseSqrt(); + auto det_q = q.prod(); + if (det_q.real() < 0.0) { + q[0] *= -1.0; } + // constrain to weyl + auto constrain_to_weyl = [](diagonal4x4 q) { + auto in_weyl = [](fp tx, fp ty, fp tz) { + return (0.5 >= tx && tx >= ty && ty >= tz && tz >= 0) || + (0.5 >= (1 - tx) && (1 - tx) >= ty && ty >= tz && tz > 0); + }; + auto lambdas_to_coords = [](diagonal4x4 lambdas) { + // [2, eq.11], but using [1]s coordinates. + constexpr fp TOLERANCE = 1e-13; + + auto [l1, l2, _, l4] = + std::array{lambdas[0], lambdas[1], lambdas[2], lambdas[3]}; + auto c1 = std::real(IM * std::log(l1 * l2)); + auto c2 = std::real(IM * std::log(l2 * l4)); + auto c3 = std::real(IM * std::log(l1 * l4)); + Eigen::Vector coords{c1, c2, c3}; + coords /= qc::PI; + + // if coords[i] == 1, then coords[i] = -1, else coords[i] = coords[i] + coords = (coords - decltype(coords)::Ones()) + .cwiseAbs() + .cwiseLess(TOLERANCE) + .select(-decltype(coords)::Ones(), coords) + .eval(); + if (coords.cwiseLess(0.0).all()) { + coords += decltype(coords)::Ones(); + } - if (p.determinant().real() < 0.0) { - std::cerr << "SECOND CORRECTION?\n"; - auto lastColumnIndex = p.cols() - 1; - p.col(lastColumnIndex) *= -1.0; - } else { - // p = -1.0 * p; - // p += matrix4x4::Constant(0.0); // ensure no -0.0 exists + // If we're close to the boundary, floating point errors can conspire + // to make it seem that we're never on the inside + // Fix: If near boundary, reset to boundary + + // Left + if (std::abs(coords[0] - coords[1]) < TOLERANCE) { + coords[1] = coords[0]; + } + + // Front + if (std::abs(coords[1] - coords[2]) < TOLERANCE) { + coords[2] = coords[1]; + } + + // Right + if (std::abs(coords[0] - coords[1] - 1.0 / 2.0) < TOLERANCE) { + coords[1] = coords[0] - 1.0 / 2.0; + } + + // Base + coords = + (coords.array() < 0).select(decltype(coords)::Zero(), coords); + + return coords; + }; + + auto permutation = std::array{0, 1, 2, 3}; + while (true) { + for (auto signs : + std::array, 4>{{{1, 1, 1, 1}, + {1, 1, -1, -1}, + {-1, 1, -1, 1}, + {1, -1, -1, 1}}}) { + decltype(q) signed_lambdas = q.cwiseProduct(signs); + // reorder according to permutation + decltype(signed_lambdas) lambdas_perm; + for (std::size_t i = 0; i < permutation.size(); ++i) { + lambdas_perm[i] = signed_lambdas[permutation[i]]; + } + + auto coords = lambdas_to_coords(lambdas_perm); + + if (in_weyl(coords[0], coords[1], coords[2])) { + return std::make_tuple(coords, permutation, signs); + } + } + if (!std::ranges::next_permutation(permutation).found) { + throw std::runtime_error{ + "Unable to find permutation to calculate Weyl coordinates!"}; + } + } + }; + + auto [cs, permutation, signs] = constrain_to_weyl(q); + + q = q.cwiseProduct(signs); + auto origQ = q; + matrix4x4 origP = signs.asDiagonal() * p.transpose(); + assert(permutation.size() == q.size()); + assert(permutation.size() == p.cols()); + for (std::size_t i = 0; i < permutation.size(); ++i) { + q[i] = origQ[permutation[i]]; + p.row(i) = origP.row(permutation[i]); } + p.transposeInPlace(); + matrix4x4 temp = q.asDiagonal(); + temp = temp.conjugate(); + + - matrix4x4 temp = dReal.asDiagonal(); - temp *= IM; - temp = temp.exp(); // temp = temp.conjugate(); // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); @@ -912,10 +959,12 @@ struct GateDecompositionPattern final matrix4x4 k1 = uP * p * temp; helpers::print(k1, "K1 (1)", true); assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal + assert(k1.determinant().real() > 0.0); k1 = magicBasisTransform(k1, MagicBasisTransform::Into); matrix4x4 k2 = p.transpose().conjugate(); helpers::print(k2, "K2 (1)", true); assert((k2.transpose() * k2).isIdentity()); // k2 must be orthogonal + assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); From fbd6647c13aac32885fa703a09786d3f5d9a5e33 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 13 Nov 2025 18:51:08 +0100 Subject: [PATCH 057/100] sanity checks and stuff --- .../Transforms/GateDecompositionPattern.cpp | 211 ++++++++---------- 1 file changed, 96 insertions(+), 115 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 1dd3b11bc..030bdc1be 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -63,17 +63,7 @@ struct GateDecompositionPattern final return mlir::failure(); } - matrix4x4 unitaryMatrix = - helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); - int i{}; - for (auto&& gate : series.gates) { - auto gateMatrix = - getTwoQubitMatrix({.type = helpers::getQcType(gate.op), - .parameter = helpers::getParameters(gate.op), - .qubitId = gate.qubitIds}); - unitaryMatrix = gateMatrix * unitaryMatrix; - helpers::print(gateMatrix, "GATE MATRIX " + std::to_string(i++), true); - } + matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); helpers::print(unitaryMatrix, "UNITARY MATRIX", true); auto decomposer = TwoQubitBasisDecomposer::newInner(); @@ -144,6 +134,19 @@ struct GateDecompositionPattern final return result; } + matrix4x4 getUnitaryMatrix() { + matrix4x4 unitaryMatrix = + helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); + for (auto&& gate : gates) { + auto gateMatrix = + getTwoQubitMatrix({.type = helpers::getQcType(gate.op), + .parameter = helpers::getParameters(gate.op), + .qubitId = gate.qubitIds}); + unitaryMatrix = gateMatrix * unitaryMatrix; + } + return unitaryMatrix; + } + private: explicit TwoQubitSeries(UnitaryInterface initialOperation) { auto&& in = initialOperation.getAllInQubits(); @@ -310,7 +313,12 @@ struct GateDecompositionPattern final } std::cerr << '\n'; std::cerr << "GATE SEQUENCE!: " << std::flush; + matrix4x4 unitaryMatrix = + helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); for (auto&& gate : sequence.gates) { + auto gateMatrix = getTwoQubitMatrix(gate); + unitaryMatrix = gateMatrix * unitaryMatrix; + std::cerr << qc::toString(gate.type) << ", "; if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; @@ -348,6 +356,9 @@ struct GateDecompositionPattern final throw std::runtime_error{"Unknown gate type!"}; } } + std::cerr << '\n'; + helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY", true); + helpers::print(unitaryMatrix, "RESULT UNITARY MATRIX", true); rewriter.replaceAllUsesWith(series.outQubits, inQubits); for (auto&& gate : llvm::reverse(series.gates)) { @@ -443,41 +454,9 @@ struct GateDecompositionPattern final decomposeTwoQubitProductGate(matrix4x4 specialUnitary) { // see pennylane math.decomposition.su2su2_to_tensor_products // or "7.1 Kronecker decomposition" in on_gates.pdf - helpers::print(specialUnitary, "SPECIAL_UNITARY"); - - // Step 1: Reshape and permute C like in Python - // C (4x4) is considered as (2,2,2,2) tensor, transpose(0,2,1,3) - matrix4x4 C; - - // Perform the same rearrangement as Python's transpose(0,2,1,3) - // This swaps certain 2x2 blocks in C_in. - // The pattern is determined by the tensor index mapping. - for (int i = 0; i < 2; ++i) - for (int j = 0; j < 2; ++j) - for (int k = 0; k < 2; ++k) - for (int l = 0; l < 2; ++l) { - int row_in = 2 * i + k; - int col_in = 2 * j + l; - int row_out = 2 * i + j; - int col_out = 2 * k + l; - C(row_out, col_out) = specialUnitary(row_in, col_in); - } - - // Step 2: SVD - Eigen::JacobiSVD svd(C, Eigen::ComputeFullU | Eigen::ComputeFullV); - Eigen::Vector4d singularValues = svd.singularValues(); - matrix4x4 U = svd.matrixU(); - matrix4x4 V = svd.matrixV(); - - // Step 3: Extract first singular component - double s = std::sqrt(singularValues(0)); - - matrix2x2 A = s * U.col(0).reshaped(2, 2); - matrix2x2 B = s * V.col(0).reshaped(2, 2).transpose(); // vh[0, :] in numpy == V^T.row(0) - - // return {A, B, s}; - + // or quantumflow.kronecker_decomposition + helpers::print(specialUnitary, "SPECIAL_UNITARY"); // first quadrant matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, {specialUnitary(1, 0), specialUnitary(1, 1)}}; @@ -488,7 +467,7 @@ struct GateDecompositionPattern final r = matrix2x2{{specialUnitary(2, 0), specialUnitary(2, 1)}, {specialUnitary(3, 0), specialUnitary(3, 1)}}; detR = r.determinant(); - std::cerr << "DET_R CORRECTION: " << detR << '\n'; + std::cerr << "DET_R CORRECTION: " << detR << '\n'; } if (std::abs(detR) < 0.1) { throw std::runtime_error{ @@ -595,6 +574,16 @@ struct GateDecompositionPattern final {{0., -sinTheta}, C_ZERO, C_ZERO, cosTheta}}; } + static matrix4x4 ryyMatrix(const fp theta) { + const auto cosTheta = std::cos(theta / 2.); + const auto sinTheta = std::sin(theta / 2.); + + return matrix4x4{{{cosTheta, 0, 0, {0., sinTheta}}, + {0, cosTheta, {0., -sinTheta}, 0}, + {0, {0., -sinTheta}, cosTheta, 0}, + {std::complex{0., sinTheta}, 0, 0, cosTheta}}}; + } + static matrix4x4 rzzMatrix(const fp theta) { const auto cosTheta = std::cos(theta / 2.); const auto sinTheta = std::sin(theta / 2.); @@ -719,15 +708,13 @@ struct GateDecompositionPattern final {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } } - if (gate.type == qc::RZ) { - throw std::invalid_argument{"RZ for two-qubit gate matrix"}; + if (gate.type == qc::RXX) { // TODO: check qubit order - return rzzMatrix(gate.parameter[0]); + return rxxMatrix(gate.parameter[0]); } - if (gate.type == qc::RX) { - throw std::invalid_argument{"RX for two-qubit gate matrix"}; + if (gate.type == qc::RYY) { // TODO: check qubit order - return rxxMatrix(gate.parameter[0]); + return ryyMatrix(gate.parameter[0]); } if (gate.type == qc::RZZ) { // TODO: check qubit order @@ -748,9 +735,9 @@ struct GateDecompositionPattern final fp c; fp globalPhase; /** - * - k2r - X - k1r - - * X - * - k2l - X - k1l - + * q1 - k2r - C - k1r - + * A + * q0 - k2l - N - k1l - */ matrix2x2 k1l; matrix2x2 k2l; @@ -809,8 +796,8 @@ struct GateDecompositionPattern final // using the same rng values rules out possible RNG differences // as the root cause of a test failure if (i == 0) { - randA = 1.2602066112249388; randB = 0.22317849046722027; + randA = 1.0 - randB; } else { randA = dist(state); randB = dist(state); @@ -826,7 +813,7 @@ struct GateDecompositionPattern final matrix4x4 compare = pInner * diagD * pInner.transpose(); helpers::print(compare, "COMPARE"); - found = (compare - m2).cwiseAbs().cwiseLessOrEqual(1.0e-13).all(); + found = compare.isApprox(m2, 1e-13); if (found) { // p are the eigenvectors which are decomposed into the // single-qubit gates surrounding the canonical gate @@ -933,6 +920,7 @@ struct GateDecompositionPattern final }; auto [cs, permutation, signs] = constrain_to_weyl(q); + cs *= -qc::PI_2; q = q.cwiseProduct(signs); auto origQ = q; @@ -941,14 +929,12 @@ struct GateDecompositionPattern final assert(permutation.size() == p.cols()); for (std::size_t i = 0; i < permutation.size(); ++i) { q[i] = origQ[permutation[i]]; - p.row(i) = origP.row(permutation[i]); + p.col(i) = origP.row(permutation[i]); } - p.transposeInPlace(); + // p.transposeInPlace(); matrix4x4 temp = q.asDiagonal(); temp = temp.conjugate(); - - // temp = temp.conjugate(); // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); @@ -967,64 +953,59 @@ struct GateDecompositionPattern final assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); + helpers::print( + (k1 * + magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * + k2) + .eval(), + "SANITY CHECK (1)", true); + assert((k1 * + magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * + k2) + .isApprox(u, 1e-8)); + auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); globalPhase += phase_l + phase_r; - // Flip into Weyl chamber - if (cs[0] > qc::PI_2) { - cs[0] -= 3.0 * qc::PI_2; - K1l = K1l * IPY; - K1r = K1r * IPY; - globalPhase += qc::PI_2; - } - if (cs[1] > qc::PI_2) { - cs[1] -= 3.0 * qc::PI_2; - K1l = K1l * IPX; - K1r = K1r * IPX; - globalPhase += qc::PI_2; - } - auto conjs = 0; - if (cs[0] > qc::PI_4) { - cs[0] = qc::PI_2 - cs[0]; - K1l = K1l * IPY; - K2r = IPY * K2r; - conjs += 1; - globalPhase -= qc::PI_2; - } - if (cs[1] > qc::PI_4) { - cs[1] = qc::PI_2 - cs[1]; - K1l = K1l * IPX; - K2r = IPX * K2r; - conjs += 1; - globalPhase += qc::PI_2; - if (conjs == 1) { - globalPhase -= qc::PI; - } - } - if (cs[2] > qc::PI_2) { - cs[2] -= 3.0 * qc::PI_2; - K1l = K1l * IPZ; - K1r = K1r * IPZ; - globalPhase += qc::PI_2; - if (conjs == 1) { - globalPhase -= qc::PI; - } - } - if (conjs == 1) { - cs[2] = qc::PI_2 - cs[2]; - K1l = K1l * IPZ; - K2r = IPZ * K2r; - globalPhase += qc::PI_2; - } - if (cs[2] > qc::PI_4) { - cs[2] -= qc::PI_2; - K1l = K1l * IPZ; - K1r = K1r * IPZ; - globalPhase -= qc::PI_2; - } + helpers::print(K1l, "K1l (1)", true); + helpers::print(K2l, "K2l (1)", true); + helpers::print(K1r, "K1r (1)", true); + helpers::print(K2r, "K2r (1)", true); + helpers::print(cs, "CS (2)", true); - auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); + helpers::print(K1l, "K1l (2)", true); + helpers::print(K2l, "K2l (2)", true); + helpers::print(K1r, "K1r (2)", true); + helpers::print(K2r, "K2r (2)", true); + auto [a, b, c] = std::tie(cs[0], cs[1], cs[2]); + auto getCanonicalMatrix = [](fp a, fp b, fp c) -> matrix4x4 { + auto xx = getTwoQubitMatrix({ + .type = qc::RXX, + .parameter = {a}, + .qubitId = {0, 1}, + }); + auto yy = getTwoQubitMatrix({ + .type = qc::RYY, + .parameter = {b}, + .qubitId = {0, 1}, + }); + auto zz = getTwoQubitMatrix({ + .type = qc::RZZ, + .parameter = {c}, + .qubitId = {0, 1}, + }); + return zz * yy * xx; + }; + helpers::print(getCanonicalMatrix(a * 2.0, b * 2.0, c * 2.0), + "SANITY CHECK (2.1)", true); + helpers::print( + magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into), + "SANITY CHECK (2.2)", true); + // assert(getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) + // .isApprox(magicBasisTransform(temp.conjugate(), + // MagicBasisTransform::Into), + // 1e-8)); auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; @@ -1609,7 +1590,7 @@ struct GateDecompositionPattern final << "; basis_fid: " << actualBasisFidelity << "; Traces: " << traces[0] << ", " << traces[1] << ", " << traces[2] << ", " << traces[3]; - std::cerr << "\nDecomposition:\n"; + std::cerr << "\nDecompositions:\n"; for (auto x : decomposition) { helpers::print(x, "", true); } From db0933589f3239318d73f25a78362dd6dc80a4f5 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 13 Nov 2025 19:29:41 +0100 Subject: [PATCH 058/100] add original implementation again which seems to kind of work? --- .../Transforms/GateDecompositionPattern.cpp | 112 +++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 030bdc1be..f649b86b4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -319,7 +319,7 @@ struct GateDecompositionPattern final auto gateMatrix = getTwoQubitMatrix(gate); unitaryMatrix = gateMatrix * unitaryMatrix; - std::cerr << qc::toString(gate.type) << ", "; + std::cerr << qc::toString(gate.type) << "(" << (gate.parameter.empty() ? -1000.0 : gate.parameter[0]) << ")" << ", "; if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubitId.size() > 1) { @@ -921,6 +921,7 @@ struct GateDecompositionPattern final auto [cs, permutation, signs] = constrain_to_weyl(q); cs *= -qc::PI_2; + matrix4x4 p2 = p; q = q.cwiseProduct(signs); auto origQ = q; @@ -929,12 +930,64 @@ struct GateDecompositionPattern final assert(permutation.size() == p.cols()); for (std::size_t i = 0; i < permutation.size(); ++i) { q[i] = origQ[permutation[i]]; - p.col(i) = origP.row(permutation[i]); + p2.col(i) = origP.row(permutation[i]); } // p.transposeInPlace(); matrix4x4 temp = q.asDiagonal(); temp = temp.conjugate(); + // see + // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, + // Step 7 + rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; + helpers::print(dReal, "D_REAL", true); + dReal(3) = -dReal(0) - dReal(1) - dReal(2); + for (int i = 0; i < static_cast(cs.size()); ++i) { + assert(i < dReal.size()); + cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); + } + helpers::print(cs, "CS (1)", true); + + decltype(cs) cstemp; + llvm::transform(cs, cstemp.begin(), [](auto&& x) { + auto tmp = remEuclid(x, qc::PI_2); + return std::min(tmp, qc::PI_2 - tmp); + }); + std::array order{ + 0, 1, 2}; // TODO: needs to be adjusted depending on eigenvector + // order in eigen decomposition algorithm? + llvm::stable_sort(order, + [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); + // llvm::stable_sort(order, [&](fp a, fp b) { + // auto tmp1 = remEuclid(cs[a], qc::PI_2); + // tmp1 = std::min(tmp1, qc::PI_2 - tmp1); + // auto tmp2 = remEuclid(cs[b], qc::PI_2); + // tmp2 = std::min(tmp2, qc::PI_2 - tmp2); + // return tmp1 < tmp2; + // }); + std::tie(order[0], order[1], order[2]) = + std::tuple{order[1], order[2], order[0]}; + std::tie(cs[0], cs[1], cs[2]) = + std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; + std::tie(dReal(0), dReal(1), dReal(2)) = + std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; + helpers::print(dReal, "D_REAL (sorted)", true); + + // swap columns of p according to order + matrix4x4 pOrig = p; + for (int i = 0; i < static_cast(order.size()); ++i) { + p.col(i) = pOrig.col(order[i]); + } + if (p.determinant().real() < 0.0) { + std::cerr << "SECOND CORRECTION?\n"; + auto lastColumnIndex = p.cols() - 1; + p.col(lastColumnIndex) *= -1.0; + } + + temp = dReal.asDiagonal(); + temp *= IM; + temp = temp.exp(); + // temp = temp.conjugate(); // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); @@ -968,6 +1021,59 @@ struct GateDecompositionPattern final auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); globalPhase += phase_l + phase_r; + // Flip into Weyl chamber + if (cs[0] > qc::PI_2) { + cs[0] -= 3.0 * qc::PI_2; + K1l = K1l * IPY; + K1r = K1r * IPY; + globalPhase += qc::PI_2; + } + if (cs[1] > qc::PI_2) { + cs[1] -= 3.0 * qc::PI_2; + K1l = K1l * IPX; + K1r = K1r * IPX; + globalPhase += qc::PI_2; + } + auto conjs = 0; + if (cs[0] > qc::PI_4) { + cs[0] = qc::PI_2 - cs[0]; + K1l = K1l * IPY; + K2r = IPY * K2r; + conjs += 1; + globalPhase -= qc::PI_2; + } + if (cs[1] > qc::PI_4) { + cs[1] = qc::PI_2 - cs[1]; + K1l = K1l * IPX; + K2r = IPX * K2r; + conjs += 1; + globalPhase += qc::PI_2; + if (conjs == 1) { + globalPhase -= qc::PI; + } + } + if (cs[2] > qc::PI_2) { + cs[2] -= 3.0 * qc::PI_2; + K1l = K1l * IPZ; + K1r = K1r * IPZ; + globalPhase += qc::PI_2; + if (conjs == 1) { + globalPhase -= qc::PI; + } + } + if (conjs == 1) { + cs[2] = qc::PI_2 - cs[2]; + K1l = K1l * IPZ; + K2r = IPZ * K2r; + globalPhase += qc::PI_2; + } + if (cs[2] > qc::PI_4) { + cs[2] -= qc::PI_2; + K1l = K1l * IPZ; + K1r = K1r * IPZ; + globalPhase -= qc::PI_2; + } + helpers::print(K1l, "K1l (1)", true); helpers::print(K2l, "K2l (1)", true); helpers::print(K1r, "K1r (1)", true); @@ -978,7 +1084,7 @@ struct GateDecompositionPattern final helpers::print(K2l, "K2l (2)", true); helpers::print(K1r, "K1r (2)", true); helpers::print(K2r, "K2r (2)", true); - auto [a, b, c] = std::tie(cs[0], cs[1], cs[2]); + auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); auto getCanonicalMatrix = [](fp a, fp b, fp c) -> matrix4x4 { auto xx = getTwoQubitMatrix({ .type = qc::RXX, From afd19ee86a21d7dbf76ec2bbf1562f9f49d68dc7 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 14 Nov 2025 12:16:54 +0100 Subject: [PATCH 059/100] generate openqasm code of generated sequence --- .../Transforms/GateDecompositionPattern.cpp | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index f649b86b4..0787abc80 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -312,21 +312,39 @@ struct GateDecompositionPattern final std::cerr << gate.op->getName().stripDialect().str() << ", "; } std::cerr << '\n'; - std::cerr << "GATE SEQUENCE!: " << std::flush; + std::cerr << "GATE SEQUENCE!: \n"; matrix4x4 unitaryMatrix = helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); for (auto&& gate : sequence.gates) { auto gateMatrix = getTwoQubitMatrix(gate); unitaryMatrix = gateMatrix * unitaryMatrix; - std::cerr << qc::toString(gate.type) << "(" << (gate.parameter.empty() ? -1000.0 : gate.parameter[0]) << ")" << ", "; + if (gate.type == qc::X && gate.qubitId.size() == 2) { + std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[1], + gate.qubitId[0]); + } else if (gate.parameter.empty()) { + std::cerr << std::format( + "{}() q[{}] {};", qc::toString(gate.type), gate.qubitId[0], + (gate.qubitId.size() > 1 + ? (", q[" + std::to_string(gate.qubitId[1]) + "]") + : std::string{})); + } else { + std::cerr << std::format( + "{}({:.5}*pi) q[{}] {};", qc::toString(gate.type), + gate.parameter[0] / qc::PI, gate.qubitId[0], + (gate.qubitId.size() > 1 + ? (", q[" + std::to_string(gate.qubitId[1]) + "]") + : std::string{})); + } + std::cerr << '\n'; if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubitId.size() > 1) { inCtrlQubits.push_back(inQubits[gate.qubitId[1]]); } - auto newGate = createGate(rewriter, location, {inQubits[0]}, - inCtrlQubits, gate.parameter); + auto newGate = + createGate(rewriter, location, {inQubits[gate.qubitId[0]]}, + inCtrlQubits, gate.parameter); updateInQubits(gate, newGate); } else if (gate.type == qc::RX) { mlir::SmallVector qubits; @@ -358,7 +376,8 @@ struct GateDecompositionPattern final } std::cerr << '\n'; helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY", true); - helpers::print(unitaryMatrix, "RESULT UNITARY MATRIX", true); + helpers::print((unitaryMatrix * std::exp(IM * sequence.globalPhase)).eval(), + "RESULT UNITARY MATRIX", true); rewriter.replaceAllUsesWith(series.outQubits, inQubits); for (auto&& gate : llvm::reverse(series.gates)) { From 79ced7bc39d1c490f8b9048007e2833188b1e156 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 14 Nov 2025 12:17:49 +0100 Subject: [PATCH 060/100] some more small fixes, maybe breakthrough? --- .../Transforms/GateDecompositionPattern.cpp | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 0787abc80..7f5e35fa3 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -433,8 +433,7 @@ struct GateDecompositionPattern final // Wrap angle into interval [-π,π). If within atol of the endpoint, clamp // to -π - static fp mod2pi(fp angle, - fp angleZeroEpsilon = std::numeric_limits::epsilon()) { + static fp mod2pi(fp angle, fp angleZeroEpsilon = 1e-13) { // remEuclid() isn't exactly the same as Python's % operator, but // because the RHS here is a constant and positive it is effectively // equivalent for this case @@ -708,21 +707,21 @@ struct GateDecompositionPattern final } if (gate.qubitId.size() == 1) { if (gate.qubitId[0] == 0) { - return kroneckerProduct(IDENTITY_GATE, getSingleQubitMatrix(gate)); + return kroneckerProduct(getSingleQubitMatrix(gate), IDENTITY_GATE); } if (gate.qubitId[0] == 1) { - return kroneckerProduct(getSingleQubitMatrix(gate), IDENTITY_GATE); + return kroneckerProduct(IDENTITY_GATE, getSingleQubitMatrix(gate)); } throw std::logic_error{"Invalid qubit ID in getTwoQubitMatrix"}; } if (gate.qubitId.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubitId == llvm::SmallVector{1, 0}) { + if (gate.qubitId == llvm::SmallVector{0, 1}) { return matrix4x4{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubitId == llvm::SmallVector{0, 1}) { + if (gate.qubitId == llvm::SmallVector{1, 0}) { return matrix4x4{ {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } @@ -771,7 +770,7 @@ struct GateDecompositionPattern final static TwoQubitWeylDecomposition newInner(matrix4x4 unitaryMatrix, std::optional fidelity, std::optional specialization) { - auto& u = unitaryMatrix; + auto u = unitaryMatrix; auto detU = u.determinant(); std::cerr << "DET_U: " << detU << '\n'; auto detPow = std::pow(detU, static_cast(-0.25)); @@ -815,8 +814,8 @@ struct GateDecompositionPattern final // using the same rng values rules out possible RNG differences // as the root cause of a test failure if (i == 0) { + randA = 1.2602066112249388; randB = 0.22317849046722027; - randA = 1.0 - randB; } else { randA = dist(state); randB = dist(state); @@ -997,7 +996,7 @@ struct GateDecompositionPattern final for (int i = 0; i < static_cast(order.size()); ++i) { p.col(i) = pOrig.col(order[i]); } - if (p.determinant().real() < 0.0) { + if (p.determinant().real() < 0.0) { std::cerr << "SECOND CORRECTION?\n"; auto lastColumnIndex = p.cols() - 1; p.col(lastColumnIndex) *= -1.0; @@ -1038,6 +1037,8 @@ struct GateDecompositionPattern final auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); + assert(helpers::kroneckerProduct(K1l, K1r).isApprox(k1, 1e-9)); + assert(helpers::kroneckerProduct(K2l, K2r).isApprox(k2, 1e-9)); globalPhase += phase_l + phase_r; // Flip into Weyl chamber @@ -1122,15 +1123,24 @@ struct GateDecompositionPattern final }); return zz * yy * xx; }; - helpers::print(getCanonicalMatrix(a * 2.0, b * 2.0, c * 2.0), + helpers::print(getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0), "SANITY CHECK (2.1)", true); - helpers::print( - magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into), - "SANITY CHECK (2.2)", true); - // assert(getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) - // .isApprox(magicBasisTransform(temp.conjugate(), - // MagicBasisTransform::Into), - // 1e-8)); + helpers::print(helpers::kroneckerProduct(K1l, K1r), "SANITY CHECK (2.2)", + true); + helpers::print(helpers::kroneckerProduct(K2l, K2r), "SANITY CHECK (2.3)", + true); + helpers::print((helpers::kroneckerProduct(K1l, K1r) * + getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * + helpers::kroneckerProduct(K2l, K2r) * + std::exp(IM * globalPhase)) + .eval(), + "SANITY CHECK (2.x)", true); + std::cerr << "gphase: " << globalPhase << ", phase_l: " << phase_l + << ", phase_r: " << phase_r << '\n'; + assert((helpers::kroneckerProduct(K1l, K1r) * + getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * + helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) + .isApprox(unitaryMatrix, 1e-8)); auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; @@ -1563,10 +1573,8 @@ struct GateDecompositionPattern final auto basisDecomposer = TwoQubitWeylDecomposition::newInner( getTwoQubitMatrix(basisGate), DEFAULT_FIDELITY, std::nullopt); auto superControlled = - relativeEq(basisDecomposer.a, qc::PI_4, - std::numeric_limits::epsilon(), 1e-09) && - relativeEq(basisDecomposer.c, 0.0, std::numeric_limits::epsilon(), - 1e-09); + relativeEq(basisDecomposer.a, qc::PI_4, 1e-13, 1e-09) && + relativeEq(basisDecomposer.c, 0.0, 1e-13, 1e-09); // Create some useful matrices U1, U2, U3 are equivalent to the basis, // expand as Ui = Ki1.Ubasis.Ki2 From 65057a0fe7f4d25d83870b1659b6268163ac04a0 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 14 Nov 2025 14:09:28 +0100 Subject: [PATCH 061/100] rework getTwoQubitSeries logic --- .../Transforms/GateDecompositionPattern.cpp | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 7f5e35fa3..e50b838ff 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -115,20 +115,34 @@ struct GateDecompositionPattern final return false; }; - bool isFirstQubitOngoing = result.outQubits[0] != mlir::Value{}; - bool isSecondQubitOngoing = result.outQubits[1] != mlir::Value{}; - while (isFirstQubitOngoing || isSecondQubitOngoing) { - // TODO: can cause issues; instead: per iteration, collect all - // single-qubit operations, then take one two-qubit operation; repeat - if (result.outQubits[0]) { - assert(result.outQubits[0].hasOneUse()); - isFirstQubitOngoing = - findNextInSeries(*result.outQubits[0].getUsers().begin()); + auto getUser = + [](mlir::Value qubit, auto&& filter) -> std::optional { + if (qubit) { + assert(qubit.hasOneUse()); + auto user = + mlir::dyn_cast(*qubit.getUsers().begin()); + if (user && filter(user)) { + return user; + } } - if (result.outQubits[1]) { - assert(result.outQubits[1].hasOneUse()); - isSecondQubitOngoing = - findNextInSeries(*result.outQubits[1].getUsers().begin()); + return std::nullopt; + }; + + bool foundGate = true; + while (foundGate) { + // collect all available single-qubit operations + for (std::size_t i = 0; i < result.outQubits.size(); ++i) { + while (auto user = getUser(result.outQubits[i], &helpers::isSingleQubitOperation)) { + result.appendSingleQubitGate(*user); + foundGate = true; + } + } + + for (std::size_t i = 0; i < result.outQubits.size(); ++i) { + while (auto user = getUser(result.outQubits[i], &helpers::isTwoQubitOperation)) { + result.appendTwoQubitGate(*user); + foundGate = true; + } } } return result; From 61e56780c1de3ed5423eae03d25f4546889123e6 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 14 Nov 2025 14:10:16 +0100 Subject: [PATCH 062/100] generate openqasm code of found series --- .../Transforms/GateDecompositionPattern.cpp | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e50b838ff..efae38941 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -323,7 +323,27 @@ struct GateDecompositionPattern final std::cerr << "SERIES: "; for (auto&& gate : series.gates) { - std::cerr << gate.op->getName().stripDialect().str() << ", "; + auto name = gate.op->getName().stripDialect().str(); + if (name == "x" && gate.qubitIds.size() == 2) { + // controls come first + std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitIds[0], + gate.qubitIds[1]); + } else if (name == "i") { + } else if (gate.op.getParams().empty()) { + std::cerr << std::format( + "{}() q[{}] {};", name, gate.qubitIds[0], + (gate.qubitIds.size() > 1 + ? (", q[" + std::to_string(gate.qubitIds[1]) + "]") + : std::string{})); + } else { + auto parameter = helpers::getParameters(gate.op)[0]; + std::cerr << std::format( + "{}({:.5}*pi) q[{}] {};", name, parameter / qc::PI, + gate.qubitIds[0], + (gate.qubitIds.size() > 1 + ? (", q[" + std::to_string(gate.qubitIds[1]) + "]") + : std::string{})); + } } std::cerr << '\n'; std::cerr << "GATE SEQUENCE!: \n"; @@ -334,8 +354,9 @@ struct GateDecompositionPattern final unitaryMatrix = gateMatrix * unitaryMatrix; if (gate.type == qc::X && gate.qubitId.size() == 2) { - std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[1], - gate.qubitId[0]); + // controls come first + std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[0], + gate.qubitId[1]); } else if (gate.parameter.empty()) { std::cerr << std::format( "{}() q[{}] {};", qc::toString(gate.type), gate.qubitId[0], From 812fcd794b3a06ebfd6510ad438689139a3f3bd0 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 14 Nov 2025 14:12:36 +0100 Subject: [PATCH 063/100] fix new getTwoQubitSeries logic --- .../Transforms/GateDecompositionPattern.cpp | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index efae38941..9b60002c0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -100,23 +100,8 @@ struct GateDecompositionPattern final [[nodiscard]] static TwoQubitSeries getTwoQubitSeries(UnitaryInterface op) { TwoQubitSeries result(op); - auto findNextInSeries = [&](mlir::Operation* user) { - auto userUnitary = mlir::dyn_cast(user); - if (!userUnitary) { - return false; - } - - if (helpers::isSingleQubitOperation(userUnitary)) { - return result.appendSingleQubitGate(userUnitary); - } - if (helpers::isTwoQubitOperation(userUnitary)) { - return result.appendTwoQubitGate(userUnitary); - } - return false; - }; - - auto getUser = - [](mlir::Value qubit, auto&& filter) -> std::optional { + auto getUser = [](mlir::Value qubit, + auto&& filter) -> std::optional { if (qubit) { assert(qubit.hasOneUse()); auto user = @@ -130,18 +115,20 @@ struct GateDecompositionPattern final bool foundGate = true; while (foundGate) { + foundGate = false; // collect all available single-qubit operations for (std::size_t i = 0; i < result.outQubits.size(); ++i) { - while (auto user = getUser(result.outQubits[i], &helpers::isSingleQubitOperation)) { - result.appendSingleQubitGate(*user); - foundGate = true; + while (auto user = getUser(result.outQubits[i], + &helpers::isSingleQubitOperation)) { + foundGate = result.appendSingleQubitGate(*user); + assert(foundGate); // appending a single-qubit gate should not fail } } for (std::size_t i = 0; i < result.outQubits.size(); ++i) { - while (auto user = getUser(result.outQubits[i], &helpers::isTwoQubitOperation)) { - result.appendTwoQubitGate(*user); - foundGate = true; + if (auto user = + getUser(result.outQubits[i], &helpers::isTwoQubitOperation)) { + foundGate = result.appendTwoQubitGate(*user); } } } From 53f53c87f9330a0e63e1f397b84bb0f4337911d3 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sat, 15 Nov 2025 20:20:32 +0100 Subject: [PATCH 064/100] working except global phase? --- .../Transforms/GateDecompositionPattern.cpp | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 9b60002c0..4379511e1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -207,14 +207,22 @@ struct GateDecompositionPattern final firstQubitIt = (firstQubitIt != outQubits.end()) ? firstQubitIt : it; secondQubitIt = (secondQubitIt != outQubits.end()) ? secondQubitIt : it; } - *firstQubitIt = nextGate->getResult(0); std::size_t firstQubitId = std::distance(outQubits.begin(), firstQubitIt); - *secondQubitIt = nextGate->getResult(1); std::size_t secondQubitId = std::distance(outQubits.begin(), secondQubitIt); - gates.push_back( - {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); + if (nextGate.isControlled()) { + // controls of a gate should come first, but are last in the qubit order + gates.push_back( + {.op = nextGate, .qubitIds = {secondQubitId, firstQubitId}}); + *firstQubitIt = nextGate->getResult(1); + *secondQubitIt = nextGate->getResult(0); + } else { + gates.push_back( + {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); + *firstQubitIt = nextGate->getResult(0); + *secondQubitIt = nextGate->getResult(1); + } complexity += 2; return true; } @@ -297,6 +305,7 @@ struct GateDecompositionPattern final auto updateInQubits = [&inQubits](const TwoQubitGateSequence::Gate& gateDescription, auto&& newGate) { + // TODO: need to handle controls differently? auto results = newGate.getAllOutQubits(); if (gateDescription.qubitId.size() == 2) { inQubits[gateDescription.qubitId[0]] = results[0]; @@ -362,10 +371,11 @@ struct GateDecompositionPattern final if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubitId.size() > 1) { - inCtrlQubits.push_back(inQubits[gate.qubitId[1]]); + // controls come first + inCtrlQubits.push_back(inQubits[gate.qubitId[0]]); } auto newGate = - createGate(rewriter, location, {inQubits[gate.qubitId[0]]}, + createGate(rewriter, location, {inQubits[gate.qubitId[1]]}, inCtrlQubits, gate.parameter); updateInQubits(gate, newGate); } else if (gate.type == qc::RX) { @@ -729,21 +739,21 @@ struct GateDecompositionPattern final } if (gate.qubitId.size() == 1) { if (gate.qubitId[0] == 0) { - return kroneckerProduct(getSingleQubitMatrix(gate), IDENTITY_GATE); + return kroneckerProduct(IDENTITY_GATE, getSingleQubitMatrix(gate)); } if (gate.qubitId[0] == 1) { - return kroneckerProduct(IDENTITY_GATE, getSingleQubitMatrix(gate)); + return kroneckerProduct(getSingleQubitMatrix(gate), IDENTITY_GATE); } throw std::logic_error{"Invalid qubit ID in getTwoQubitMatrix"}; } if (gate.qubitId.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubitId == llvm::SmallVector{0, 1}) { + if (gate.qubitId == llvm::SmallVector{1, 0}) { return matrix4x4{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubitId == llvm::SmallVector{1, 0}) { + if (gate.qubitId == llvm::SmallVector{0, 1}) { return matrix4x4{ {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } @@ -1785,14 +1795,18 @@ struct GateDecompositionPattern final }; for (std::size_t i = 0; i < bestNbasis; ++i) { - addEulerDecomposition(2 * i, 0); - addEulerDecomposition((2 * i) + 1, 1); + addEulerDecomposition(2 * i, basisGate.qubitId[0]); + addEulerDecomposition((2 * i) + 1, basisGate.qubitId[1]); gates.gates.push_back(basisGate); } - addEulerDecomposition(2UL * bestNbasis, 0); - addEulerDecomposition((2UL * bestNbasis) + 1, 1); + addEulerDecomposition(2UL * bestNbasis, basisGate.qubitId[0]); + addEulerDecomposition((2UL * bestNbasis) + 1, basisGate.qubitId[1]); + + // large global phases can be generated by the decomposition, thus limit + // it to [-2*pi, +2*pi) + gates.globalPhase = std::fmod(gates.globalPhase, qc::TAU); return gates; } @@ -1852,7 +1866,6 @@ struct GateDecompositionPattern final for (auto&& gate : sequence.gates) { matrix4x4 gateMatrix = getTwoQubitMatrix(gate); - matrix = gateMatrix * matrix; } return matrix; From 1a0eebbca222a2da2add818b48955305f12acee5 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sat, 15 Nov 2025 21:56:35 +0100 Subject: [PATCH 065/100] fix global phase issues --- .../Transforms/GateDecompositionPattern.cpp | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 4379511e1..f12756f3e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -48,10 +48,6 @@ struct GateDecompositionPattern final std::cerr << gate.op->getName().stripDialect().str() << ", "; } std::cerr << '\n'; - static int a{}; - if (a++ > 0) { - return mlir::failure(); - } if (series.gates.size() < 3) { // too short @@ -215,13 +211,13 @@ struct GateDecompositionPattern final // controls of a gate should come first, but are last in the qubit order gates.push_back( {.op = nextGate, .qubitIds = {secondQubitId, firstQubitId}}); - *firstQubitIt = nextGate->getResult(1); - *secondQubitIt = nextGate->getResult(0); + *firstQubitIt = nextGate->getResult(1); + *secondQubitIt = nextGate->getResult(0); } else { gates.push_back( {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); - *firstQubitIt = nextGate->getResult(0); - *secondQubitIt = nextGate->getResult(1); + *firstQubitIt = nextGate->getResult(0); + *secondQubitIt = nextGate->getResult(1); } complexity += 2; return true; @@ -1529,6 +1525,22 @@ struct GateDecompositionPattern final } } specialized.globalPhase += std::arg(tr); + + helpers::print( + (helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * + getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, + specialized.c * -2.0) * + helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * + std::exp(IM * specialized.globalPhase)) + .eval(), + "SANITY CHECK (3)", true); + assert((helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * + getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, + specialized.c * -2.0) * + helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * + std::exp(IM * specialized.globalPhase)) + .isApprox(unitaryMatrix, 1e-8)); + return specialized; } }; @@ -1734,6 +1746,7 @@ struct GateDecompositionPattern final } return minIndex; }; + // number of basis gates that need to be inserted auto bestNbasis = numBasisUses.value_or(getDefaultNbasis()); auto chooseDecomposition = [&]() { if (bestNbasis == 0) { @@ -1789,11 +1802,12 @@ struct GateDecompositionPattern final gates.gates.push_back({.type = gate.type, .parameter = gate.parameter, .qubitId = {qubitId}}); - gates.globalPhase += eulerDecomp->globalPhase; } + gates.globalPhase += eulerDecomp->globalPhase; } }; + // TODO: check if this actually uses the correct qubitIds for (std::size_t i = 0; i < bestNbasis; ++i) { addEulerDecomposition(2 * i, basisGate.qubitId[0]); addEulerDecomposition((2 * i) + 1, basisGate.qubitId[1]); From cb85552b392ff378ea2c73d11ac1659c4af26af8 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 16 Nov 2025 19:16:52 +0100 Subject: [PATCH 066/100] first working snapshot --- .../Transforms/GateDecompositionPattern.cpp | 139 +++++++++++------- 1 file changed, 86 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index f12756f3e..8992bf66b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace mqt::ir::opt { @@ -35,9 +36,46 @@ namespace mqt::ir::opt { */ struct GateDecompositionPattern final : mlir::OpInterfaceRewritePattern { + enum class EulerBasis : std::uint8_t { + U3 = 0, + U321 = 1, + U = 2, + PSX = 3, + U1X = 4, + RR = 5, + ZYZ = 6, + ZXZ = 7, + XZX = 8, + XYX = 9, + ZSXX = 10, + ZSX = 11, + }; + + struct QubitGateSequence { + struct Gate { + qc::OpType type{qc::I}; + llvm::SmallVector parameter; + llvm::SmallVector qubitId = {0}; + }; + std::vector gates; + std::size_t complexity() { + std::size_t c{}; + for (auto&& gate : gates) { + c += getComplexity(gate.type, gate.qubitId.size()); + } + return c; + } + fp globalPhase{}; + }; + using OneQubitGateSequence = QubitGateSequence; + using TwoQubitGateSequence = QubitGateSequence; - explicit GateDecompositionPattern(mlir::MLIRContext* context) - : OpInterfaceRewritePattern(context) {} + explicit GateDecompositionPattern(mlir::MLIRContext* context, + QubitGateSequence::Gate basisGate, + EulerBasis eulerBasis) + : OpInterfaceRewritePattern(context), + decomposerBasisGate{std::move(basisGate)}, + decomposerEulerBasis{eulerBasis} {} mlir::LogicalResult matchAndRewrite(UnitaryInterface op, @@ -62,7 +100,8 @@ struct GateDecompositionPattern final matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); helpers::print(unitaryMatrix, "UNITARY MATRIX", true); - auto decomposer = TwoQubitBasisDecomposer::newInner(); + auto decomposer = TwoQubitBasisDecomposer::newInner( + decomposerBasisGate, DEFAULT_FIDELITY, decomposerEulerBasis); auto sequence = decomposer.twoQubitDecompose( unitaryMatrix, DEFAULT_FIDELITY, true, std::nullopt); if (!sequence) { @@ -82,6 +121,16 @@ struct GateDecompositionPattern final return mlir::success(); } +protected: + [[nodiscard]] static std::size_t getComplexity(qc::OpType /*type*/, + std::size_t numOfQubits) { + if (numOfQubits > 1) { + constexpr std::size_t multiQubitFactor = 10; + return (numOfQubits - 1) * multiQubitFactor; + } + return 1; + } + struct TwoQubitSeries { std::size_t complexity{0}; std::array inQubits; @@ -128,6 +177,7 @@ struct GateDecompositionPattern final } } } + // TODO: need to search and apply for global phase? return result; } @@ -152,17 +202,22 @@ struct GateDecompositionPattern final inQubits = {in[0], mlir::Value{}}; outQubits = {out[0], mlir::Value{}}; gates.push_back({.op = initialOperation, .qubitIds = {0}}); - complexity += 1; } else if (helpers::isTwoQubitOperation(initialOperation)) { inQubits = {in[0], in[1]}; outQubits = {out[0], out[1]}; - gates.push_back({.op = initialOperation, .qubitIds = {0, 1}}); - complexity += 2; + if (initialOperation.isControlled()) { + gates.push_back({.op = initialOperation, .qubitIds = {1, 0}}); + } else { + gates.push_back({.op = initialOperation, .qubitIds = {0, 1}}); + } } + complexity += + getComplexity(helpers::getQcType(initialOperation), in.size()); } /** * @return true if series continues, otherwise false + * (will always return true) */ bool appendSingleQubitGate(UnitaryInterface nextGate) { auto operand = nextGate.getAllInQubits()[0]; @@ -175,7 +230,7 @@ struct GateDecompositionPattern final *it = nextGate->getResult(0); gates.push_back({.op = nextGate, .qubitIds = {qubitId}}); - complexity += 1; + complexity += getComplexity(helpers::getQcType(nextGate), 1); return true; } @@ -206,20 +261,18 @@ struct GateDecompositionPattern final std::size_t firstQubitId = std::distance(outQubits.begin(), firstQubitIt); std::size_t secondQubitId = std::distance(outQubits.begin(), secondQubitIt); + *firstQubitIt = nextGate->getResult(0); + *secondQubitIt = nextGate->getResult(1); if (nextGate.isControlled()) { // controls of a gate should come first, but are last in the qubit order gates.push_back( {.op = nextGate, .qubitIds = {secondQubitId, firstQubitId}}); - *firstQubitIt = nextGate->getResult(1); - *secondQubitIt = nextGate->getResult(0); } else { gates.push_back( {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); - *firstQubitIt = nextGate->getResult(0); - *secondQubitIt = nextGate->getResult(1); } - complexity += 2; + complexity += getComplexity(helpers::getQcType(nextGate), 2); return true; } }; @@ -264,25 +317,6 @@ struct GateDecompositionPattern final inQubits, ctrlQubits, mlir::ValueRange{}); } - struct QubitGateSequence { - struct Gate { - qc::OpType type{qc::I}; - llvm::SmallVector parameter; - llvm::SmallVector qubitId = {0}; - }; - std::vector gates; - std::size_t complexity() { - std::size_t c{}; - for (auto&& gate : gates) { - c += gate.qubitId.size(); - } - return c; - } - fp globalPhase{}; - }; - using OneQubitGateSequence = QubitGateSequence; - using TwoQubitGateSequence = QubitGateSequence; - static void applySeries(mlir::PatternRewriter& rewriter, TwoQubitSeries& series, const TwoQubitGateSequence& sequence) { @@ -433,21 +467,6 @@ struct GateDecompositionPattern final OutOf, }; - enum class EulerBasis : std::uint8_t { - U3 = 0, - U321 = 1, - U = 2, - PSX = 3, - U1X = 4, - RR = 5, - ZYZ = 6, - ZXZ = 7, - XZX = 8, - XYX = 9, - ZSXX = 10, - ZSX = 11, - }; - static constexpr auto SQRT2 = std::numbers::sqrt2_v; static constexpr auto FRAC1_SQRT2 = static_cast(0.707106781186547524400844362104849039); @@ -1578,7 +1597,8 @@ struct GateDecompositionPattern final newInner(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, .parameter = {}, .qubitId = {0, 1}}, - fp basisFidelity = 1.0, EulerBasis eulerBasis = EulerBasis::ZYZ) { + fp basisFidelity = DEFAULT_FIDELITY, + EulerBasis eulerBasis = EulerBasis::ZYZ) { auto relativeEq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& maxRelative) { // Handle same infinities @@ -1809,14 +1829,14 @@ struct GateDecompositionPattern final // TODO: check if this actually uses the correct qubitIds for (std::size_t i = 0; i < bestNbasis; ++i) { - addEulerDecomposition(2 * i, basisGate.qubitId[0]); - addEulerDecomposition((2 * i) + 1, basisGate.qubitId[1]); + addEulerDecomposition(2 * i, 0); + addEulerDecomposition((2 * i) + 1, 1); gates.gates.push_back(basisGate); } - addEulerDecomposition(2UL * bestNbasis, basisGate.qubitId[0]); - addEulerDecomposition((2UL * bestNbasis) + 1, basisGate.qubitId[1]); + addEulerDecomposition(2UL * bestNbasis, 0); + addEulerDecomposition((2UL * bestNbasis) + 1, 1); // large global phases can be generated by the decomposition, thus limit // it to [-2*pi, +2*pi) @@ -2012,7 +2032,11 @@ struct GateDecompositionPattern final } return {gates, globalPhase}; } - }; // namespace mqt::ir::opt + }; + +private: + QubitGateSequence::Gate decomposerBasisGate; + EulerBasis decomposerEulerBasis; }; const matrix2x2 GateDecompositionPattern::IDENTITY_GATE = matrix2x2::Identity(); @@ -2028,7 +2052,16 @@ const matrix2x2 GateDecompositionPattern::IPX{{C_ZERO, IM}, {IM, C_ZERO}}; * decomposition. */ void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { - patterns.add(patterns.getContext()); + patterns.add( + patterns.getContext(), + GateDecompositionPattern::QubitGateSequence::Gate{ + .type = qc::X, .parameter = {}, .qubitId = {1, 0}}, + GateDecompositionPattern::EulerBasis::ZYZ); + patterns.add( + patterns.getContext(), + GateDecompositionPattern::QubitGateSequence::Gate{ + .type = qc::X, .parameter = {}, .qubitId = {0, 1}}, + GateDecompositionPattern::EulerBasis::ZYZ); } } // namespace mqt::ir::opt From 09c9f2b6ea00ebda9217dd4801842df86e61d370 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 16 Nov 2025 21:05:48 +0100 Subject: [PATCH 067/100] very minor cleanup --- .../Transforms/GateDecompositionPattern.cpp | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 8992bf66b..6a0634944 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -58,7 +58,7 @@ struct GateDecompositionPattern final llvm::SmallVector qubitId = {0}; }; std::vector gates; - std::size_t complexity() { + [[nodiscard]] std::size_t complexity() const { std::size_t c{}; for (auto&& gate : gates) { c += getComplexity(gate.type, gate.qubitId.size()); @@ -691,7 +691,7 @@ struct GateDecompositionPattern final } static std::array paramsXyxInner(const matrix2x2& matrix) { - auto matZyz = matrix2x2{ + matrix2x2 matZyz{ {static_cast(0.5) * (matrix(0, 0) + matrix(0, 1) + matrix(1, 0) + matrix(1, 1)), static_cast(0.5) * @@ -1625,11 +1625,11 @@ struct GateDecompositionPattern final } return absDiff <= absLhs * maxRelative; }; - const auto k12RArr = matrix2x2{ + const matrix2x2 k12RArr{ {qfp(0., FRAC1_SQRT2), qfp(FRAC1_SQRT2, 0.)}, {qfp(-FRAC1_SQRT2, 0.), qfp(0., -FRAC1_SQRT2)}, }; - const auto k12LArr = matrix2x2{ + const matrix2x2 k12LArr{ {qfp(0.5, 0.5), qfp(0.5, 0.5)}, {qfp(-0.5, 0.5), qfp(0.5, -0.5)}, }; @@ -1644,42 +1644,41 @@ struct GateDecompositionPattern final // expand as Ui = Ki1.Ubasis.Ki2 auto b = basisDecomposer.b; auto temp = qfp(0.5, -0.5); - auto k11l = matrix2x2{ + matrix2x2 k11l{ {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, - {temp * (M_IM * std::exp(qfp(0., b))), - temp * -(std::exp(qfp(0., b)))}}; - auto k11r = matrix2x2{{FRAC1_SQRT2 * std::exp((IM * qfp(0., -b))), - FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, - {FRAC1_SQRT2 * std::exp(qfp(0., b)), - FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; - auto k32lK21l = matrix2x2{{FRAC1_SQRT2 * std::cos(qfp(1., (2. * b))), - FRAC1_SQRT2 * (IM * std::sin((2. * b)))}, - {FRAC1_SQRT2 * (IM * std::sin(2. * b)), - FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; + {temp * (M_IM * std::exp(qfp(0., b))), temp * -std::exp(qfp(0., b))}}; + matrix2x2 k11r{{FRAC1_SQRT2 * std::exp((IM * qfp(0., -b))), + FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, + {FRAC1_SQRT2 * std::exp(qfp(0., b)), + FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; + matrix2x2 k32lK21l{{FRAC1_SQRT2 * std::cos(qfp(1., (2. * b))), + FRAC1_SQRT2 * (IM * std::sin((2. * b)))}, + {FRAC1_SQRT2 * (IM * std::sin(2. * b)), + FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); - auto k21r = matrix2x2{ + matrix2x2 k21r{ {temp * (M_IM * std::exp(qfp(0., -2. * b))), temp * std::exp(qfp(0., -2. * b))}, {temp * (IM * std::exp(qfp(0., 2. * b))), temp * std::exp(qfp(0., 2. * b))}, }; - const auto k22LArr = matrix2x2{ + const matrix2x2 k22LArr{ {qfp(FRAC1_SQRT2, 0.), qfp(-FRAC1_SQRT2, 0.)}, {qfp(FRAC1_SQRT2, 0.), qfp(FRAC1_SQRT2, 0.)}, }; - const auto k22RArr = matrix2x2{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; - auto k31l = matrix2x2{ + const matrix2x2 k22RArr{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; + matrix2x2 k31l{ {FRAC1_SQRT2 * std::exp(qfp(0., -b)), FRAC1_SQRT2 * std::exp(qfp(0., -b))}, {FRAC1_SQRT2 * -std::exp(qfp(0., b)), FRAC1_SQRT2 * std::exp(qfp(0., b))}, }; - auto k31r = matrix2x2{ + matrix2x2 k31r{ {IM * std::exp(qfp(0., b)), C_ZERO}, {C_ZERO, M_IM * std::exp(qfp(0., -b))}, }; temp = qfp(0.5, 0.5); - auto k32r = matrix2x2{ + matrix2x2 k32r{ {temp * std::exp(qfp(0., b)), temp * -std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * (M_IM * std::exp(qfp(0., -b)))}, @@ -1743,7 +1742,7 @@ struct GateDecompositionPattern final std::optional twoQubitDecompose(const matrix4x4& unitaryMatrix, std::optional basisFidelity, bool approximate, - std::optional numBasisUses) { + std::optional numBasisGateUses) { auto getBasisFidelity = [&]() { if (approximate) { return basisFidelity.value_or(this->basisFidelity); @@ -1767,7 +1766,7 @@ struct GateDecompositionPattern final return minIndex; }; // number of basis gates that need to be inserted - auto bestNbasis = numBasisUses.value_or(getDefaultNbasis()); + auto bestNbasis = numBasisGateUses.value_or(getDefaultNbasis()); auto chooseDecomposition = [&]() { if (bestNbasis == 0) { return decomp0Inner(targetDecomposed); From fb0366deb16a89566d83ea9ebce82119b13a2cfd Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 16 Nov 2025 23:26:16 +0100 Subject: [PATCH 068/100] add more sanity checks for unitary --- .../Transforms/GateDecompositionPattern.cpp | 35 +++++++++++-------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 19 ++++++---- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 6a0634944..f6d2f5c00 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -66,6 +66,18 @@ struct GateDecompositionPattern final return c; } fp globalPhase{}; + + [[nodiscard]] matrix4x4 getUnitaryMatrix() const { + matrix4x4 unitaryMatrix = + helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); + for (auto&& gate : gates) { + auto gateMatrix = getTwoQubitMatrix(gate); + unitaryMatrix = gateMatrix * unitaryMatrix; + } + unitaryMatrix *= std::exp(IM * globalPhase); + assert(helpers::isUnitaryMatrix(unitaryMatrix)); + return unitaryMatrix; + } }; using OneQubitGateSequence = QubitGateSequence; using TwoQubitGateSequence = QubitGateSequence; @@ -191,6 +203,7 @@ struct GateDecompositionPattern final .qubitId = gate.qubitIds}); unitaryMatrix = gateMatrix * unitaryMatrix; } + assert(helpers::isUnitaryMatrix(unitaryMatrix)); return unitaryMatrix; } @@ -440,6 +453,7 @@ struct GateDecompositionPattern final helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY", true); helpers::print((unitaryMatrix * std::exp(IM * sequence.globalPhase)).eval(), "RESULT UNITARY MATRIX", true); + assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)).isApprox(series.getUnitaryMatrix(), 1e-9)); rewriter.replaceAllUsesWith(series.outQubits, inQubits); for (auto&& gate : llvm::reverse(series.gates)) { @@ -1797,6 +1811,7 @@ struct GateDecompositionPattern final llvm::SmallVector, 8> eulerDecompositions; for (auto&& decomp : decomposition) { + assert(helpers::isUnitaryMatrix(decomp)); auto eulerDecomp = unitaryToGateSequenceInner( decomp, target1qBasisList, 0, {}, true, std::nullopt); eulerDecompositions.push_back(eulerDecomp); @@ -1891,19 +1906,6 @@ struct GateDecompositionPattern final }; } - static matrix4x4 computeUnitary(const TwoQubitGateSequence& sequence, - fp globalPhase) { - auto phase = std::exp(std::complex{0, globalPhase}); - matrix4x4 matrix{}; - matrix.diagonal().setConstant(phase); - - for (auto&& gate : sequence.gates) { - matrix4x4 gateMatrix = getTwoQubitMatrix(gate); - matrix = gateMatrix * matrix; - } - return matrix; - } - [[nodiscard]] std::array traces(TwoQubitWeylDecomposition target) const { return { @@ -1955,13 +1957,18 @@ struct GateDecompositionPattern final // independence bool simplify, std::optional atol) { auto calculateError = [](const OneQubitGateSequence& sequence) -> fp { - return static_cast(sequence.gates.size()); + return static_cast(sequence.complexity()); }; auto minError = std::numeric_limits::max(); OneQubitGateSequence bestCircuit; for (auto targetBasis : targetBasisList) { auto circuit = generateCircuit(targetBasis, unitaryMat, simplify, atol); + helpers::print(circuit.getUnitaryMatrix(), "SANITY CHECK (4.1)", true); + helpers::print(helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), + "SANITY CHECK (4.2)", true); + assert(circuit.getUnitaryMatrix().isApprox( + helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), 1e-9)); auto error = calculateError(circuit); if (error < minError) { bestCircuit = circuit; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 3849f66cc..b12c436cc 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -10,15 +10,15 @@ #pragma once -#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "ir/operations/OpType.hpp" +#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include #include #include #include // TODO: unstable -#include // TODO: remove -#include // TODO: remove +#include // TODO: remove +#include // TODO: remove #include #include @@ -55,7 +55,7 @@ void print(Eigen::Matrix matrix, const std::string& s = "", bool force = false) { if (!force) { return; -} + } if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } @@ -67,7 +67,7 @@ template void print(T matrix, const std::string& s = "", bool force = false) { if (!force) { return; -} + } if (!s.empty()) { llvm::errs() << "=== " << s << " ===\n"; } @@ -213,7 +213,7 @@ inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, return result; } -template +template inline auto selfAdjointEvd(Eigen::Matrix a) { Eigen::SelfAdjointEigenSolver s; std::cerr << "=EigIN==\n" << a << "\n========\n" << '\n'; @@ -225,4 +225,11 @@ inline auto selfAdjointEvd(Eigen::Matrix a) { return std::make_pair(vecs, vals); } +template +[[nodiscard]] static bool +isUnitaryMatrix(const Eigen::Matrix& matrix) { + return (matrix.transpose().conjugate() * matrix) + .isApprox(Eigen::Matrix::Identity(), 1e-9); +} + } // namespace mqt::ir::opt::helpers From 0a41410bcef04e0393e098b09dde28e5c30865fc Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 16 Nov 2025 23:29:23 +0100 Subject: [PATCH 069/100] find and fix minor formula mistake --- .../Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index f6d2f5c00..e498342d9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -1661,12 +1661,12 @@ struct GateDecompositionPattern final matrix2x2 k11l{ {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * -std::exp(qfp(0., b))}}; - matrix2x2 k11r{{FRAC1_SQRT2 * std::exp((IM * qfp(0., -b))), + matrix2x2 k11r{{FRAC1_SQRT2 * (IM * std::exp(qfp(0., -b))), FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, {FRAC1_SQRT2 * std::exp(qfp(0., b)), FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; - matrix2x2 k32lK21l{{FRAC1_SQRT2 * std::cos(qfp(1., (2. * b))), - FRAC1_SQRT2 * (IM * std::sin((2. * b)))}, + matrix2x2 k32lK21l{{FRAC1_SQRT2 * qfp(1., std::cos(2. * b)), + FRAC1_SQRT2 * (IM * std::sin(2. * b))}, {FRAC1_SQRT2 * (IM * std::sin(2. * b)), FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); From 5bc93d9e02188486468619f59cb246bcc8d2f9e6 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 16 Nov 2025 23:30:31 +0100 Subject: [PATCH 070/100] test update --- .../MQTOpt/Transforms/gate-decomposition.mlir | 95 +++++++++++++++++-- 1 file changed, 87 insertions(+), 8 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 402c79105..154e75709 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -226,8 +226,8 @@ module { } module { - // CHECK-LABEL: func.func @testComplexSeries - func.func @testComplexSeries() { + // CHECK-LABEL: func.func @testTwoBasisGateDecomposition + func.func @testTwoBasisGateDecomposition() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit @@ -246,8 +246,9 @@ module { %q1_0 = mqtopt.allocQubit %q2_0 = mqtopt.allocQubit - %q0_1 = mqtopt.h() %q0_0: !mqtopt.Qubit - %q1_1, %q0_2 = mqtopt.x() %q1_0 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_x, %q1_x = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1 = mqtopt.h() %q0_x: !mqtopt.Qubit + %q1_1, %q0_2 = mqtopt.x() %q1_x ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_3, %q1_2 = mqtopt.rzz(%cst0) %q0_2, %q1_1: !mqtopt.Qubit, !mqtopt.Qubit %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit @@ -293,17 +294,17 @@ module { %q2_0 = mqtopt.allocQubit %q0_1 = mqtopt.i() %q0_0: !mqtopt.Qubit - %q1_1 = mqtopt.i() %q1_0: !mqtopt.Qubit %q0_2 = mqtopt.i() %q0_1: !mqtopt.Qubit - %q0_3, %q1_2 = mqtopt.x() %q0_2 ctrl %q1_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q1_3 = mqtopt.i() %q1_2: !mqtopt.Qubit + %q0_3, %q1_1 = mqtopt.x() %q0_2 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_4 = mqtopt.i() %q0_3: !mqtopt.Qubit - %q1_4 = mqtopt.i() %q1_3: !mqtopt.Qubit %q0_5 = mqtopt.i() %q0_4: !mqtopt.Qubit %q0_6 = mqtopt.i() %q0_5: !mqtopt.Qubit %q0_7 = mqtopt.i() %q0_6: !mqtopt.Qubit %q0_8 = mqtopt.i() %q0_7: !mqtopt.Qubit %q0_9 = mqtopt.i() %q0_8: !mqtopt.Qubit + %q1_2 = mqtopt.i() %q1_1: !mqtopt.Qubit + %q1_3 = mqtopt.i() %q1_2: !mqtopt.Qubit + %q1_4 = mqtopt.i() %q1_3: !mqtopt.Qubit %q1_5 = mqtopt.i() %q1_4: !mqtopt.Qubit %q1_6 = mqtopt.i() %q1_5: !mqtopt.Qubit %q1_7 = mqtopt.i() %q1_6: !mqtopt.Qubit @@ -317,3 +318,81 @@ module { return } } + +// ----- +// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. + +module { + // CHECK-LABEL: func.func @testSingleQubitSeries + func.func @testSingleQubitSeries() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + + %cst0 = arith.constant 2.5 : f64 + %cst1 = arith.constant 1.2 : f64 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q0_1, %q1_1 = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2 = mqtopt.ry(%cst0) %q0_1: !mqtopt.Qubit + %q1_2 = mqtopt.rx(%cst1) %q1_1: !mqtopt.Qubit + %q1_3 = mqtopt.rx(%cst1) %q1_2: !mqtopt.Qubit + + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_3 + + return + } +} + +// ----- +// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. + +module { + // CHECK-LABEL: func.func @testThreeBasisGateDecomposition + func.func @testThreeBasisGateDecomposition() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit + + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + // CHECK: mqtopt.deallocQubit %[[Q2_1]] + + %cst0 = arith.constant 2.5 : f64 + %cst1 = arith.constant 1.2 : f64 + %cst2 = arith.constant 0.5 : f64 + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit + + %q0_x, %q1_x = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1 = mqtopt.h() %q0_x: !mqtopt.Qubit + %q1_1, %q0_2 = mqtopt.x() %q1_x ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.rzz(%cst0) %q0_2, %q1_1: !mqtopt.Qubit, !mqtopt.Qubit + %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit + %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit + %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_6 = mqtopt.rz(%cst2) %q0_5: !mqtopt.Qubit + %q0_7, %q1_5 = mqtopt.rxx(%cst0) %q0_6, %q1_4: !mqtopt.Qubit, !mqtopt.Qubit + %q0_8, %q1_6 = mqtopt.ryy(%cst2) %q0_7, %q1_5: !mqtopt.Qubit, !mqtopt.Qubit + // make series longer to enforce decomposition + %q0_9, %q1_7 = mqtopt.i() %q0_8 ctrl %q1_6: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_9 + mqtopt.deallocQubit %q1_7 + mqtopt.deallocQubit %q2_0 + + return + } +} + From 27b4cf89f44bd4a040abd4c71ee16fbc6b1a1130 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 17 Nov 2025 19:48:20 +0100 Subject: [PATCH 071/100] make calculations exact without approximation --- .../Transforms/GateDecompositionPattern.cpp | 52 +++++++++++-------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 3 +- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e498342d9..90c8863ce 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -113,9 +113,9 @@ struct GateDecompositionPattern final helpers::print(unitaryMatrix, "UNITARY MATRIX", true); auto decomposer = TwoQubitBasisDecomposer::newInner( - decomposerBasisGate, DEFAULT_FIDELITY, decomposerEulerBasis); + decomposerBasisGate, 1.0, decomposerEulerBasis); auto sequence = decomposer.twoQubitDecompose( - unitaryMatrix, DEFAULT_FIDELITY, true, std::nullopt); + unitaryMatrix, DEFAULT_FIDELITY, false, std::nullopt); if (!sequence) { llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); @@ -134,6 +134,7 @@ struct GateDecompositionPattern final } protected: + static constexpr fp SANITY_CHECK_PRECISION = 1e-12; [[nodiscard]] static std::size_t getComplexity(qc::OpType /*type*/, std::size_t numOfQubits) { if (numOfQubits > 1) { @@ -377,15 +378,15 @@ struct GateDecompositionPattern final } else { auto parameter = helpers::getParameters(gate.op)[0]; std::cerr << std::format( - "{}({:.5}*pi) q[{}] {};", name, parameter / qc::PI, - gate.qubitIds[0], + "{}({}*pi) q[{}] {};", name, parameter / qc::PI, gate.qubitIds[0], (gate.qubitIds.size() > 1 ? (", q[" + std::to_string(gate.qubitIds[1]) + "]") : std::string{})); } } std::cerr << '\n'; - std::cerr << "GATE SEQUENCE!: \n"; + std::cerr << "GATE SEQUENCE!: gphase(" << sequence.globalPhase / qc::PI + << "*pi); \n"; matrix4x4 unitaryMatrix = helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); for (auto&& gate : sequence.gates) { @@ -404,7 +405,7 @@ struct GateDecompositionPattern final : std::string{})); } else { std::cerr << std::format( - "{}({:.5}*pi) q[{}] {};", qc::toString(gate.type), + "{}({}*pi) q[{}] {};", qc::toString(gate.type), gate.parameter[0] / qc::PI, gate.qubitId[0], (gate.qubitId.size() > 1 ? (", q[" + std::to_string(gate.qubitId[1]) + "]") @@ -453,7 +454,8 @@ struct GateDecompositionPattern final helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY", true); helpers::print((unitaryMatrix * std::exp(IM * sequence.globalPhase)).eval(), "RESULT UNITARY MATRIX", true); - assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)).isApprox(series.getUnitaryMatrix(), 1e-9)); + assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)) + .isApprox(series.getUnitaryMatrix(), SANITY_CHECK_PRECISION)); rewriter.replaceAllUsesWith(series.outQubits, inQubits); for (auto&& gate : llvm::reverse(series.gates)) { @@ -908,9 +910,10 @@ struct GateDecompositionPattern final "TwoQubitWeylDecomposition: failed to diagonalize M2."}; } // check that p is in SO(4) - assert((p.transpose() * p).isIdentity()); + assert((p.transpose() * p).isIdentity(SANITY_CHECK_PRECISION)); // make sure determinant of sqrt(eigenvalues) is 1.0 - assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < 1e-13); + assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < + SANITY_CHECK_PRECISION); diagonal4x4 q = d.cwiseSqrt(); auto det_q = q.prod(); @@ -1071,7 +1074,7 @@ struct GateDecompositionPattern final // temp += matrix4x4::Constant(0.0); helpers::print(temp, "TEMP", true); helpers::print(p, "P", true); - assert(std::abs(p.determinant() - 1.0) < 1e-13); + assert(std::abs(p.determinant() - 1.0) < SANITY_CHECK_PRECISION); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 matrix4x4 k1 = uP * p * temp; @@ -1094,12 +1097,14 @@ struct GateDecompositionPattern final assert((k1 * magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * k2) - .isApprox(u, 1e-8)); + .isApprox(u, SANITY_CHECK_PRECISION)); auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); - assert(helpers::kroneckerProduct(K1l, K1r).isApprox(k1, 1e-9)); - assert(helpers::kroneckerProduct(K2l, K2r).isApprox(k2, 1e-9)); + assert(helpers::kroneckerProduct(K1l, K1r).isApprox( + k1, SANITY_CHECK_PRECISION)); + assert(helpers::kroneckerProduct(K2l, K2r).isApprox( + k2, SANITY_CHECK_PRECISION)); globalPhase += phase_l + phase_r; // Flip into Weyl chamber @@ -1201,7 +1206,7 @@ struct GateDecompositionPattern final assert((helpers::kroneckerProduct(K1l, K1r) * getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) - .isApprox(unitaryMatrix, 1e-8)); + .isApprox(unitaryMatrix, SANITY_CHECK_PRECISION)); auto isClose = [&](fp ap, fp bp, fp cp) -> bool { auto da = a - ap; auto db = b - bp; @@ -1572,7 +1577,7 @@ struct GateDecompositionPattern final specialized.c * -2.0) * helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * std::exp(IM * specialized.globalPhase)) - .isApprox(unitaryMatrix, 1e-8)); + .isApprox(unitaryMatrix, SANITY_CHECK_PRECISION)); return specialized; } @@ -1649,7 +1654,7 @@ struct GateDecompositionPattern final }; auto basisDecomposer = TwoQubitWeylDecomposition::newInner( - getTwoQubitMatrix(basisGate), DEFAULT_FIDELITY, std::nullopt); + getTwoQubitMatrix(basisGate), basisFidelity, std::nullopt); auto superControlled = relativeEq(basisDecomposer.a, qc::PI_4, 1e-13, 1e-09) && relativeEq(basisDecomposer.c, 0.0, 1e-13, 1e-09); @@ -1765,7 +1770,7 @@ struct GateDecompositionPattern final }; fp actualBasisFidelity = getBasisFidelity(); auto targetDecomposed = TwoQubitWeylDecomposition::newInner( - unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); + unitaryMatrix, actualBasisFidelity, std::nullopt); auto traces = this->traces(targetDecomposed); auto getDefaultNbasis = [&]() { auto minValue = std::numeric_limits::min(); @@ -1813,7 +1818,7 @@ struct GateDecompositionPattern final for (auto&& decomp : decomposition) { assert(helpers::isUnitaryMatrix(decomp)); auto eulerDecomp = unitaryToGateSequenceInner( - decomp, target1qBasisList, 0, {}, true, std::nullopt); + decomp, target1qBasisList, 0, {}, true, 1.0 - actualBasisFidelity); eulerDecompositions.push_back(eulerDecomp); } TwoQubitGateSequence gates{.globalPhase = targetDecomposed.globalPhase}; @@ -1968,7 +1973,8 @@ struct GateDecompositionPattern final helpers::print(helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), "SANITY CHECK (4.2)", true); assert(circuit.getUnitaryMatrix().isApprox( - helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), 1e-9)); + helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), + SANITY_CHECK_PRECISION)); auto error = calculateError(circuit); if (error < minError) { bestCircuit = circuit; @@ -2004,7 +2010,7 @@ struct GateDecompositionPattern final fp globalPhase = phase - ((phi + lambda) / 2.); std::vector gates; - if (std::abs(theta) < angleZeroEpsilon) { + if (std::abs(theta) <= angleZeroEpsilon) { lambda += phi; lambda = mod2pi(lambda); if (std::abs(lambda) > angleZeroEpsilon) { @@ -2014,13 +2020,13 @@ struct GateDecompositionPattern final return {gates, globalPhase}; } - if (std::abs(theta - qc::PI) < angleZeroEpsilon) { + if (std::abs(theta - qc::PI) <= angleZeroEpsilon) { globalPhase += phi; lambda -= phi; phi = 0.0; } - if (std::abs(mod2pi(lambda + qc::PI)) < angleZeroEpsilon || - std::abs(mod2pi(phi + qc::PI)) < angleZeroEpsilon) { + if (std::abs(mod2pi(lambda + qc::PI)) <= angleZeroEpsilon || + std::abs(mod2pi(phi + qc::PI)) <= angleZeroEpsilon) { lambda += qc::PI; theta = -theta; phi += qc::PI; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index b12c436cc..5180a7ea9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -228,8 +228,7 @@ inline auto selfAdjointEvd(Eigen::Matrix a) { template [[nodiscard]] static bool isUnitaryMatrix(const Eigen::Matrix& matrix) { - return (matrix.transpose().conjugate() * matrix) - .isApprox(Eigen::Matrix::Identity(), 1e-9); + return (matrix.transpose().conjugate() * matrix).isIdentity(); } } // namespace mqt::ir::opt::helpers From 0a027634bc0b9e7156e5f5ecd2811a18cd603f17 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 17 Nov 2025 19:48:55 +0100 Subject: [PATCH 072/100] respect gphase of circuit and fix series collection bug --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 90c8863ce..34d6fa639 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -148,6 +148,7 @@ struct GateDecompositionPattern final std::size_t complexity{0}; std::array inQubits; std::array outQubits; + fp globalPhase{}; struct Gate { UnitaryInterface op; @@ -187,10 +188,10 @@ struct GateDecompositionPattern final if (auto user = getUser(result.outQubits[i], &helpers::isTwoQubitOperation)) { foundGate = result.appendTwoQubitGate(*user); + break; // go back to single-qubit collection } } } - // TODO: need to search and apply for global phase? return result; } @@ -204,6 +205,8 @@ struct GateDecompositionPattern final .qubitId = gate.qubitIds}); unitaryMatrix = gateMatrix * unitaryMatrix; } + unitaryMatrix *= std::exp(IM * globalPhase); + assert(helpers::isUnitaryMatrix(unitaryMatrix)); return unitaryMatrix; } @@ -227,6 +230,11 @@ struct GateDecompositionPattern final } complexity += getComplexity(helpers::getQcType(initialOperation), in.size()); + + // TODO: necessary when using non-global phase gates? + for (auto&& globalPhaseOp : initialOperation->getBlock()->getOps()) { + globalPhase += helpers::getParameters(globalPhaseOp)[0]; + } } /** @@ -1858,7 +1866,8 @@ struct GateDecompositionPattern final addEulerDecomposition((2UL * bestNbasis) + 1, 1); // large global phases can be generated by the decomposition, thus limit - // it to [-2*pi, +2*pi) + // it to (-2*pi, +2*pi); TODO: can be removed, should be done by something + // like constant folding gates.globalPhase = std::fmod(gates.globalPhase, qc::TAU); return gates; From 786143e38002861946fda38817c8cd8c19456287 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Mon, 17 Nov 2025 23:41:51 +0100 Subject: [PATCH 073/100] change global phase to local phase --- .../Transforms/GateDecompositionPattern.cpp | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 34d6fa639..e3dc20d36 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -63,9 +63,17 @@ struct GateDecompositionPattern final for (auto&& gate : gates) { c += getComplexity(gate.type, gate.qubitId.size()); } + if (hasGlobalPhase()) { + // need to add two phase gates if a global phase needs to be applied + c += 2 * getComplexity(qc::P, 1); + } return c; } + fp globalPhase{}; + [[nodiscard]] bool hasGlobalPhase() const { + return std::abs(globalPhase) > std::numeric_limits::epsilon(); + } [[nodiscard]] matrix4x4 getUnitaryMatrix() const { matrix4x4 unitaryMatrix = @@ -120,7 +128,9 @@ struct GateDecompositionPattern final llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } - if (sequence->complexity() >= series.complexity) { + // only accept new sequence if it shortens existing series by more than two + // gates; this prevents an oscillation with phase gates + if (sequence->complexity() >= series.complexity + 2) { // TODO: add more sophisticated metric to determine complexity of // series/sequence llvm::errs() << "SEQUENCE LONGER THAN INPUT (" << sequence->gates.size() @@ -232,7 +242,8 @@ struct GateDecompositionPattern final getComplexity(helpers::getQcType(initialOperation), in.size()); // TODO: necessary when using non-global phase gates? - for (auto&& globalPhaseOp : initialOperation->getBlock()->getOps()) { + for (auto&& globalPhaseOp : + initialOperation->getBlock()->getOps()) { globalPhase += helpers::getParameters(globalPhaseOp)[0]; } } @@ -346,29 +357,31 @@ struct GateDecompositionPattern final auto location = lastSeriesOp->getLoc(); rewriter.setInsertionPointAfter(lastSeriesOp); - if (sequence.globalPhase != 0.0) { - // TODO: use POp instead and apply negative globalPhase after insertion of - // new gates to "undo" the phase shift - createOneParameterGate(rewriter, location, sequence.globalPhase, - {}); - } - auto inQubits = series.inQubits; auto updateInQubits = - [&inQubits](const TwoQubitGateSequence::Gate& gateDescription, + [&inQubits](const llvm::SmallVector& qubitIds, auto&& newGate) { // TODO: need to handle controls differently? auto results = newGate.getAllOutQubits(); - if (gateDescription.qubitId.size() == 2) { - inQubits[gateDescription.qubitId[0]] = results[0]; - inQubits[gateDescription.qubitId[1]] = results[1]; - } else if (gateDescription.qubitId.size() == 1) { - inQubits[gateDescription.qubitId[0]] = results[0]; + if (qubitIds.size() == 2) { + inQubits[qubitIds[0]] = results[0]; + inQubits[qubitIds[1]] = results[1]; + } else if (qubitIds.size() == 1) { + inQubits[qubitIds[0]] = results[0]; } else { throw std::logic_error{"Invalid number of qubit IDs!"}; } }; + if (sequence.hasGlobalPhase()) { + auto newGate = createOneParameterGate( + rewriter, location, sequence.globalPhase, {inQubits[0]}); + updateInQubits({0}, newGate); + newGate = createOneParameterGate( + rewriter, location, sequence.globalPhase, {inQubits[1]}); + updateInQubits({1}, newGate); + } + std::cerr << "SERIES: "; for (auto&& gate : series.gates) { auto name = gate.op->getName().stripDialect().str(); @@ -429,7 +442,7 @@ struct GateDecompositionPattern final auto newGate = createGate(rewriter, location, {inQubits[gate.qubitId[1]]}, inCtrlQubits, gate.parameter); - updateInQubits(gate, newGate); + updateInQubits(gate.qubitId, newGate); } else if (gate.type == qc::RX) { mlir::SmallVector qubits; for (auto&& x : gate.qubitId) { @@ -437,7 +450,7 @@ struct GateDecompositionPattern final } auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - updateInQubits(gate, newGate); + updateInQubits(gate.qubitId, newGate); } else if (gate.type == qc::RY) { mlir::SmallVector qubits; for (auto&& x : gate.qubitId) { @@ -445,7 +458,7 @@ struct GateDecompositionPattern final } auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - updateInQubits(gate, newGate); + updateInQubits(gate.qubitId, newGate); } else if (gate.type == qc::RZ) { mlir::SmallVector qubits; for (auto&& x : gate.qubitId) { @@ -453,7 +466,7 @@ struct GateDecompositionPattern final } auto newGate = createGate(rewriter, location, qubits, {}, gate.parameter); - updateInQubits(gate, newGate); + updateInQubits(gate.qubitId, newGate); } else { throw std::runtime_error{"Unknown gate type!"}; } @@ -683,6 +696,10 @@ struct GateDecompositionPattern final {C_ZERO, C_ZERO, C_ZERO, {cosTheta, -sinTheta}}}; } + static matrix2x2 pMatrix(const fp lambda) { + return matrix2x2{{1, 0}, {0, {std::cos(lambda), std::sin(lambda)}}}; + } + static std::array anglesFromUnitary(const matrix2x2& matrix, EulerBasis basis) { if (basis == EulerBasis::XYX) { @@ -760,6 +777,9 @@ struct GateDecompositionPattern final if (gate.type == qc::I) { return IDENTITY_GATE; } + if (gate.type == qc::P) { + return pMatrix(gate.parameter[0]); + } if (gate.type == qc::H) { static constexpr fp SQRT2_2 = static_cast( 0.707106781186547524400844362104849039284835937688474036588L); From a9bf676e8d424ab17f25aaf9fa3dab643b20a39e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 18 Nov 2025 14:33:58 +0100 Subject: [PATCH 074/100] revert to global phase, fix controlled gate qubit order --- .../Transforms/GateDecompositionPattern.cpp | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e3dc20d36..592443e0b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -130,7 +130,7 @@ struct GateDecompositionPattern final } // only accept new sequence if it shortens existing series by more than two // gates; this prevents an oscillation with phase gates - if (sequence->complexity() >= series.complexity + 2) { + if (sequence->complexity() + 2 >= series.complexity) { // TODO: add more sophisticated metric to determine complexity of // series/sequence llvm::errs() << "SEQUENCE LONGER THAN INPUT (" << sequence->gates.size() @@ -232,11 +232,7 @@ struct GateDecompositionPattern final } else if (helpers::isTwoQubitOperation(initialOperation)) { inQubits = {in[0], in[1]}; outQubits = {out[0], out[1]}; - if (initialOperation.isControlled()) { - gates.push_back({.op = initialOperation, .qubitIds = {1, 0}}); - } else { - gates.push_back({.op = initialOperation, .qubitIds = {0, 1}}); - } + gates.push_back({.op = initialOperation, .qubitIds = {0, 1}}); } complexity += getComplexity(helpers::getQcType(initialOperation), in.size()); @@ -297,14 +293,8 @@ struct GateDecompositionPattern final *firstQubitIt = nextGate->getResult(0); *secondQubitIt = nextGate->getResult(1); - if (nextGate.isControlled()) { - // controls of a gate should come first, but are last in the qubit order - gates.push_back( - {.op = nextGate, .qubitIds = {secondQubitId, firstQubitId}}); - } else { - gates.push_back( - {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); - } + gates.push_back( + {.op = nextGate, .qubitIds = {firstQubitId, secondQubitId}}); complexity += getComplexity(helpers::getQcType(nextGate), 2); return true; } @@ -374,12 +364,8 @@ struct GateDecompositionPattern final }; if (sequence.hasGlobalPhase()) { - auto newGate = createOneParameterGate( - rewriter, location, sequence.globalPhase, {inQubits[0]}); - updateInQubits({0}, newGate); - newGate = createOneParameterGate( - rewriter, location, sequence.globalPhase, {inQubits[1]}); - updateInQubits({1}, newGate); + createOneParameterGate(rewriter, location, sequence.globalPhase, + {}); } std::cerr << "SERIES: "; @@ -416,8 +402,8 @@ struct GateDecompositionPattern final if (gate.type == qc::X && gate.qubitId.size() == 2) { // controls come first - std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[0], - gate.qubitId[1]); + std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[1], + gate.qubitId[0]); } else if (gate.parameter.empty()) { std::cerr << std::format( "{}() q[{}] {};", qc::toString(gate.type), gate.qubitId[0], @@ -437,10 +423,10 @@ struct GateDecompositionPattern final mlir::SmallVector inCtrlQubits; if (gate.qubitId.size() > 1) { // controls come first - inCtrlQubits.push_back(inQubits[gate.qubitId[0]]); + inCtrlQubits.push_back(inQubits[gate.qubitId[1]]); } auto newGate = - createGate(rewriter, location, {inQubits[gate.qubitId[1]]}, + createGate(rewriter, location, {inQubits[gate.qubitId[0]]}, inCtrlQubits, gate.parameter); updateInQubits(gate.qubitId, newGate); } else if (gate.type == qc::RX) { @@ -808,11 +794,11 @@ struct GateDecompositionPattern final if (gate.qubitId.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubitId == llvm::SmallVector{1, 0}) { + if (gate.qubitId == llvm::SmallVector{0, 1}) { return matrix4x4{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubitId == llvm::SmallVector{0, 1}) { + if (gate.qubitId == llvm::SmallVector{1, 0}) { return matrix4x4{ {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } From 3a132ec13f20824783ada89cddd34545fd0e4bf4 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 18 Nov 2025 14:34:20 +0100 Subject: [PATCH 075/100] fix first couple of tests --- .../MQTOpt/Transforms/gate-decomposition.mlir | 201 ++++++++++++------ 1 file changed, 137 insertions(+), 64 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 154e75709..c9292b914 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -9,11 +9,11 @@ // RUN: quantum-opt %s -split-input-file --gate-decomposition | FileCheck %s // ----- -// This test checks if an even number of CNOT gates will be cancelled out. +// This test checks if a series equal to the identity will be cancelled out. module { - // CHECK-LABEL: func.func @testEvenNegationSeries - func.func @testEvenNegationSeries() { + // CHECK-LABEL: func.func @testIdentitySeries + func.func @testIdentitySeries() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit @@ -93,20 +93,57 @@ module { } } +// ----- +// This test checks if an odd number of CNOT gates with ctrl/target swapped will be reduced to a single CNOT. + +module { + // CHECK-LABEL: func.func @testCNotOtherDirection + func.func @testCNotOtherDirection() { + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK-NOT: mqtopt.i(%[[ANY:.*]]) + // CHECK: %[[Q0_0]], %[[Q1_0]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] + + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %q1_1, %q0_1 = mqtopt.i() %q1_0 ctrl %q0_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_2, %q0_2 = mqtopt.x() %q1_1 ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_3, %q0_3 = mqtopt.i() %q1_2 ctrl %q0_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + mqtopt.deallocQubit %q1_3 + + return + } +} + // ----- // This test checks if a two-qubit series containing a single-qubit gate is decomposed correctly. module { // CHECK-LABEL: func.func @testSeriesOneQubitOpInbetween func.func @testSeriesOneQubitOpInbetween() { + // CHECK: %[[C2:.*]] = arith.constant -1.5707 + // CHECK: %[[C1:.*]] = arith.constant 3.14159 + // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] - // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -129,14 +166,19 @@ module { module { // CHECK-LABEL: func.func @testSeriesStartingOneQubitOp func.func @testSeriesStartingOneQubitOp() { + // CHECK: %[[C2:.*]] = arith.constant -1.5707 + // CHECK: %[[C1:.*]] = arith.constant 3.14159 + // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] - // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -159,14 +201,19 @@ module { module { // CHECK-LABEL: func.func @testSeriesEndingOneQubitOp func.func @testSeriesEndingOneQubitOp() { + // CHECK: %[[C2:.*]] = arith.constant -1.5707 + // CHECK: %[[C1:.*]] = arith.constant 3.14159 + // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_1]] + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] - // CHECK: mqtopt.deallocQubit %[[Q0_2]] + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -228,15 +275,46 @@ module { module { // CHECK-LABEL: func.func @testTwoBasisGateDecomposition func.func @testTwoBasisGateDecomposition() { + // CHECK: %[[C0:.*]] = arith.constant -2.356194490 + // CHECK: %[[C1:.*]] = arith.constant -1.070796326 + // CHECK: %[[C2:.*]] = arith.constant -0.370796326 + // CHECK: %[[C3:.*]] = arith.constant -2.526112944 + // CHECK: %[[C4:.*]] = arith.constant 1.0471975511 + // CHECK: %[[C5:.*]] = arith.constant 0.6154797086 + // CHECK: %[[C6:.*]] = arith.constant -3.141592653 + // CHECK: %[[C7:.*]] = arith.constant 2.7707963267 + // CHECK: %[[C8:.*]] = arith.constant -1.570796326 + // CHECK: %[[C9:.*]] = arith.constant 0.7853981633 + // CHECK: %[[C10:.*]] = arith.constant 2.5000 + // CHECK: %[[C11:.*]] = arith.constant 1.570796326 + // CHECK: %[[C12:.*]] = arith.constant -1.57079632 + // CHECK: %[[C13:.*]] = arith.constant 8.881784197 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) - - // CHECK: mqtopt.deallocQubit %[[Q0_5]] - // CHECK: mqtopt.deallocQubit %[[Q1_4]] - // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // CHECK: mqtopt.gphase(%[[C13]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C12]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C11]]) %[[Q0_1]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C11]]) %[[Q0_2]] + // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_0]] + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C9]]) %[[Q1_1]] + // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C8]]) %[[Q1_2]] + // CHECK: %[[Q1_4:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_3]] + // CHECK: %[[Q0_5:.*]] = mqtopt.rz(%[[C11]]) %[[Q0_4]] + // CHECK: %[[Q0_6:.*]] = mqtopt.ry(%[[C7]]) %[[Q0_5]] + // CHECK: %[[Q0_7:.*]] = mqtopt.rz(%[[C6]]) %[[Q0_6]] + // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C5]]) %[[Q1_4]] + // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C4]]) %[[Q1_5]] + // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C3]]) %[[Q1_6]] + // CHECK: %[[Q1_8:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_7]] + // CHECK: %[[Q0_9:.*]] = mqtopt.ry(%[[C2]]) %[[Q0_8]] + // CHECK: %[[Q0_10:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_9]] + // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_8]] + // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C0]]) %[[Q1_9]] + + // CHECK: mqtopt.deallocQubit %[[Q0_10]] + // CHECK: mqtopt.deallocQubit %[[Q1_10]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 @@ -244,7 +322,6 @@ module { %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit - %q2_0 = mqtopt.allocQubit %q0_x, %q1_x = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_1 = mqtopt.h() %q0_x: !mqtopt.Qubit @@ -266,54 +343,38 @@ module { mqtopt.deallocQubit %q0_9 mqtopt.deallocQubit %q1_9 - mqtopt.deallocQubit %q2_0 return } } +// ----- +// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. + module { - // CHECK-LABEL: func.func @testCNotOtherDirection - func.func @testCNotOtherDirection() { + // CHECK-LABEL: func.func @testSingleQubitSeries + func.func @testSingleQubitSeries() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] - // CHECK: mqtopt.deallocQubit %[[Q0_5]] - // CHECK: mqtopt.deallocQubit %[[Q1_4]] - // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 - %cst2 = arith.constant 0.5 : f64 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit - %q2_0 = mqtopt.allocQubit - %q0_1 = mqtopt.i() %q0_0: !mqtopt.Qubit - %q0_2 = mqtopt.i() %q0_1: !mqtopt.Qubit - %q0_3, %q1_1 = mqtopt.x() %q0_2 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_4 = mqtopt.i() %q0_3: !mqtopt.Qubit - %q0_5 = mqtopt.i() %q0_4: !mqtopt.Qubit - %q0_6 = mqtopt.i() %q0_5: !mqtopt.Qubit - %q0_7 = mqtopt.i() %q0_6: !mqtopt.Qubit - %q0_8 = mqtopt.i() %q0_7: !mqtopt.Qubit - %q0_9 = mqtopt.i() %q0_8: !mqtopt.Qubit - %q1_2 = mqtopt.i() %q1_1: !mqtopt.Qubit - %q1_3 = mqtopt.i() %q1_2: !mqtopt.Qubit - %q1_4 = mqtopt.i() %q1_3: !mqtopt.Qubit - %q1_5 = mqtopt.i() %q1_4: !mqtopt.Qubit - %q1_6 = mqtopt.i() %q1_5: !mqtopt.Qubit - %q1_7 = mqtopt.i() %q1_6: !mqtopt.Qubit - %q1_8 = mqtopt.i() %q1_7: !mqtopt.Qubit - %q1_9 = mqtopt.i() %q1_8: !mqtopt.Qubit + %q0_1, %q1_1 = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2 = mqtopt.ry(%cst0) %q0_1: !mqtopt.Qubit + %q1_2 = mqtopt.rx(%cst1) %q1_1: !mqtopt.Qubit + %q1_3 = mqtopt.rx(%cst1) %q1_2: !mqtopt.Qubit - mqtopt.deallocQubit %q0_9 - mqtopt.deallocQubit %q1_9 - mqtopt.deallocQubit %q2_0 + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_3 return } @@ -323,40 +384,53 @@ module { // This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. module { - // CHECK-LABEL: func.func @testSingleQubitSeries - func.func @testSingleQubitSeries() { + // CHECK-LABEL: func.func @testThreeBasisGateDecomposition + func.func @testThreeBasisGateDecomposition() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + // CHECK: mqtopt.deallocQubit %[[Q2_1]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 + %cst2 = arith.constant 0.5 : f64 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit + %q2_0 = mqtopt.allocQubit - %q0_1, %q1_1 = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_2 = mqtopt.ry(%cst0) %q0_1: !mqtopt.Qubit - %q1_2 = mqtopt.rx(%cst1) %q1_1: !mqtopt.Qubit - %q1_3 = mqtopt.rx(%cst1) %q1_2: !mqtopt.Qubit + %q0_x, %q1_x = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1 = mqtopt.h() %q0_x: !mqtopt.Qubit + %q1_1, %q0_2 = mqtopt.x() %q1_x ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_3, %q1_2 = mqtopt.rzz(%cst0) %q0_2, %q1_1: !mqtopt.Qubit, !mqtopt.Qubit + %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit + %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit + %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_6 = mqtopt.rz(%cst2) %q0_5: !mqtopt.Qubit + %q0_7, %q1_5 = mqtopt.rxx(%cst0) %q0_6, %q1_4: !mqtopt.Qubit, !mqtopt.Qubit + %q0_8, %q1_6 = mqtopt.ryy(%cst2) %q0_7, %q1_5: !mqtopt.Qubit, !mqtopt.Qubit + // make series longer to enforce decomposition + %q0_9, %q1_7 = mqtopt.i() %q0_8 ctrl %q1_6: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_3 + mqtopt.deallocQubit %q0_9 + mqtopt.deallocQubit %q1_7 + mqtopt.deallocQubit %q2_0 return } } // ----- -// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. +// This test checks if the repeated application of the decomposition works by "interrupting" the first series and then having a second one. module { - // CHECK-LABEL: func.func @testThreeBasisGateDecomposition - func.func @testThreeBasisGateDecomposition() { + // CHECK-LABEL: func.func @testRepeatedDecomposition + func.func @testRepeatedDecomposition() { // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit @@ -395,4 +469,3 @@ module { return } } - From 3b61c083d7ac5f7303b94b0449f5ccd783b55892 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Tue, 18 Nov 2025 23:27:11 +0100 Subject: [PATCH 076/100] start working on basis set and remove dead code --- .../Transforms/GateDecompositionPattern.cpp | 142 +++--------------- 1 file changed, 19 insertions(+), 123 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 592443e0b..752fa5461 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -90,12 +90,13 @@ struct GateDecompositionPattern final using OneQubitGateSequence = QubitGateSequence; using TwoQubitGateSequence = QubitGateSequence; - explicit GateDecompositionPattern(mlir::MLIRContext* context, - QubitGateSequence::Gate basisGate, - EulerBasis eulerBasis) + explicit GateDecompositionPattern( + mlir::MLIRContext* context, + llvm::SmallVector basisGate, + llvm::SmallVector eulerBasis) : OpInterfaceRewritePattern(context), decomposerBasisGate{std::move(basisGate)}, - decomposerEulerBasis{eulerBasis} {} + decomposerEulerBasis{std::move(eulerBasis)} {} mlir::LogicalResult matchAndRewrite(UnitaryInterface op, @@ -121,7 +122,7 @@ struct GateDecompositionPattern final helpers::print(unitaryMatrix, "UNITARY MATRIX", true); auto decomposer = TwoQubitBasisDecomposer::newInner( - decomposerBasisGate, 1.0, decomposerEulerBasis); + decomposerBasisGate[0], 1.0, decomposerEulerBasis[0]); auto sequence = decomposer.twoQubitDecompose( unitaryMatrix, DEFAULT_FIDELITY, false, std::nullopt); if (!sequence) { @@ -373,8 +374,8 @@ struct GateDecompositionPattern final auto name = gate.op->getName().stripDialect().str(); if (name == "x" && gate.qubitIds.size() == 2) { // controls come first - std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitIds[0], - gate.qubitIds[1]); + std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitIds[1], + gate.qubitIds[0]); } else if (name == "i") { } else if (gate.op.getParams().empty()) { std::cerr << std::format( @@ -929,112 +930,10 @@ struct GateDecompositionPattern final assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < SANITY_CHECK_PRECISION); - diagonal4x4 q = d.cwiseSqrt(); - auto det_q = q.prod(); - if (det_q.real() < 0.0) { - q[0] *= -1.0; - } - // constrain to weyl - auto constrain_to_weyl = [](diagonal4x4 q) { - auto in_weyl = [](fp tx, fp ty, fp tz) { - return (0.5 >= tx && tx >= ty && ty >= tz && tz >= 0) || - (0.5 >= (1 - tx) && (1 - tx) >= ty && ty >= tz && tz > 0); - }; - auto lambdas_to_coords = [](diagonal4x4 lambdas) { - // [2, eq.11], but using [1]s coordinates. - constexpr fp TOLERANCE = 1e-13; - - auto [l1, l2, _, l4] = - std::array{lambdas[0], lambdas[1], lambdas[2], lambdas[3]}; - auto c1 = std::real(IM * std::log(l1 * l2)); - auto c2 = std::real(IM * std::log(l2 * l4)); - auto c3 = std::real(IM * std::log(l1 * l4)); - Eigen::Vector coords{c1, c2, c3}; - coords /= qc::PI; - - // if coords[i] == 1, then coords[i] = -1, else coords[i] = coords[i] - coords = (coords - decltype(coords)::Ones()) - .cwiseAbs() - .cwiseLess(TOLERANCE) - .select(-decltype(coords)::Ones(), coords) - .eval(); - if (coords.cwiseLess(0.0).all()) { - coords += decltype(coords)::Ones(); - } - - // If we're close to the boundary, floating point errors can conspire - // to make it seem that we're never on the inside - // Fix: If near boundary, reset to boundary - - // Left - if (std::abs(coords[0] - coords[1]) < TOLERANCE) { - coords[1] = coords[0]; - } - - // Front - if (std::abs(coords[1] - coords[2]) < TOLERANCE) { - coords[2] = coords[1]; - } - - // Right - if (std::abs(coords[0] - coords[1] - 1.0 / 2.0) < TOLERANCE) { - coords[1] = coords[0] - 1.0 / 2.0; - } - - // Base - coords = - (coords.array() < 0).select(decltype(coords)::Zero(), coords); - - return coords; - }; - - auto permutation = std::array{0, 1, 2, 3}; - while (true) { - for (auto signs : - std::array, 4>{{{1, 1, 1, 1}, - {1, 1, -1, -1}, - {-1, 1, -1, 1}, - {1, -1, -1, 1}}}) { - decltype(q) signed_lambdas = q.cwiseProduct(signs); - // reorder according to permutation - decltype(signed_lambdas) lambdas_perm; - for (std::size_t i = 0; i < permutation.size(); ++i) { - lambdas_perm[i] = signed_lambdas[permutation[i]]; - } - - auto coords = lambdas_to_coords(lambdas_perm); - - if (in_weyl(coords[0], coords[1], coords[2])) { - return std::make_tuple(coords, permutation, signs); - } - } - if (!std::ranges::next_permutation(permutation).found) { - throw std::runtime_error{ - "Unable to find permutation to calculate Weyl coordinates!"}; - } - } - }; - - auto [cs, permutation, signs] = constrain_to_weyl(q); - cs *= -qc::PI_2; - matrix4x4 p2 = p; - - q = q.cwiseProduct(signs); - auto origQ = q; - matrix4x4 origP = signs.asDiagonal() * p.transpose(); - assert(permutation.size() == q.size()); - assert(permutation.size() == p.cols()); - for (std::size_t i = 0; i < permutation.size(); ++i) { - q[i] = origQ[permutation[i]]; - p2.col(i) = origP.row(permutation[i]); - } - // p.transposeInPlace(); - matrix4x4 temp = q.asDiagonal(); - temp = temp.conjugate(); - // see // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, // Step 7 + Eigen::Vector cs; rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; helpers::print(dReal, "D_REAL", true); dReal(3) = -dReal(0) - dReal(1) - dReal(2); @@ -1080,7 +979,7 @@ struct GateDecompositionPattern final p.col(lastColumnIndex) *= -1.0; } - temp = dReal.asDiagonal(); + matrix4x4 temp = dReal.asDiagonal(); temp *= IM; temp = temp.exp(); @@ -2062,8 +1961,8 @@ struct GateDecompositionPattern final }; private: - QubitGateSequence::Gate decomposerBasisGate; - EulerBasis decomposerEulerBasis; + llvm::SmallVector decomposerBasisGate; + llvm::SmallVector decomposerEulerBasis; }; const matrix2x2 GateDecompositionPattern::IDENTITY_GATE = matrix2x2::Identity(); @@ -2079,16 +1978,13 @@ const matrix2x2 GateDecompositionPattern::IPX{{C_ZERO, IM}, {IM, C_ZERO}}; * decomposition. */ void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { - patterns.add( - patterns.getContext(), - GateDecompositionPattern::QubitGateSequence::Gate{ - .type = qc::X, .parameter = {}, .qubitId = {1, 0}}, - GateDecompositionPattern::EulerBasis::ZYZ); - patterns.add( - patterns.getContext(), - GateDecompositionPattern::QubitGateSequence::Gate{ - .type = qc::X, .parameter = {}, .qubitId = {0, 1}}, - GateDecompositionPattern::EulerBasis::ZYZ); + llvm::SmallVector basisGates; + llvm::SmallVector eulerBases; + basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {0, 1}}); + basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {1, 0}}); + eulerBases.push_back(GateDecompositionPattern::EulerBasis::ZYZ); + patterns.add(patterns.getContext(), basisGates, + eulerBases); } } // namespace mqt::ir::opt From dff3f304e4979402fe0c9e53efa8d57be2c2842e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 00:21:57 +0100 Subject: [PATCH 077/100] decompose for all bases, choose best --- .../Transforms/GateDecompositionPattern.cpp | 111 ++++++++++-------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 752fa5461..f1345877d 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -96,7 +96,12 @@ struct GateDecompositionPattern final llvm::SmallVector eulerBasis) : OpInterfaceRewritePattern(context), decomposerBasisGate{std::move(basisGate)}, - decomposerEulerBasis{std::move(eulerBasis)} {} + decomposerEulerBases{std::move(eulerBasis)} { + for (auto&& basisGate : decomposerBasisGate) { + basisDecomposers.push_back( + TwoQubitBasisDecomposer::newInner(basisGate, DEFAULT_FIDELITY)); + } + } mlir::LogicalResult matchAndRewrite(UnitaryInterface op, @@ -121,25 +126,38 @@ struct GateDecompositionPattern final matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); helpers::print(unitaryMatrix, "UNITARY MATRIX", true); - auto decomposer = TwoQubitBasisDecomposer::newInner( - decomposerBasisGate[0], 1.0, decomposerEulerBasis[0]); - auto sequence = decomposer.twoQubitDecompose( - unitaryMatrix, DEFAULT_FIDELITY, false, std::nullopt); - if (!sequence) { + auto targetDecomposition = TwoQubitWeylDecomposition::newInner( + unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); + + std::optional bestSequence; + for (const auto& decomposer : basisDecomposers) { + auto sequence = decomposer.twoQubitDecompose( + targetDecomposition, decomposerEulerBases, DEFAULT_FIDELITY, false, + std::nullopt); + if (sequence) { + if (!bestSequence || + sequence->complexity() < bestSequence->complexity()) { + bestSequence = sequence; + } + } + } + if (!bestSequence) { llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } // only accept new sequence if it shortens existing series by more than two // gates; this prevents an oscillation with phase gates - if (sequence->complexity() + 2 >= series.complexity) { + if (bestSequence->complexity() + 2 >= series.complexity) { // TODO: add more sophisticated metric to determine complexity of // series/sequence - llvm::errs() << "SEQUENCE LONGER THAN INPUT (" << sequence->gates.size() + llvm::errs() << "SEQUENCE LONGER THAN INPUT (" + << bestSequence->gates.size() << "; " + << bestSequence->complexity() << " vs " << series.complexity << ")\n"; return mlir::failure(); } - applySeries(rewriter, series, *sequence); + applySeries(rewriter, series, *bestSequence); return mlir::success(); } @@ -826,24 +844,25 @@ struct GateDecompositionPattern final } struct TwoQubitWeylDecomposition { - fp a; - fp b; - fp c; - fp globalPhase; + // a, b, c are the parameters of the canonical gate (CAN) + fp a; // rotation of RXX gate in CAN + fp b; // rotation of RYY gate in CAN + fp c; // rotation of RZZ gate in CAN + fp globalPhase; // global phase adjustment /** * q1 - k2r - C - k1r - * A * q0 - k2l - N - k1l - */ - matrix2x2 k1l; - matrix2x2 k2l; - matrix2x2 k1r; - matrix2x2 k2r; - Specialization specialization; - EulerBasis defaultEulerBasis; - std::optional requestedFidelity; - fp calculatedFidelity; - matrix4x4 unitaryMatrix; + matrix2x2 k1l; // "left" qubit after canonical gate + matrix2x2 k2l; // "left" qubit before canonical gate + matrix2x2 k1r; // "right" qubit after canonical gate + matrix2x2 k2r; // "right" qubit before canonical gate + Specialization specialization; // detected symmetries in the matrix + EulerBasis defaultEulerBasis; // recommended euler basis for k1l/k2l/k1r/k2r + std::optional requestedFidelity; // desired fidelity + fp calculatedFidelity; // actual fidelity of decomposition + matrix4x4 unitaryMatrix; // original matrix for this decomposition static TwoQubitWeylDecomposition newInner(matrix4x4 unitaryMatrix, std::optional fidelity, @@ -1496,12 +1515,11 @@ struct GateDecompositionPattern final } }; - static constexpr auto DEFAULT_FIDELITY = 1.0 - 1.0e-9; + static constexpr auto DEFAULT_FIDELITY = 1.0 - 1e-15; struct TwoQubitBasisDecomposer { QubitGateSequence::Gate basisGate; fp basisFidelity; - EulerBasis eulerBasis; TwoQubitWeylDecomposition basisDecomposer; bool superControlled; matrix2x2 u0l; @@ -1529,8 +1547,7 @@ struct GateDecompositionPattern final newInner(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, .parameter = {}, .qubitId = {0, 1}}, - fp basisFidelity = DEFAULT_FIDELITY, - EulerBasis eulerBasis = EulerBasis::ZYZ) { + fp basisFidelity = DEFAULT_FIDELITY) { auto relativeEq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& maxRelative) { // Handle same infinities @@ -1646,7 +1663,6 @@ struct GateDecompositionPattern final return TwoQubitBasisDecomposer{ .basisGate = basisGate, .basisFidelity = basisFidelity, - .eulerBasis = eulerBasis, .basisDecomposer = basisDecomposer, .superControlled = superControlled, .u0l = u0l, @@ -1671,10 +1687,11 @@ struct GateDecompositionPattern final }; } - std::optional - twoQubitDecompose(const matrix4x4& unitaryMatrix, - std::optional basisFidelity, bool approximate, - std::optional numBasisGateUses) { + [[nodiscard]] std::optional twoQubitDecompose( + const TwoQubitWeylDecomposition& targetDecomposition, + const llvm::SmallVector& target1qEulerBasisList, + std::optional basisFidelity, bool approximate, + std::optional numBasisGateUses) const { auto getBasisFidelity = [&]() { if (approximate) { return basisFidelity.value_or(this->basisFidelity); @@ -1682,9 +1699,7 @@ struct GateDecompositionPattern final return static_cast(1.0); }; fp actualBasisFidelity = getBasisFidelity(); - auto targetDecomposed = TwoQubitWeylDecomposition::newInner( - unitaryMatrix, actualBasisFidelity, std::nullopt); - auto traces = this->traces(targetDecomposed); + auto traces = this->traces(targetDecomposition); auto getDefaultNbasis = [&]() { auto minValue = std::numeric_limits::min(); auto minIndex = -1; @@ -1701,16 +1716,16 @@ struct GateDecompositionPattern final auto bestNbasis = numBasisGateUses.value_or(getDefaultNbasis()); auto chooseDecomposition = [&]() { if (bestNbasis == 0) { - return decomp0Inner(targetDecomposed); + return decomp0Inner(targetDecomposition); } if (bestNbasis == 1) { - return decomp1Inner(targetDecomposed); + return decomp1Inner(targetDecomposition); } if (bestNbasis == 2) { - return decomp2SupercontrolledInner(targetDecomposed); + return decomp2SupercontrolledInner(targetDecomposition); } if (bestNbasis == 3) { - return decomp3SupercontrolledInner(targetDecomposed); + return decomp3SupercontrolledInner(targetDecomposition); } throw std::logic_error{"Invalid basis to use"}; }; @@ -1723,18 +1738,17 @@ struct GateDecompositionPattern final for (auto x : decomposition) { helpers::print(x, "", true); } - std::vector target1qBasisList; // TODO: simplify because list - // only has one element? - target1qBasisList.push_back(eulerBasis); llvm::SmallVector, 8> eulerDecompositions; for (auto&& decomp : decomposition) { assert(helpers::isUnitaryMatrix(decomp)); - auto eulerDecomp = unitaryToGateSequenceInner( - decomp, target1qBasisList, 0, {}, true, 1.0 - actualBasisFidelity); + auto eulerDecomp = + unitaryToGateSequenceInner(decomp, target1qEulerBasisList, 0, {}, + true, 1.0 - actualBasisFidelity); eulerDecompositions.push_back(eulerDecomp); } - TwoQubitGateSequence gates{.globalPhase = targetDecomposed.globalPhase}; + TwoQubitGateSequence gates{.globalPhase = + targetDecomposition.globalPhase}; // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q // gate We might overallocate a bit if the euler basis is different but // the worst case is just 16 extra elements with just a String and 2 @@ -1869,7 +1883,8 @@ struct GateDecompositionPattern final } static OneQubitGateSequence unitaryToGateSequenceInner( - matrix2x2 unitaryMat, const std::vector& targetBasisList, + matrix2x2 unitaryMat, + const llvm::SmallVector& targetBasisList, std::size_t /*qubit*/, const std::vector>& /*error_map*/, // TODO: remove error_map+qubit for platform @@ -1962,7 +1977,8 @@ struct GateDecompositionPattern final private: llvm::SmallVector decomposerBasisGate; - llvm::SmallVector decomposerEulerBasis; + llvm::SmallVector basisDecomposers; + llvm::SmallVector decomposerEulerBases; }; const matrix2x2 GateDecompositionPattern::IDENTITY_GATE = matrix2x2::Identity(); @@ -1978,7 +1994,8 @@ const matrix2x2 GateDecompositionPattern::IPX{{C_ZERO, IM}, {IM, C_ZERO}}; * decomposition. */ void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { - llvm::SmallVector basisGates; + llvm::SmallVector + basisGates; llvm::SmallVector eulerBases; basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {0, 1}}); basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {1, 0}}); From e3f67b9c4f804479e59b217909972d6e7555f920 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 00:27:31 +0100 Subject: [PATCH 078/100] add checks for most tests --- .../MQTOpt/Transforms/gate-decomposition.mlir | 97 ++++++++++++++++--- 1 file changed, 85 insertions(+), 12 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index c9292b914..f66b0d333 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -76,6 +76,9 @@ module { // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + // CHECK: mqtopt.deallocQubit %[[Q0_1]] // CHECK: mqtopt.deallocQubit %[[Q1_1]] @@ -143,6 +146,9 @@ module { // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] @@ -178,6 +184,9 @@ module { // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] @@ -213,6 +222,9 @@ module { // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + // CHECK: mqtopt.deallocQubit %[[Q0_3]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] @@ -246,9 +258,8 @@ module { // CHECK: %[[Q1_3:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_2]] ctrl %[[Q0_3]] // CHECK: %[[Q0_5:.*]], %[[Q1_4:.*]] = mqtopt.x() %[[Q0_4]] ctrl %[[Q1_3]] - // CHECK-NOT: mqtopt.ry - // CHECK-NOT: mqtopt.rz - // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) // CHECK: mqtopt.deallocQubit %[[Q0_5]] // CHECK: mqtopt.deallocQubit %[[Q1_4]] @@ -313,6 +324,9 @@ module { // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_8]] // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C0]]) %[[Q1_9]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + // CHECK: mqtopt.deallocQubit %[[Q0_10]] // CHECK: mqtopt.deallocQubit %[[Q1_10]] @@ -354,13 +368,24 @@ module { module { // CHECK-LABEL: func.func @testSingleQubitSeries func.func @testSingleQubitSeries() { + // CHECK: %[[C0:.*]] = arith.constant -1.570796326 + // CHECK: %[[C1:.*]] = arith.constant 2.400000e+00 + // CHECK: %[[C2:.*]] = arith.constant 1.5707963267 + // CHECK: %[[C3:.*]] = arith.constant 2.500000e+00 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q0_1:.*]], %[[Q1_1:.*]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C3]]) %[[Q0_0]] + // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C2]]) %[[Q1_0]] + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C1]]) %[[Q1_1]] + // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_2]] + + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_3]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 @@ -386,15 +411,63 @@ module { module { // CHECK-LABEL: func.func @testThreeBasisGateDecomposition func.func @testThreeBasisGateDecomposition() { + // CHECK: %[[C0:.*]] = arith.constant 2.5762316133983267 : f64 + // CHECK: %[[C1:.*]] = arith.constant 0.7853981633974495 : f64 + // CHECK: %[[C2:.*]] = arith.constant -1.5707963267948981 : f64 + // CHECK: %[[C3:.*]] = arith.constant -2.2830967446466035 : f64 + // CHECK: %[[C4:.*]] = arith.constant 2.7053466041231213 : f64 + // CHECK: %[[C5:.*]] = arith.constant -2.4341917193679428 : f64 + // CHECK: %[[C6:.*]] = arith.constant -0.61547970867038693 : f64 + // CHECK: %[[C7:.*]] = arith.constant 2.0943951023931953 : f64 + // CHECK: %[[C8:.*]] = arith.constant 0.61547970867038737 : f64 + // CHECK: %[[C9:.*]] = arith.constant 0.25938051218841107 : f64 + // CHECK: %[[C10:.*]] = arith.constant -2.5261129449194062 : f64 + // CHECK: %[[C11:.*]] = arith.constant 1.0471975511965979 : f64 + // CHECK: %[[C12:.*]] = arith.constant -2.5261129449194053 : f64 + // CHECK: %[[C13:.*]] = arith.constant -1.5707963267948966 : f64 + // CHECK: %[[C14:.*]] = arith.constant 0.6948042173197494 : f64 + // CHECK: %[[C15:.*]] = arith.constant -1.5707963267948968 : f64 + // CHECK: %[[C16:.*]] = arith.constant 0.45501497985473183 : f64 + // CHECK: %[[C17:.*]] = arith.constant 1.7692140485614463 : f64 + // CHECK: %[[C18:.*]] = arith.constant -1.508911464303158 : f64 + // CHECK: %[[C19:.*]] = arith.constant -0.82190477627456371 : f64 + // CHECK: %[[C20:.*]] = arith.constant 1.5707963267948963 : f64 + // CHECK: %[[C21:.*]] = arith.constant 1.5707963267948966 : f64 + // CHECK: %[[C22:.*]] = arith.constant -0.78539816339744739 : f64 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - - // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) - // CHECK: mqtopt.deallocQubit %[[Q0_5]] - // CHECK: mqtopt.deallocQubit %[[Q1_4]] - // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // CHECK: mqtopt.gphase(%[[C22]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C21]]) %[[Q0_0]] : !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C20]]) %[[Q0_1]] : !mqtopt.Qubit + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C19]]) %[[Q0_2]] : !mqtopt.Qubit + // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C18]]) %[[Q1_0]] : !mqtopt.Qubit + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C17]]) %[[Q1_1]] : !mqtopt.Qubit + // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C16]]) %[[Q1_2]] : !mqtopt.Qubit + // CHECK: %[[Q1_4:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_5:.*]] = mqtopt.rz(%[[C15]]) %[[Q0_4]] : !mqtopt.Qubit + // CHECK: %[[Q0_6:.*]] = mqtopt.ry(%[[C14]]) %[[Q0_5]] : !mqtopt.Qubit + // CHECK: %[[Q0_7:.*]] = mqtopt.rz(%[[C13]]) %[[Q0_6]] : !mqtopt.Qubit + // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_4]] : !mqtopt.Qubit + // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C11]]) %[[Q1_5]] : !mqtopt.Qubit + // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_6]] : !mqtopt.Qubit + // CHECK: %[[Q1_8:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_7]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_9:.*]] = mqtopt.rz(%[[C21]]) %[[Q0_8]] : !mqtopt.Qubit + // CHECK: %[[Q0_10:.*]] = mqtopt.ry(%[[C9]]) %[[Q0_9]] : !mqtopt.Qubit + // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C8]]) %[[Q1_8]] : !mqtopt.Qubit + // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C7]]) %[[Q1_9]] : !mqtopt.Qubit + // CHECK: %[[Q1_11:.*]] = mqtopt.rz(%[[C6]]) %[[Q1_10]] : !mqtopt.Qubit + // CHECK: %[[Q1_12:.*]], %[[Q0_11:.*]] = mqtopt.x() %[[Q1_11]] ctrl %[[Q0_10]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_12:.*]] = mqtopt.rz(%[[C5]]) %[[Q0_11]] : !mqtopt.Qubit + // CHECK: %[[Q0_13:.*]] = mqtopt.ry(%[[C4]]) %[[Q0_12]] : !mqtopt.Qubit + // CHECK: %[[Q0_14:.*]] = mqtopt.rz(%[[C3]]) %[[Q0_13]] : !mqtopt.Qubit + // CHECK: %[[Q1_13:.*]] = mqtopt.rz(%[[C2]]) %[[Q1_12]] : !mqtopt.Qubit + // CHECK: %[[Q1_14:.*]] = mqtopt.ry(%[[C1]]) %[[Q1_13]] : !mqtopt.Qubit + // CHECK: %[[Q1_15:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_14]] : !mqtopt.Qubit + + // CHECK: mqtopt.deallocQubit %[[Q0_14]] + // CHECK: mqtopt.deallocQubit %[[Q1_15]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 @@ -435,7 +508,7 @@ module { // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + // TODO // CHECK: mqtopt.deallocQubit %[[Q0_5]] // CHECK: mqtopt.deallocQubit %[[Q1_4]] From b89d602d10a836306921f25417361e2069b993ef Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 00:28:11 +0100 Subject: [PATCH 079/100] TMP, needs to be removed; allow OpenQASM export with round-trip pass --- .../Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp | 5 ++++- .../MQTOpt/Transforms/ToQuantumComputationPattern.cpp | 11 +++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp index 276b31502..37a7f2029 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp @@ -33,12 +33,15 @@ struct MQTCoreRoundTrip final : impl::MQTCoreRoundTripBase { // Define the set of patterns to use. mlir::RewritePatternSet patterns(ctx); populateToQuantumComputationPatterns(patterns, circuit); - populateFromQuantumComputationPatterns(patterns, circuit); + // populateFromQuantumComputationPatterns(patterns, circuit); // Apply patterns in an iterative and greedy manner. if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { signalPassFailure(); } + std::cerr << "======================\n"; + circuit.dumpOpenQASM(std::cerr, false); + std::cerr << "======================\n"; } }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp index 06c64a6b1..481897736 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "Helpers.h" #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/Control.hpp" @@ -212,7 +213,9 @@ struct ToQuantumComputationPattern final findQubitIndex(val, currentQubitVariables)); } // Get the qubit index of the target qubit (if already collected). + if (!in.empty()) { targetIndex[0] = findQubitIndex(in[0], currentQubitVariables); + } if (in.size() > 1) { targetIndex[1] = findQubitIndex(in[1], currentQubitVariables); } @@ -233,7 +236,9 @@ struct ToQuantumComputationPattern final currentQubitVariables[negCtrlInsIndices[i]] = outs[i + 1 + posCtrlInsIndices.size()]; } - currentQubitVariables[targetIndex[0]] = outs[0]; + if (!op.getOutQubits().empty()) { + currentQubitVariables[targetIndex[0]] = outs[0]; + } if (op.getOutQubits().size() > 1) { currentQubitVariables[targetIndex[1]] = outs[1]; } @@ -255,6 +260,8 @@ struct ToQuantumComputationPattern final parameters.emplace_back(param); } } + auto x = helpers::getParameters(op); + parameters.insert(parameters.end(), x.begin(), x.end()); if (op.getOutQubits().size() > 1) { circuit.emplace_back( @@ -381,7 +388,7 @@ struct ToQuantumComputationPattern final std::string regName; llvm::raw_string_ostream os(regName); - op.getResult().print(os); + os << "q"; circuit.addQubitRegister(numQubits, regName); circuit.addClassicalRegister(numQubits); From c531c560a76c8eabef83ccbbfdf21fe10f8cb84d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 16:49:58 +0100 Subject: [PATCH 080/100] add single-qubit backtracking --- .../Transforms/GateDecompositionPattern.cpp | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index f1345877d..b3ec45bf5 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -59,6 +59,7 @@ struct GateDecompositionPattern final }; std::vector gates; [[nodiscard]] std::size_t complexity() const { + // TODO: caching mechanism std::size_t c{}; for (auto&& gate : gates) { c += getComplexity(gate.type, gate.qubitId.size()); @@ -117,8 +118,7 @@ struct GateDecompositionPattern final // too short return mlir::failure(); } - if (llvm::is_contained(series.inQubits, mlir::Value{}) || - llvm::is_contained(series.outQubits, mlir::Value{})) { + if (series.isSingleQubitSeries()) { // only a single-qubit series return mlir::failure(); } @@ -224,7 +224,7 @@ struct GateDecompositionPattern final return result; } - matrix4x4 getUnitaryMatrix() { + [[nodiscard]] matrix4x4 getUnitaryMatrix() const { matrix4x4 unitaryMatrix = helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); for (auto&& gate : gates) { @@ -240,6 +240,11 @@ struct GateDecompositionPattern final return unitaryMatrix; } + [[nodiscard]] bool isSingleQubitSeries() const { + return llvm::is_contained(inQubits, mlir::Value{}) || + llvm::is_contained(outQubits, mlir::Value{}); + } + private: explicit TwoQubitSeries(UnitaryInterface initialOperation) { auto&& in = initialOperation.getAllInQubits(); @@ -298,13 +303,25 @@ struct GateDecompositionPattern final if (it == outQubits.end()) { return false; } + // iterator in the operation input of "old" qubit that already has + // previous single-qubit gates in this series auto it2 = llvm::find(opInQubits, firstQubitIt != outQubits.end() ? *firstQubitIt : *secondQubitIt); - inQubits[std::distance(outQubits.begin(), it)] = - opInQubits[1 - std::distance(opInQubits.begin(), it2)]; + // new qubit ID based on position in outQubits + auto newInQubitId = std::distance(outQubits.begin(), it); + // position in operation input; since there are only two qubits, it must + // be the "not old" one + auto newOpInQubitId = 1 - std::distance(opInQubits.begin(), it2); + + // update inQubit and update dangling iterator, then proceed as usual + inQubits[newInQubitId] = opInQubits[newOpInQubitId]; firstQubitIt = (firstQubitIt != outQubits.end()) ? firstQubitIt : it; secondQubitIt = (secondQubitIt != outQubits.end()) ? secondQubitIt : it; + + // before proceeding as usual, see if backtracking on the "new" qubit is + // possible to collect other single-qubit operations + backtrackSingleQubitSeries(newInQubitId); } std::size_t firstQubitId = std::distance(outQubits.begin(), firstQubitIt); std::size_t secondQubitId = @@ -317,6 +334,30 @@ struct GateDecompositionPattern final complexity += getComplexity(helpers::getQcType(nextGate), 2); return true; } + + /** + * Traverse single-qubit series back from a given qubit. + * This is used when a series starts with single-qubit gates and then + * encounters a two-qubit gate. The second qubit involved in the two-qubit + * gate could have previous single-qubit operations that can be incorporated + * in the series. + */ + void backtrackSingleQubitSeries(std::size_t qubitId) { + auto prependSingleQubitGate = [&](UnitaryInterface op) { + inQubits[qubitId] = op.getAllInQubits()[0]; + gates.insert(gates.begin(), {.op = op, .qubitIds = {qubitId}}); + // outQubits do not need to be updated because the final out qubit is + // already fixed + }; + while (auto* op = inQubits[qubitId].getDefiningOp()) { + auto unitaryOp = mlir::dyn_cast(op); + if (unitaryOp && helpers::isSingleQubitOperation(unitaryOp)) { + prependSingleQubitGate(unitaryOp); + } else { + break; + } + } + } }; /** From 75d64a3c706d27844718825bd939cc7ae628b687 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 16:50:20 +0100 Subject: [PATCH 081/100] fix atol issues --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index b3ec45bf5..08fb0014c 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -1557,6 +1557,7 @@ struct GateDecompositionPattern final }; static constexpr auto DEFAULT_FIDELITY = 1.0 - 1e-15; + static constexpr auto DEFAULT_ATOL = 1e-12; struct TwoQubitBasisDecomposer { QubitGateSequence::Gate basisGate; @@ -1783,9 +1784,8 @@ struct GateDecompositionPattern final eulerDecompositions; for (auto&& decomp : decomposition) { assert(helpers::isUnitaryMatrix(decomp)); - auto eulerDecomp = - unitaryToGateSequenceInner(decomp, target1qEulerBasisList, 0, {}, - true, 1.0 - actualBasisFidelity); + auto eulerDecomp = unitaryToGateSequenceInner( + decomp, target1qEulerBasisList, 0, {}, true, std::nullopt); eulerDecompositions.push_back(eulerDecomp); } TwoQubitGateSequence gates{.globalPhase = @@ -1972,7 +1972,7 @@ struct GateDecompositionPattern final calculateRotationGates(fp theta, fp phi, fp lambda, fp phase, qc::OpType kGate, qc::OpType aGate, bool simplify, std::optional atol) { - fp angleZeroEpsilon = atol.value_or(1e-12); + fp angleZeroEpsilon = atol.value_or(DEFAULT_ATOL); if (!simplify) { angleZeroEpsilon = -1.0; } From 13d9a0d2f7006c917a0b67cf1fcd8e7c2b4ae549 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 19:00:35 +0100 Subject: [PATCH 082/100] add more euler bases to pattern --- mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 08fb0014c..7ccf7af45 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -2041,6 +2041,8 @@ void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {0, 1}}); basisGates.push_back({.type = qc::X, .parameter = {}, .qubitId = {1, 0}}); eulerBases.push_back(GateDecompositionPattern::EulerBasis::ZYZ); + eulerBases.push_back(GateDecompositionPattern::EulerBasis::XYX); + eulerBases.push_back(GateDecompositionPattern::EulerBasis::ZXZ); patterns.add(patterns.getContext(), basisGates, eulerBases); } From d14ac7626191007f67e5afb8097016bc22887ef0 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 19:01:35 +0100 Subject: [PATCH 083/100] minor cleanup (renaming, comments, disable printing) --- .../Transforms/GateDecompositionPattern.cpp | 195 +++++++++++------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 3 - 2 files changed, 122 insertions(+), 76 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 7ccf7af45..cb051b147 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -100,7 +100,7 @@ struct GateDecompositionPattern final decomposerEulerBases{std::move(eulerBasis)} { for (auto&& basisGate : decomposerBasisGate) { basisDecomposers.push_back( - TwoQubitBasisDecomposer::newInner(basisGate, DEFAULT_FIDELITY)); + TwoQubitBasisDecomposer::create(basisGate, DEFAULT_FIDELITY)); } } @@ -124,9 +124,9 @@ struct GateDecompositionPattern final } matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); - helpers::print(unitaryMatrix, "UNITARY MATRIX", true); + helpers::print(unitaryMatrix, "UNITARY MATRIX"); - auto targetDecomposition = TwoQubitWeylDecomposition::newInner( + auto targetDecomposition = TwoQubitWeylDecomposition::create( unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); std::optional bestSequence; @@ -518,9 +518,9 @@ struct GateDecompositionPattern final } } std::cerr << '\n'; - helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY", true); + helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY"); helpers::print((unitaryMatrix * std::exp(IM * sequence.globalPhase)).eval(), - "RESULT UNITARY MATRIX", true); + "RESULT UNITARY MATRIX"); assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)) .isApprox(series.getUnitaryMatrix(), SANITY_CHECK_PRECISION)); @@ -609,13 +609,11 @@ struct GateDecompositionPattern final matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, {specialUnitary(1, 0), specialUnitary(1, 1)}}; auto detR = r.determinant(); - std::cerr << "DET_R: " << detR << '\n'; if (std::abs(detR) < 0.1) { // third quadrant r = matrix2x2{{specialUnitary(2, 0), specialUnitary(2, 1)}, {specialUnitary(3, 0), specialUnitary(3, 1)}}; detR = r.determinant(); - std::cerr << "DET_R CORRECTION: " << detR << '\n'; } if (std::abs(detR) < 0.1) { throw std::runtime_error{ @@ -906,22 +904,19 @@ struct GateDecompositionPattern final matrix4x4 unitaryMatrix; // original matrix for this decomposition static TwoQubitWeylDecomposition - newInner(matrix4x4 unitaryMatrix, std::optional fidelity, - std::optional specialization) { + create(matrix4x4 unitaryMatrix, std::optional fidelity, + std::optional specialization) { auto u = unitaryMatrix; auto detU = u.determinant(); - std::cerr << "DET_U: " << detU << '\n'; auto detPow = std::pow(detU, static_cast(-0.25)); u *= detPow; - helpers::print(u, "U", true); + helpers::print(u, "U"); auto globalPhase = std::arg(detU) / 4.; auto uP = magicBasisTransform(u, MagicBasisTransform::OutOf); - helpers::print(uP, "U_P", true); + helpers::print(uP, "U_P"); matrix4x4 m2 = uP.transpose() * uP; auto defaultEulerBasis = EulerBasis::ZYZ; - helpers::print(m2, "M2", true); - - std::cerr << "DET_U after division: " << u.determinant() << '\n'; + helpers::print(m2, "M2"); // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -963,8 +958,8 @@ struct GateDecompositionPattern final matrix4x4 pInner = pInnerReal; diagonal4x4 dInner = (pInner.transpose() * m2 * pInner).diagonal(); - helpers::print(dInner, "D_INNER", true); - helpers::print(pInner, "P_INNER", true); + helpers::print(dInner, "D_INNER"); + helpers::print(pInner, "P_INNER"); matrix4x4 diagD = dInner.asDiagonal(); matrix4x4 compare = pInner * diagD * pInner.transpose(); @@ -995,13 +990,13 @@ struct GateDecompositionPattern final // Step 7 Eigen::Vector cs; rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; - helpers::print(dReal, "D_REAL", true); + helpers::print(dReal, "D_REAL"); dReal(3) = -dReal(0) - dReal(1) - dReal(2); for (int i = 0; i < static_cast(cs.size()); ++i) { assert(i < dReal.size()); cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); } - helpers::print(cs, "CS (1)", true); + helpers::print(cs, "CS (1)"); decltype(cs) cstemp; llvm::transform(cs, cstemp.begin(), [](auto&& x) { @@ -1026,7 +1021,7 @@ struct GateDecompositionPattern final std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; std::tie(dReal(0), dReal(1), dReal(2)) = std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - helpers::print(dReal, "D_REAL (sorted)", true); + helpers::print(dReal, "D_REAL (sorted)"); // swap columns of p according to order matrix4x4 pOrig = p; @@ -1045,18 +1040,18 @@ struct GateDecompositionPattern final // temp = temp.conjugate(); // temp += matrix4x4::Constant(0.0); - helpers::print(temp, "TEMP", true); - helpers::print(p, "P", true); + helpers::print(temp, "TEMP"); + helpers::print(p, "P"); assert(std::abs(p.determinant() - 1.0) < SANITY_CHECK_PRECISION); // https://threeplusone.com/pubs/on_gates.pdf // uP = V, m2 = V^T*V, temp = D, p = Q1 matrix4x4 k1 = uP * p * temp; - helpers::print(k1, "K1 (1)", true); + helpers::print(k1, "K1 (1)"); assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal assert(k1.determinant().real() > 0.0); k1 = magicBasisTransform(k1, MagicBasisTransform::Into); matrix4x4 k2 = p.transpose().conjugate(); - helpers::print(k2, "K2 (1)", true); + helpers::print(k2, "K2 (1)"); assert((k2.transpose() * k2).isIdentity()); // k2 must be orthogonal assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); @@ -1066,7 +1061,7 @@ struct GateDecompositionPattern final magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * k2) .eval(), - "SANITY CHECK (1)", true); + "SANITY CHECK (1)"); assert((k1 * magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * k2) @@ -1133,16 +1128,16 @@ struct GateDecompositionPattern final globalPhase -= qc::PI_2; } - helpers::print(K1l, "K1l (1)", true); - helpers::print(K2l, "K2l (1)", true); - helpers::print(K1r, "K1r (1)", true); - helpers::print(K2r, "K2r (1)", true); + helpers::print(K1l, "K1l (1)"); + helpers::print(K2l, "K2l (1)"); + helpers::print(K1r, "K1r (1)"); + helpers::print(K2r, "K2r (1)"); - helpers::print(cs, "CS (2)", true); - helpers::print(K1l, "K1l (2)", true); - helpers::print(K2l, "K2l (2)", true); - helpers::print(K1r, "K1r (2)", true); - helpers::print(K2r, "K2r (2)", true); + helpers::print(cs, "CS (2)"); + helpers::print(K1l, "K1l (2)"); + helpers::print(K2l, "K2l (2)"); + helpers::print(K1r, "K1r (2)"); + helpers::print(K2r, "K2r (2)"); auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); auto getCanonicalMatrix = [](fp a, fp b, fp c) -> matrix4x4 { auto xx = getTwoQubitMatrix({ @@ -1163,19 +1158,15 @@ struct GateDecompositionPattern final return zz * yy * xx; }; helpers::print(getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0), - "SANITY CHECK (2.1)", true); - helpers::print(helpers::kroneckerProduct(K1l, K1r), "SANITY CHECK (2.2)", - true); - helpers::print(helpers::kroneckerProduct(K2l, K2r), "SANITY CHECK (2.3)", - true); + "SANITY CHECK (2.1)"); + helpers::print(helpers::kroneckerProduct(K1l, K1r), "SANITY CHECK (2.2)"); + helpers::print(helpers::kroneckerProduct(K2l, K2r), "SANITY CHECK (2.3)"); helpers::print((helpers::kroneckerProduct(K1l, K1r) * getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) .eval(), - "SANITY CHECK (2.x)", true); - std::cerr << "gphase: " << globalPhase << ", phase_l: " << phase_l - << ", phase_r: " << phase_r << '\n'; + "SANITY CHECK (2.x)"); assert((helpers::kroneckerProduct(K1l, K1r) * getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) @@ -1544,7 +1535,7 @@ struct GateDecompositionPattern final helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * std::exp(IM * specialized.globalPhase)) .eval(), - "SANITY CHECK (3)", true); + "SANITY CHECK (3)"); assert((helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, specialized.c * -2.0) * @@ -1586,10 +1577,10 @@ struct GateDecompositionPattern final public: static TwoQubitBasisDecomposer - newInner(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, - .parameter = {}, - .qubitId = {0, 1}}, - fp basisFidelity = DEFAULT_FIDELITY) { + create(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, + .parameter = {}, + .qubitId = {0, 1}}, + fp basisFidelity = DEFAULT_FIDELITY) { auto relativeEq = [](auto&& lhs, auto&& rhs, auto&& epsilon, auto&& maxRelative) { // Handle same infinities @@ -1625,7 +1616,7 @@ struct GateDecompositionPattern final {qfp(-0.5, 0.5), qfp(0.5, -0.5)}, }; - auto basisDecomposer = TwoQubitWeylDecomposition::newInner( + auto basisDecomposer = TwoQubitWeylDecomposition::create( getTwoQubitMatrix(basisGate), basisFidelity, std::nullopt); auto superControlled = relativeEq(basisDecomposer.a, qc::PI_4, 1e-13, 1e-09) && @@ -1729,11 +1720,11 @@ struct GateDecompositionPattern final }; } - [[nodiscard]] std::optional twoQubitDecompose( - const TwoQubitWeylDecomposition& targetDecomposition, - const llvm::SmallVector& target1qEulerBasisList, - std::optional basisFidelity, bool approximate, - std::optional numBasisGateUses) const { + [[nodiscard]] std::optional + twoQubitDecompose(const TwoQubitWeylDecomposition& targetDecomposition, + const llvm::SmallVector& target1qEulerBases, + std::optional basisFidelity, bool approximate, + std::optional numBasisGateUses) const { auto getBasisFidelity = [&]() { if (approximate) { return basisFidelity.value_or(this->basisFidelity); @@ -1746,6 +1737,8 @@ struct GateDecompositionPattern final auto minValue = std::numeric_limits::min(); auto minIndex = -1; for (int i = 0; i < static_cast(traces.size()); ++i) { + // lower fidelity means it becomes easier to choose a lower number of + // basis gates auto value = traceToFid(traces[i]) * std::pow(actualBasisFidelity, i); if (value > minValue) { minIndex = i; @@ -1758,34 +1751,26 @@ struct GateDecompositionPattern final auto bestNbasis = numBasisGateUses.value_or(getDefaultNbasis()); auto chooseDecomposition = [&]() { if (bestNbasis == 0) { - return decomp0Inner(targetDecomposition); + return decomp0(targetDecomposition); } if (bestNbasis == 1) { - return decomp1Inner(targetDecomposition); + return decomp1(targetDecomposition); } if (bestNbasis == 2) { - return decomp2SupercontrolledInner(targetDecomposition); + return decomp2Supercontrolled(targetDecomposition); } if (bestNbasis == 3) { - return decomp3SupercontrolledInner(targetDecomposition); + return decomp3Supercontrolled(targetDecomposition); } throw std::logic_error{"Invalid basis to use"}; }; auto decomposition = chooseDecomposition(); - std::cerr << "NBasis: " << static_cast(bestNbasis) - << "; basis_fid: " << actualBasisFidelity - << "; Traces: " << traces[0] << ", " << traces[1] << ", " - << traces[2] << ", " << traces[3]; - std::cerr << "\nDecompositions:\n"; - for (auto x : decomposition) { - helpers::print(x, "", true); - } llvm::SmallVector, 8> eulerDecompositions; for (auto&& decomp : decomposition) { assert(helpers::isUnitaryMatrix(decomp)); auto eulerDecomp = unitaryToGateSequenceInner( - decomp, target1qEulerBasisList, 0, {}, true, std::nullopt); + decomp, target1qEulerBases, 0, {}, true, std::nullopt); eulerDecompositions.push_back(eulerDecomp); } TwoQubitGateSequence gates{.globalPhase = @@ -1814,7 +1799,6 @@ struct GateDecompositionPattern final } }; - // TODO: check if this actually uses the correct qubitIds for (std::size_t i = 0; i < bestNbasis; ++i) { addEulerDecomposition(2 * i, 0); addEulerDecomposition((2 * i) + 1, 1); @@ -1834,16 +1818,43 @@ struct GateDecompositionPattern final } private: + /** + * Calculate decompositions when no basis gate is required. + * + * Decompose target :math:`\sim U_d(x, y, z)` with :math:`0` uses of the + * basis gate. Result :math:`U_r` has trace: + * + * .. math:: + * + * \Big\vert\text{Tr}(U_r\cdot U_\text{target}^{\dag})\Big\vert = + * 4\Big\vert (\cos(x)\cos(y)\cos(z)+ j \sin(x)\sin(y)\sin(z)\Big\vert + * + * which is optimal for all targets and bases + */ [[nodiscard]] static std::vector - decomp0Inner(const TwoQubitWeylDecomposition& target) { + decomp0(const TwoQubitWeylDecomposition& target) { return { target.k1r * target.k2r, target.k1l * target.k2l, }; } + /** + * Calculate decompositions when one basis gate is required. + * + * Decompose target :math:`\sim U_d(x, y, z)` with :math:`1` use of the + * basis gate math:`\sim U_d(a, b, c)`. Result :math:`U_r` has trace: + * + * .. math:: + * + * \Big\vert\text{Tr}(U_r \cdot U_\text{target}^{\dag})\Big\vert = + * 4\Big\vert \cos(x-a)\cos(y-b)\cos(z-c) + j + * \sin(x-a)\sin(y-b)\sin(z-c)\Big\vert + * + * which is optimal for all targets and bases with ``z==0`` or ``c==0``. + */ [[nodiscard]] std::vector - decomp1Inner(const TwoQubitWeylDecomposition& target) const { + decomp1(const TwoQubitWeylDecomposition& target) const { // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) return { @@ -1854,8 +1865,31 @@ struct GateDecompositionPattern final }; } + /** + * Calculate decompositions when two basis gates are required. + * + * Decompose target :math:`\sim U_d(x, y, z)` with :math:`2` uses of the + * basis gate. + * + * For supercontrolled basis :math:`\sim U_d(\pi/4, b, 0)`, all b, result + * :math:`U_r` has trace + * + * .. math:: + * + * \Big\vert\text{Tr}(U_r \cdot U_\text{target}^\dag) \Big\vert = + * 4\cos(z) + * + * which is the optimal approximation for basis of CNOT-class :math:`\sim + * U_d(\pi/4, 0, 0)` or DCNOT-class :math:`\sim U_d(\pi/4, \pi/4, 0)` and + * any target. It may be sub-optimal for :math:`b \neq 0` (i.e. there exists + * an exact decomposition for any target using + * :math:`B \sim U_d(\pi/4, \pi/8, 0)`, but it may not be this + * decomposition). This is an exact decomposition for supercontrolled basis + * and target :math:`\sim U_d(x, y, 0)`. No guarantees for + * non-supercontrolled basis. + */ [[nodiscard]] std::vector - decomp2SupercontrolledInner(const TwoQubitWeylDecomposition& target) const { + decomp2Supercontrolled(const TwoQubitWeylDecomposition& target) const { return { q2r * target.k2r, q2l * target.k2l, @@ -1866,8 +1900,17 @@ struct GateDecompositionPattern final }; } + /** + * Calculate decompositions when three basis gates are required. + * + * Decompose target with :math:`3` uses of the basis. + * + * This is an exact decomposition for supercontrolled basis + * :math:`\sim U_d(\pi/4, b, 0)`, all b, and any target. No guarantees for + * non-supercontrolled basis. + */ [[nodiscard]] std::vector - decomp3SupercontrolledInner(const TwoQubitWeylDecomposition& target) const { + decomp3Supercontrolled(const TwoQubitWeylDecomposition& target) const { return { u3r * target.k2r, u3l * target.k2l, @@ -1880,6 +1923,12 @@ struct GateDecompositionPattern final }; } + /** + * Calculate traces for a combination of the parameters of the canonical + * gates of the target and basis decompositions. + * This can be used to determine the smallest number of basis gates that are + * necessary to construct an equivalent to the canonical gate. + */ [[nodiscard]] std::array traces(TwoQubitWeylDecomposition target) const { return { @@ -1939,9 +1988,9 @@ struct GateDecompositionPattern final OneQubitGateSequence bestCircuit; for (auto targetBasis : targetBasisList) { auto circuit = generateCircuit(targetBasis, unitaryMat, simplify, atol); - helpers::print(circuit.getUnitaryMatrix(), "SANITY CHECK (4.1)", true); + helpers::print(circuit.getUnitaryMatrix(), "SANITY CHECK (4.1)"); helpers::print(helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), - "SANITY CHECK (4.2)", true); + "SANITY CHECK (4.2)"); assert(circuit.getUnitaryMatrix().isApprox( helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), SANITY_CHECK_PRECISION)); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 5180a7ea9..d8ab37346 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -216,12 +216,9 @@ inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, template inline auto selfAdjointEvd(Eigen::Matrix a) { Eigen::SelfAdjointEigenSolver s; - std::cerr << "=EigIN==\n" << a << "\n========\n" << '\n'; s.compute(a); // TODO: computeDirect is faster auto vecs = s.eigenvectors().eval(); auto vals = s.eigenvalues(); - std::cerr << "=Eigen==\n" << vecs << "\n========\n" << '\n'; - std::cerr << "=Eigen==\n" << vals << "\n========\n" << '\n'; return std::make_pair(vecs, vals); } From 453565fd6a8a7ce5776570633395476cf2627c4d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 19 Nov 2025 20:19:24 +0100 Subject: [PATCH 084/100] Revert "TMP, needs to be removed; allow OpenQASM export with round-trip pass" This reverts commit 4b54f872a7b934f9d1ed5ae1fe528bcf5af243e3. --- .../Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp | 5 +---- .../MQTOpt/Transforms/ToQuantumComputationPattern.cpp | 11 ++--------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp index 37a7f2029..276b31502 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/MQTCoreRoundTrip.cpp @@ -33,15 +33,12 @@ struct MQTCoreRoundTrip final : impl::MQTCoreRoundTripBase { // Define the set of patterns to use. mlir::RewritePatternSet patterns(ctx); populateToQuantumComputationPatterns(patterns, circuit); - // populateFromQuantumComputationPatterns(patterns, circuit); + populateFromQuantumComputationPatterns(patterns, circuit); // Apply patterns in an iterative and greedy manner. if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { signalPassFailure(); } - std::cerr << "======================\n"; - circuit.dumpOpenQASM(std::cerr, false); - std::cerr << "======================\n"; } }; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp index 481897736..06c64a6b1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/ToQuantumComputationPattern.cpp @@ -8,7 +8,6 @@ * Licensed under the MIT License */ -#include "Helpers.h" #include "ir/Definitions.hpp" #include "ir/QuantumComputation.hpp" #include "ir/operations/Control.hpp" @@ -213,9 +212,7 @@ struct ToQuantumComputationPattern final findQubitIndex(val, currentQubitVariables)); } // Get the qubit index of the target qubit (if already collected). - if (!in.empty()) { targetIndex[0] = findQubitIndex(in[0], currentQubitVariables); - } if (in.size() > 1) { targetIndex[1] = findQubitIndex(in[1], currentQubitVariables); } @@ -236,9 +233,7 @@ struct ToQuantumComputationPattern final currentQubitVariables[negCtrlInsIndices[i]] = outs[i + 1 + posCtrlInsIndices.size()]; } - if (!op.getOutQubits().empty()) { - currentQubitVariables[targetIndex[0]] = outs[0]; - } + currentQubitVariables[targetIndex[0]] = outs[0]; if (op.getOutQubits().size() > 1) { currentQubitVariables[targetIndex[1]] = outs[1]; } @@ -260,8 +255,6 @@ struct ToQuantumComputationPattern final parameters.emplace_back(param); } } - auto x = helpers::getParameters(op); - parameters.insert(parameters.end(), x.begin(), x.end()); if (op.getOutQubits().size() > 1) { circuit.emplace_back( @@ -388,7 +381,7 @@ struct ToQuantumComputationPattern final std::string regName; llvm::raw_string_ostream os(regName); - os << "q"; + op.getResult().print(os); circuit.addQubitRegister(numQubits, regName); circuit.addClassicalRegister(numQubits); From 29d01aa17fb2d90195e56cc1d7e5c95b615d1e40 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 13:05:17 +0100 Subject: [PATCH 085/100] remove armadillo in cmake --- CMakeLists.txt | 18 ------------------ .../Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9bc120c4..0cd8e9551 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,24 +24,6 @@ include(PackageAddTest) include(Cache) include(AddMQTCoreLibrary) -# include(FetchContent) -# FetchContent_Declare( -# armadillo -# GIT_REPOSITORY https://gitlab.com/conradsnicta/armadillo-code.git -# GIT_TAG 15.0.1 -# ) -# FetchContent_MakeAvailable(armadillo) -find_package(Armadillo 15 REQUIRED) -if(Armadillo_FOUND AND NOT TARGET Armadillo::Armadillo) - add_library(Armadillo::Armadillo INTERFACE IMPORTED) - set_target_properties( - Armadillo::Armadillo - PROPERTIES - INTERFACE_LINK_LIBRARIES "${ARMADILLO_LIBRARIES}" - INTERFACE_INCLUDE_DIRECTORIES "${ARMADILLO_INCLUDE_DIRS}" - ) -endif() - option(BUILD_MQT_CORE_BINDINGS "Build the MQT Core Python bindings" OFF) if(BUILD_MQT_CORE_BINDINGS) # ensure that the BINDINGS option is set diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index 2098ff4af..6badce698 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD Armadillo::Armadillo) +set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD) add_compile_options(-fexceptions) file(GLOB_RECURSE TRANSFORMS_SOURCES *.cpp) From b5f4b2d363b505bd65df5a211d3059c058f6756f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 13:07:16 +0100 Subject: [PATCH 086/100] more cleanup --- .../Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- .../MQTOpt/Transforms/GateDecomposition.cpp | 3 +- .../Transforms/GateDecompositionPattern.cpp | 120 ++- mlir/lib/Dialect/MQTOpt/Transforms/a.cpp | 904 ------------------ 4 files changed, 96 insertions(+), 933 deletions(-) delete mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/a.cpp diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index 6badce698..e7f1f55da 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -set(LIBRARIES ${dialect_libs} MQT::CoreIR MQT::CoreDD) +set(LIBRARIES ${dialect_libs} MQT::CoreIR) add_compile_options(-fexceptions) file(GLOB_RECURSE TRANSFORMS_SOURCES *.cpp) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index a5c039568..7c60ab259 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -40,7 +40,8 @@ struct GateDecomposition final config.setUseTopDownTraversal(true); // Apply patterns in an iterative and greedy manner. - if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns), config))) { + if (mlir::failed( + mlir::applyPatternsGreedily(op, std::move(patterns), config))) { signalPassFailure(); } } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index cb051b147..8e01af1dd 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -51,31 +51,49 @@ struct GateDecompositionPattern final ZSX = 11, }; + using QubitId = std::size_t; + /** + * Gate sequence of single-qubit and/or two-qubit gates. + */ struct QubitGateSequence { + /** + * Gate description which should be able to represent every possible + * one-qubit or two-qubit operation. + */ struct Gate { qc::OpType type{qc::I}; llvm::SmallVector parameter; - llvm::SmallVector qubitId = {0}; + llvm::SmallVector qubitId = {0}; }; + /** + * Container sorting the gate sequence in order. + */ std::vector gates; + + fp globalPhase{}; + [[nodiscard]] bool hasGlobalPhase() const { + return std::abs(globalPhase) > DEFAULT_ATOL; + } + + /** + * Calculate complexity of sequence according to getComplexity(). + */ [[nodiscard]] std::size_t complexity() const { - // TODO: caching mechanism + // TODO: caching mechanism? std::size_t c{}; for (auto&& gate : gates) { c += getComplexity(gate.type, gate.qubitId.size()); } if (hasGlobalPhase()) { - // need to add two phase gates if a global phase needs to be applied - c += 2 * getComplexity(qc::P, 1); + // need to add a global phase gate if a global phase needs to be applied + c += getComplexity(qc::GPhase, 1); } return c; } - fp globalPhase{}; - [[nodiscard]] bool hasGlobalPhase() const { - return std::abs(globalPhase) > std::numeric_limits::epsilon(); - } - + /** + * Calculate overall unitary matrix of the sequence. + */ [[nodiscard]] matrix4x4 getUnitaryMatrix() const { matrix4x4 unitaryMatrix = helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); @@ -91,6 +109,11 @@ struct GateDecompositionPattern final using OneQubitGateSequence = QubitGateSequence; using TwoQubitGateSequence = QubitGateSequence; + /** + * Initialize pattern with a set of basis gates and euler bases. + * The best combination of (basis gate, euler basis) will be evaluated for + * each decomposition. + */ explicit GateDecompositionPattern( mlir::MLIRContext* context, llvm::SmallVector basisGate, @@ -119,13 +142,12 @@ struct GateDecompositionPattern final return mlir::failure(); } if (series.isSingleQubitSeries()) { - // only a single-qubit series + // only a single-qubit series; + // single-qubit euler decomposition is more efficient return mlir::failure(); } matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); - helpers::print(unitaryMatrix, "UNITARY MATRIX"); - auto targetDecomposition = TwoQubitWeylDecomposition::create( unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); @@ -174,14 +196,29 @@ struct GateDecompositionPattern final } struct TwoQubitSeries { + /** + * Complexity of series using getComplexity() for each gate. + */ std::size_t complexity{0}; + /** + * Qubits that are the input for the series. + * First qubit will always be set, second qubit may be equal to + * mlir::Value{} if the series consists of only single-qubit gates. + * + * All + */ std::array inQubits; + /** + * Qubits that are the input for the series. + * First qubit will always be set, second qubit may be equal to + * mlir::Value{} if the series consists of only single-qubit gates. + */ std::array outQubits; fp globalPhase{}; struct Gate { UnitaryInterface op; - llvm::SmallVector qubitIds; + llvm::SmallVector qubitIds; }; llvm::SmallVector gates; @@ -261,7 +298,7 @@ struct GateDecompositionPattern final complexity += getComplexity(helpers::getQcType(initialOperation), in.size()); - // TODO: necessary when using non-global phase gates? + // TODO: necessary? for (auto&& globalPhaseOp : initialOperation->getBlock()->getOps()) { globalPhase += helpers::getParameters(globalPhaseOp)[0]; @@ -279,7 +316,7 @@ struct GateDecompositionPattern final throw std::logic_error{"Operand of single-qubit op and user of " "qubit is not in current outQubits"}; } - std::size_t qubitId = std::distance(outQubits.begin(), it); + QubitId qubitId = std::distance(outQubits.begin(), it); *it = nextGate->getResult(0); gates.push_back({.op = nextGate, .qubitIds = {qubitId}}); @@ -309,10 +346,10 @@ struct GateDecompositionPattern final ? *firstQubitIt : *secondQubitIt); // new qubit ID based on position in outQubits - auto newInQubitId = std::distance(outQubits.begin(), it); + QubitId newInQubitId = std::distance(outQubits.begin(), it); // position in operation input; since there are only two qubits, it must // be the "not old" one - auto newOpInQubitId = 1 - std::distance(opInQubits.begin(), it2); + QubitId newOpInQubitId = 1 - std::distance(opInQubits.begin(), it2); // update inQubit and update dangling iterator, then proceed as usual inQubits[newInQubitId] = opInQubits[newOpInQubitId]; @@ -323,9 +360,8 @@ struct GateDecompositionPattern final // possible to collect other single-qubit operations backtrackSingleQubitSeries(newInQubitId); } - std::size_t firstQubitId = std::distance(outQubits.begin(), firstQubitIt); - std::size_t secondQubitId = - std::distance(outQubits.begin(), secondQubitIt); + QubitId firstQubitId = std::distance(outQubits.begin(), firstQubitIt); + QubitId secondQubitId = std::distance(outQubits.begin(), secondQubitIt); *firstQubitIt = nextGate->getResult(0); *secondQubitIt = nextGate->getResult(1); @@ -342,7 +378,7 @@ struct GateDecompositionPattern final * gate could have previous single-qubit operations that can be incorporated * in the series. */ - void backtrackSingleQubitSeries(std::size_t qubitId) { + void backtrackSingleQubitSeries(QubitId qubitId) { auto prependSingleQubitGate = [&](UnitaryInterface op) { inQubits[qubitId] = op.getAllInQubits()[0]; gates.insert(gates.begin(), {.op = op, .qubitIds = {qubitId}}); @@ -409,7 +445,7 @@ struct GateDecompositionPattern final auto inQubits = series.inQubits; auto updateInQubits = - [&inQubits](const llvm::SmallVector& qubitIds, + [&inQubits](const llvm::SmallVector& qubitIds, auto&& newGate) { // TODO: need to handle controls differently? auto results = newGate.getAllOutQubits(); @@ -852,11 +888,11 @@ struct GateDecompositionPattern final if (gate.qubitId.size() == 2) { if (gate.type == qc::X) { // controlled X (CX) - if (gate.qubitId == llvm::SmallVector{0, 1}) { + if (gate.qubitId == llvm::SmallVector{0, 1}) { return matrix4x4{ {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}}; } - if (gate.qubitId == llvm::SmallVector{1, 0}) { + if (gate.qubitId == llvm::SmallVector{1, 0}) { return matrix4x4{ {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}, {0, 1, 0, 0}}; } @@ -903,6 +939,17 @@ struct GateDecompositionPattern final fp calculatedFidelity; // actual fidelity of decomposition matrix4x4 unitaryMatrix; // original matrix for this decomposition + /** + * Create Weyl decomposition. + * + * @param unitaryMatrix Matrix of the two-qubit operation/series to be + * decomposed. + * @param fidelity Tolerance to assume a specialization which is used to + * reduce the number of parameters required by the canonical + * gate and thus potentially decreasing the number of basis + * gates. + * @param specialization Force the use this specialization. + */ static TwoQubitWeylDecomposition create(matrix4x4 unitaryMatrix, std::optional fidelity, std::optional specialization) { @@ -1550,6 +1597,10 @@ struct GateDecompositionPattern final static constexpr auto DEFAULT_FIDELITY = 1.0 - 1e-15; static constexpr auto DEFAULT_ATOL = 1e-12; + /** + * Decomposer that must be initialized with a two-qubit basis gate that will + * be used to generate a circuit equivalent to a canonical gate (RXX+RYY+RZZ). + */ struct TwoQubitBasisDecomposer { QubitGateSequence::Gate basisGate; fp basisFidelity; @@ -1720,6 +1771,22 @@ struct GateDecompositionPattern final }; } + /** + * Perform decomposition using the basis gate of this decomposer. + * + * @param targetDecomposition Prepared Weyl decomposition of unitary matrix + * to be decomposed. + * @param target1qEulerBases List of euler bases that should be tried out to + * find the best one for each euler decomposition. + * All bases will be mixed to get the best overall + * result. + * @param basisFidelity Fidelity for lowering the number of basis gates + * required + * @param approximate If true, use basisFidelity or, if std::nullopt, use + * basisFidelity of this decomposer. If false, fidelity + * of 1.0 will be assumed. + * @param numBasisGateUses Force use of given number of basis gates. + */ [[nodiscard]] std::optional twoQubitDecompose(const TwoQubitWeylDecomposition& targetDecomposition, const llvm::SmallVector& target1qEulerBases, @@ -1788,7 +1855,7 @@ struct GateDecompositionPattern final gates.globalPhase += qc::PI; } - auto addEulerDecomposition = [&](std::size_t index, std::size_t qubitId) { + auto addEulerDecomposition = [&](std::size_t index, QubitId qubitId) { if (auto&& eulerDecomp = eulerDecompositions[index]) { for (auto&& gate : eulerDecomp->gates) { gates.gates.push_back({.type = gate.type, @@ -1974,8 +2041,7 @@ struct GateDecompositionPattern final static OneQubitGateSequence unitaryToGateSequenceInner( matrix2x2 unitaryMat, - const llvm::SmallVector& targetBasisList, - std::size_t /*qubit*/, + const llvm::SmallVector& targetBasisList, QubitId /*qubit*/, const std::vector>& /*error_map*/, // TODO: remove error_map+qubit for platform // independence diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp deleted file mode 100644 index a4da79e47..000000000 --- a/mlir/lib/Dialect/MQTOpt/Transforms/a.cpp +++ /dev/null @@ -1,904 +0,0 @@ -#include -#include -#include -#include -#include - -using fp = long double; -using qfp = std::complex; -using diagonal4x4 = std::array; -using vector2d = std::vector; -using matrix2x2 = std::array; -using matrix4x4 = std::array; - -using logical = bool; -using integer = int; -using doublecomplex = qfp; - -matrix4x4 zgemm2(matrix4x4 a, matrix4x4 b) { - matrix4x4 c__; - // a_dim1 = *lda; - // a_offset = 1 + a_dim1; - // a -= a_offset; - // b_dim1 = *ldb; - // b_offset = 1 + b_dim1; - // b -= b_offset; - // c_dim1 = *ldc; - // c_offset = 1 + c_dim1; - // c__ -= c_offset; - - int i__1 = 4; - int i__3{}; - qfp z__1; - qfp z__2; - qfp temp; - qfp alpha {1.0, 0.0}; - for (auto j = 0; j < i__1; ++j) { - for (auto i__ = 0; i__ < 4; ++i__) { - auto i__3 = i__ + j * 4; - c__[i__3].real(0.), c__[i__3].imag(0.); - } - int i__2 = 4; - for (auto l = 0; l < i__2; ++l) { - i__3 = l + j * 4; - if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { - i__3 = l + j * 4; - z__1.real(alpha.real() * b[i__3].real() - - alpha.imag() * b[i__3].imag()), - z__1.imag(alpha.real() * b[i__3].imag() + - alpha.imag() * b[i__3].real()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - i__3 = 4; - for (auto i__ = 0; i__ < i__3; ++i__) { - auto i__4 = i__ + j * 4; - auto i__5 = i__ + j * 4; - auto i__6 = i__ + l * 4; - z__2.real(temp.real() * a[i__6].real() - - temp.imag() * a[i__6].imag()), - z__2.imag(temp.real() * a[i__6].imag() + - temp.imag() * a[i__6].real()); - z__1.real(c__[i__5].real() + z__2.real()), - z__1.imag(c__[i__5].imag() + z__2.imag()); - c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); - /* L70: */ - } - } - /* L80: */ - } - /* L90: */ - } - return c__; -} - -int zgemm_(char* transa, char* transb, integer* m, integer* n, integer* k, - doublecomplex* alpha, matrix4x4 a, integer* lda, - matrix4x4 b, integer* ldb, doublecomplex* beta, - matrix4x4& c__, integer* ldc); -matrix4x4 zgemm(matrix4x4 a, matrix4x4 b) { - qfp alpha{1.0, 0.0}; - qfp beta{1.0, 0.0}; - int dimension = 4; - matrix4x4 result{}; - // zgemm_("n", "n", &dimension, &dimension, &dimension, &alpha, a.data(), - // &dimension, b.data(), &dimension, &beta, result.data(), &dimension); - zgemm_("N", "N", &dimension, &dimension, &dimension, &alpha, a, - &dimension, b, &dimension, &beta, result, &dimension); - return result; -} - -void d_cnjg(doublecomplex* r, doublecomplex* z) { *r = std::conj(*z); } - -/* Subroutine */ int zgemm_(char* transa, char* transb, integer* m, integer* n, - integer* k, doublecomplex* alpha, matrix4x4 a, - integer* lda, matrix4x4 b, integer* ldb, - doublecomplex* beta, matrix4x4& c__, - integer* ldc) { - /* System generated locals */ - integer a_dim1, a_offset, b_dim1, b_offset, c_dim1, c_offset, i__1, i__2, - i__3, i__4, i__5, i__6; - doublecomplex z__1, z__2, z__3, z__4; - - /* Local variables */ - static integer i__, j, l, info; - static logical nota, notb; - static doublecomplex temp; - static logical conja, conjb; - static integer nrowa, nrowb; - - /* - Purpose - ======= - - ZGEMM performs one of the matrix-matrix operations - - C := alpha*op( A )*op( B ) + beta*C, - - where op( X ) is one of - - op( X ) = X or op( X ) = X' or op( X ) = conjg( X' ), - - alpha and beta are scalars, and A, B and C are matrices, with op( A ) - an m by k matrix, op( B ) a k by n matrix and C an m by n matrix. - - Arguments - ========== - - TRANSA - CHARACTER*1. - On entry, TRANSA specifies the form of op( A ) to be used in - the matrix multiplication as follows: - - TRANSA = 'N' or 'n', op( A ) = A. - - TRANSA = 'T' or 't', op( A ) = A'. - - TRANSA = 'C' or 'c', op( A ) = conjg( A' ). - - Unchanged on exit. - - TRANSB - CHARACTER*1. - On entry, TRANSB specifies the form of op( B ) to be used in - the matrix multiplication as follows: - - TRANSB = 'N' or 'n', op( B ) = B. - - TRANSB = 'T' or 't', op( B ) = B'. - - TRANSB = 'C' or 'c', op( B ) = conjg( B' ). - - Unchanged on exit. - - M - INTEGER. - On entry, M specifies the number of rows of the matrix - op( A ) and of the matrix C. M must be at least zero. - Unchanged on exit. - - N - INTEGER. - On entry, N specifies the number of columns of the matrix - op( B ) and the number of columns of the matrix C. N must be - at least zero. - Unchanged on exit. - - K - INTEGER. - On entry, K specifies the number of columns of the matrix - op( A ) and the number of rows of the matrix op( B ). K must - be at least zero. - Unchanged on exit. - - ALPHA - COMPLEX*16 . - On entry, ALPHA specifies the scalar alpha. - Unchanged on exit. - - A - COMPLEX*16 array of DIMENSION ( LDA, ka ), where ka is - k when TRANSA = 'N' or 'n', and is m otherwise. - Before entry with TRANSA = 'N' or 'n', the leading m by k - part of the array A must contain the matrix A, otherwise - the leading k by m part of the array A must contain the - matrix A. - Unchanged on exit. - - LDA - INTEGER. - On entry, LDA specifies the first dimension of A as declared - in the calling (sub) program. When TRANSA = 'N' or 'n' then - LDA must be at least max( 1, m ), otherwise LDA must be at - least max( 1, k ). - Unchanged on exit. - - B - COMPLEX*16 array of DIMENSION ( LDB, kb ), where kb is - n when TRANSB = 'N' or 'n', and is k otherwise. - Before entry with TRANSB = 'N' or 'n', the leading k by n - part of the array B must contain the matrix B, otherwise - the leading n by k part of the array B must contain the - matrix B. - Unchanged on exit. - - LDB - INTEGER. - On entry, LDB specifies the first dimension of B as declared - in the calling (sub) program. When TRANSB = 'N' or 'n' then - LDB must be at least max( 1, k ), otherwise LDB must be at - least max( 1, n ). - Unchanged on exit. - - BETA - COMPLEX*16 . - On entry, BETA specifies the scalar beta. When BETA is - supplied as zero then C need not be set on input. - Unchanged on exit. - - C - COMPLEX*16 array of DIMENSION ( LDC, n ). - Before entry, the leading m by n part of the array C must - contain the matrix C, except when beta is zero, in which - case C need not be set on entry. - On exit, the array C is overwritten by the m by n matrix - ( alpha*op( A )*op( B ) + beta*C ). - - LDC - INTEGER. - On entry, LDC specifies the first dimension of C as declared - in the calling (sub) program. LDC must be at least - max( 1, m ). - Unchanged on exit. - - Further Details - =============== - - Level 3 Blas routine. - - -- Written on 8-February-1989. - Jack Dongarra, Argonne National Laboratory. - Iain Duff, AERE Harwell. - Jeremy Du Croz, Numerical Algorithms Group Ltd. - Sven Hammarling, Numerical Algorithms Group Ltd. - - ===================================================================== - - - Set NOTA and NOTB as true if A and B respectively are not - conjugated or transposed, set CONJA and CONJB as true if A and - B respectively are to be transposed but not conjugated and set - NROWA and NROWB as the number of rows and columns of A - and the number of rows of B respectively. - */ - - auto lsame_ = [](char* a, char* b) { - return std::string{a} == std::string{b}; - }; - - /* Parameter adjustments */ - a_dim1 = *lda; - a_offset = 1 + a_dim1; - // a -= a_offset; - b_dim1 = *ldb; - b_offset = 1 + b_dim1; - // b -= b_offset; - c_dim1 = *ldc; - c_offset = 1 + c_dim1; - // c__ -= c_offset; - - /* Function Body */ - nota = lsame_(transa, "N"); - notb = lsame_(transb, "N"); - conja = lsame_(transa, "C"); - conjb = lsame_(transb, "C"); - if (nota) { - nrowa = *m; - } else { - nrowa = *k; - } - if (notb) { - nrowb = *k; - } else { - nrowb = *n; - } - - /* Test the input parameters. */ - - info = 0; - if (!nota && !conja && !lsame_(transa, "T")) { - info = 1; - } else if (!notb && !conjb && !lsame_(transb, "T")) { - info = 2; - } else if (*m < 0) { - info = 3; - } else if (*n < 0) { - info = 4; - } else if (*k < 0) { - info = 5; - } else if (*lda < std::max(1, nrowa)) { - info = 8; - } else if (*ldb < std::max(1, nrowb)) { - info = 10; - } else if (*ldc < std::max(1, *m)) { - info = 13; - } - if (info != 0) { - // xerbla_("ZGEMM ", &info); - return 0; - } - - /* Quick return if possible. */ - - if (*m == 0 || *n == 0 || - (alpha->real() == 0. && alpha->imag() == 0. || *k == 0) && - (beta->real() == 1. && beta->imag() == 0.)) { - return 0; - } - - /* And when alpha.eq.zero. */ - - if (alpha->real() == 0. && alpha->imag() == 0.) { - if (beta->real() == 0. && beta->imag() == 0.) { - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - c__[i__3].real(0.), c__[i__3].imag(0.); - /* L10: */ - } - /* L20: */ - } - } else { - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - i__4 = i__ + j * c_dim1; - z__1.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__1.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - /* L30: */ - } - /* L40: */ - } - } - return 0; - } - - /* Start the operations. */ - - if (notb) { - if (nota) { - - /* Form C := alpha*A*B + beta*C. */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - if (beta->real() == 0. && beta->imag() == 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - c__[i__3 - c_offset].real(0.), c__[i__3 - c_offset].imag(0.); - /* L50: */ - } - } else if (beta->real() != 1. || beta->imag() != 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - i__4 = i__ + j * c_dim1; - z__1.real(beta->real() * c__[i__4 - c_offset].real() - - beta->imag() * c__[i__4 - c_offset].imag()), - z__1.imag(beta->real() * c__[i__4 - c_offset].imag() + - beta->imag() * c__[i__4 - c_offset].real()); - c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); - /* L60: */ - } - } - i__2 = *k; - for (l = 1; l <= i__2; ++l) { - i__3 = l + j * b_dim1; - if (b[i__3 - b_offset].real() != 0. || b[i__3 - b_offset].imag() != 0.) { - i__3 = l + j * b_dim1; - z__1.real(alpha->real() * b[i__3 - b_offset].real() - - alpha->imag() * b[i__3 - b_offset].imag()), - z__1.imag(alpha->real() * b[i__3 - b_offset].imag() + - alpha->imag() * b[i__3 - b_offset].real()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - i__3 = *m; - for (i__ = 1; i__ <= i__3; ++i__) { - i__4 = i__ + j * c_dim1; - i__5 = i__ + j * c_dim1; - i__6 = i__ + l * a_dim1; - z__2.real(temp.real() * a[i__6 - a_offset].real() - - temp.imag() * a[i__6 - a_offset].imag()), - z__2.imag(temp.real() * a[i__6 - a_offset].imag() + - temp.imag() * a[i__6 - a_offset].real()); - z__1.real(c__[i__5 - c_offset].real() + z__2.real()), - z__1.imag(c__[i__5 - c_offset].imag() + z__2.imag()); - c__[i__4 - c_offset].real(z__1.real()), c__[i__4 - c_offset].imag(z__1.imag()); - /* L70: */ - } - } - /* L80: */ - } - /* L90: */ - } - } else if (conja) { - - /* Form C := alpha*conjg( A' )*B + beta*C. */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - d_cnjg(&z__3, &a[l + i__ * a_dim1]); - i__4 = l + j * b_dim1; - z__2.real(z__3.real() * b[i__4].real() - - z__3.imag() * b[i__4].imag()), - z__2.imag(z__3.real() * b[i__4].imag() + - z__3.imag() * b[i__4].real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L100: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4 - c_offset].real() - - beta->imag() * c__[i__4 - c_offset].imag()), - z__3.imag(beta->real() * c__[i__4 - c_offset].imag() + - beta->imag() * c__[i__4 - c_offset].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3 - c_offset].real(z__1.real()), c__[i__3 - c_offset].imag(z__1.imag()); - } - /* L110: */ - } - /* L120: */ - } - } else { - - /* Form C := alpha*A'*B + beta*C */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - i__4 = l + i__ * a_dim1; - i__5 = l + j * b_dim1; - z__2.real(a[i__4].real() * b[i__5].real() - - a[i__4].imag() * b[i__5].imag()), - z__2.imag(a[i__4].real() * b[i__5].imag() + - a[i__4].imag() * b[i__5].real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L130: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } - /* L140: */ - } - /* L150: */ - } - } - } else if (nota) { - if (conjb) { - - /* Form C := alpha*A*conjg( B' ) + beta*C. */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - if (beta->real() == 0. && beta->imag() == 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - c__[i__3].real(0.), c__[i__3].imag(0.); - /* L160: */ - } - } else if (beta->real() != 1. || beta->imag() != 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - i__4 = i__ + j * c_dim1; - z__1.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__1.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - /* L170: */ - } - } - i__2 = *k; - for (l = 1; l <= i__2; ++l) { - i__3 = j + l * b_dim1; - if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { - d_cnjg(&z__2, &b[j + l * b_dim1]); - z__1.real(alpha->real() * z__2.real() - - alpha->imag() * z__2.imag()), - z__1.imag(alpha->real() * z__2.imag() + - alpha->imag() * z__2.real()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - i__3 = *m; - for (i__ = 1; i__ <= i__3; ++i__) { - i__4 = i__ + j * c_dim1; - i__5 = i__ + j * c_dim1; - i__6 = i__ + l * a_dim1; - z__2.real(temp.real() * a[i__6].real() - - temp.imag() * a[i__6].imag()), - z__2.imag(temp.real() * a[i__6].imag() + - temp.imag() * a[i__6].real()); - z__1.real(c__[i__5].real() + z__2.real()), - z__1.imag(c__[i__5].imag() + z__2.imag()); - c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); - /* L180: */ - } - } - /* L190: */ - } - /* L200: */ - } - } else { - - /* Form C := alpha*A*B' + beta*C */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - if (beta->real() == 0. && beta->imag() == 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - c__[i__3].real(0.), c__[i__3].imag(0.); - /* L210: */ - } - } else if (beta->real() != 1. || beta->imag() != 0.) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - i__3 = i__ + j * c_dim1; - i__4 = i__ + j * c_dim1; - z__1.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__1.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - /* L220: */ - } - } - i__2 = *k; - for (l = 1; l <= i__2; ++l) { - i__3 = j + l * b_dim1; - if (b[i__3].real() != 0. || b[i__3].imag() != 0.) { - i__3 = j + l * b_dim1; - z__1.real(alpha->real() * b[i__3].real() - - alpha->imag() * b[i__3].imag()), - z__1.imag(alpha->real() * b[i__3].imag() + - alpha->imag() * b[i__3].real()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - i__3 = *m; - for (i__ = 1; i__ <= i__3; ++i__) { - i__4 = i__ + j * c_dim1; - i__5 = i__ + j * c_dim1; - i__6 = i__ + l * a_dim1; - z__2.real(temp.real() * a[i__6].real() - - temp.imag() * a[i__6].imag()), - z__2.imag(temp.real() * a[i__6].imag() + - temp.imag() * a[i__6].real()); - z__1.real(c__[i__5].real() + z__2.real()), - z__1.imag(c__[i__5].imag() + z__2.imag()); - c__[i__4].real(z__1.real()), c__[i__4].imag(z__1.imag()); - /* L230: */ - } - } - /* L240: */ - } - /* L250: */ - } - } - } else if (conja) { - if (conjb) { - - /* Form C := alpha*conjg( A' )*conjg( B' ) + beta*C. */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - d_cnjg(&z__3, &a[l + i__ * a_dim1]); - d_cnjg(&z__4, &b[j + l * b_dim1]); - z__2.real(z__3.real() * z__4.real() - z__3.imag() * z__4.imag()), - z__2.imag(z__3.real() * z__4.imag() + - z__3.imag() * z__4.real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L260: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } - /* L270: */ - } - /* L280: */ - } - } else { - - /* Form C := alpha*conjg( A' )*B' + beta*C */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - d_cnjg(&z__3, &a[l + i__ * a_dim1]); - i__4 = j + l * b_dim1; - z__2.real(z__3.real() * b[i__4].real() - - z__3.imag() * b[i__4].imag()), - z__2.imag(z__3.real() * b[i__4].imag() + - z__3.imag() * b[i__4].real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L290: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } - /* L300: */ - } - /* L310: */ - } - } - } else { - if (conjb) { - - /* Form C := alpha*A'*conjg( B' ) + beta*C */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - i__4 = l + i__ * a_dim1; - d_cnjg(&z__3, &b[j + l * b_dim1]); - z__2.real(a[i__4].real() * z__3.real() - - a[i__4].imag() * z__3.imag()), - z__2.imag(a[i__4].real() * z__3.imag() + - a[i__4].imag() * z__3.real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L320: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } - /* L330: */ - } - /* L340: */ - } - } else { - - /* Form C := alpha*A'*B' + beta*C */ - - i__1 = *n; - for (j = 1; j <= i__1; ++j) { - i__2 = *m; - for (i__ = 1; i__ <= i__2; ++i__) { - temp.real(0.), temp.imag(0.); - i__3 = *k; - for (l = 1; l <= i__3; ++l) { - i__4 = l + i__ * a_dim1; - i__5 = j + l * b_dim1; - z__2.real(a[i__4].real() * b[i__5].real() - - a[i__4].imag() * b[i__5].imag()), - z__2.imag(a[i__4].real() * b[i__5].imag() + - a[i__4].imag() * b[i__5].real()); - z__1.real(temp.real() + z__2.real()), - z__1.imag(temp.imag() + z__2.imag()); - temp.real(z__1.real()), temp.imag(z__1.imag()); - /* L350: */ - } - if (beta->real() == 0. && beta->imag() == 0.) { - i__3 = i__ + j * c_dim1; - z__1.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__1.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } else { - i__3 = i__ + j * c_dim1; - z__2.real(alpha->real() * temp.real() - - alpha->imag() * temp.imag()), - z__2.imag(alpha->real() * temp.imag() + - alpha->imag() * temp.real()); - i__4 = i__ + j * c_dim1; - z__3.real(beta->real() * c__[i__4].real() - - beta->imag() * c__[i__4].imag()), - z__3.imag(beta->real() * c__[i__4].imag() + - beta->imag() * c__[i__4].real()); - z__1.real(z__2.real() + z__3.real()), - z__1.imag(z__2.imag() + z__3.imag()); - c__[i__3].real(z__1.real()), c__[i__3].imag(z__1.imag()); - } - /* L360: */ - } - /* L370: */ - } - } - } - - return 0; - - /* End of ZGEMM . */ - -} /* zgemm_ */ - -template void print(std::array, N> matrix) { - int i{}; - for (auto&& a : matrix) { - std::cerr << std::setprecision(50) << a.real() << '+' << a.imag() << "i, "; - if (++i % 4 == 0) { - std::cerr << '\n'; - } - } - std::cerr << '\n'; -} - -auto mul(qfp a, qfp b) { - return qfp((a.real() * b.real() - a.imag() * b.imag()), - (a.real() * b.imag() + a.imag() * b.real())); -} - -// Function to perform SYRK (Symmetric Rank-K update) on 4x4 matrices stored in -// std::array -matrix4x4 syrk(bool upper, fp alpha, const matrix4x4& A, fp beta) { - matrix4x4 C; - // Iterate over the matrix rows and columns - for (size_t i = 0; i < 4; ++i) { - for (size_t j = (upper ? i : 0); j < 4; ++j) { - // Compute the dot product A[i, :] * A[j, :] (real and imaginary - // separately) - qfp sum{0.0, 0.0}; // Initialize sum as a complex number (0.0 + 0.0i) - - for (size_t k = 0; k < 4; ++k) { - sum += A[i * 4 + k] * std::conj(A[j * 4 + k]); // A[i, :] * A[j, :] - } - - // Apply the SYRK update: C(i, j) = alpha * sum + beta * C(i, j) - C[i * 4 + j] = alpha * sum + beta * C[i * 4 + j]; - - // If updating the lower triangle, mirror the values from the upper - // triangle - if (!upper && i != j) { - C[j * 4 + i] = C[i * 4 + j]; // Maintain symmetry - } - } - } - return C; -} - -template -[[nodiscard]] inline auto multiply(const std::array& lhs, - const std::array& rhs) { - // return matrixMultiplyWithKahan(lhs, rhs); - std::array result{}; - const int n = std::sqrt(N); - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - for (int k = 0; k < n; k++) { - std::cout << std::setprecision(17) << lhs[i * n + k] << " * " - << rhs[k * n + j] << " = " - << mul(lhs[i * n + k], rhs[k * n + j]) << '\n'; - result[i * n + j] += mul(lhs[i * n + k], rhs[k * n + j]); - std::cout << std::setprecision(50) << "->" << result[i * n + j] << '\n'; - } - std::cout << "\n===\n"; - } - } - return result; -} - -static matrix4x4 transpose(const matrix4x4& matrix) { - matrix4x4 result; - for (size_t i = 0; i < 4; ++i) { - for (size_t j = 0; j < 4; ++j) { - result[j * 4 + i] = matrix[i * 4 + j]; - } - } - return result; -} - -int main() { - matrix4x4 a = {qfp(0.3535533905932738, +0.35355339059327373), - qfp(-0.35355339059327373, +0.3535533905932738), - qfp(-0.35355339059327373, +0.3535533905932738), - qfp(0.3535533905932738, +0.35355339059327373), - qfp(0.35355339059327373, -0.3535533905932738), - qfp(0.3535533905932738, +0.35355339059327373), - qfp(-0.3535533905932738, -0.35355339059327373), - qfp(-0.35355339059327373, +0.3535533905932738), - qfp(0.35355339059327373, -0.3535533905932738), - qfp(-0.3535533905932738, -0.35355339059327373), - qfp(0.3535533905932738, +0.35355339059327373), - qfp(-0.35355339059327373, +0.3535533905932738), - qfp(0.3535533905932738, +0.35355339059327373), - qfp(0.35355339059327373, -0.3535533905932738), - qfp(0.35355339059327373, -0.3535533905932738), - qfp(0.3535533905932738, +0.35355339059327373)}; - - // print(multiply(transpose(a), transpose(transpose(a)))); - // print(syrk(false, 1.0, transpose(a), 0.0)); - print(zgemm(transpose(a), a)); -} From 008408198421afcd9bbbcbd886d2445c9e83dc3e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 14:06:57 +0100 Subject: [PATCH 087/100] fix (?) tests --- .../MQTOpt/Transforms/gate-decomposition.mlir | 354 +++++++++--------- 1 file changed, 174 insertions(+), 180 deletions(-) diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index f66b0d333..afb45bab9 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -106,7 +106,7 @@ module { // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit // CHECK-NOT: mqtopt.i(%[[ANY:.*]]) - // CHECK: %[[Q0_0]], %[[Q1_0]] = mqtopt.x() %[[Q0_0]] ctrl %[[Q1_0]] + // CHECK: %[[Q1_1:.*]], %[[Q0_1:.*]] = mqtopt.x() %[[Q1_0]] ctrl %[[Q0_0]] // CHECK: mqtopt.deallocQubit %[[Q0_1]] // CHECK: mqtopt.deallocQubit %[[Q1_1]] @@ -134,22 +134,19 @@ module { module { // CHECK-LABEL: func.func @testSeriesOneQubitOpInbetween func.func @testSeriesOneQubitOpInbetween() { - // CHECK: %[[C2:.*]] = arith.constant -1.5707 - // CHECK: %[[C1:.*]] = arith.constant 3.14159 - // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK-DAG: %[[C0:.*]] = arith.constant 3.14159 + // CHECK-DAG: %[[C1:.*]] = arith.constant 1.5707 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] - // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // CHECK: mqtopt.gphase(%[[C1]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C0]]) %[[Q0_0]] // ensure no other operations are inserted // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) - // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -172,22 +169,19 @@ module { module { // CHECK-LABEL: func.func @testSeriesStartingOneQubitOp func.func @testSeriesStartingOneQubitOp() { - // CHECK: %[[C2:.*]] = arith.constant -1.5707 - // CHECK: %[[C1:.*]] = arith.constant 3.14159 - // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK-DAG: %[[C0:.*]] = arith.constant 3.14159 + // CHECK-DAG: %[[C1:.*]] = arith.constant 1.5707 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] - // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // CHECK: mqtopt.gphase(%[[C1]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C0]]) %[[Q0_0]] // ensure no other operations are inserted // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) - // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -210,22 +204,19 @@ module { module { // CHECK-LABEL: func.func @testSeriesEndingOneQubitOp func.func @testSeriesEndingOneQubitOp() { - // CHECK: %[[C2:.*]] = arith.constant -1.5707 - // CHECK: %[[C1:.*]] = arith.constant 3.14159 - // CHECK: %[[C0:.*]] = arith.constant 1.57079 + // CHECK-DAG: %[[C0:.*]] = arith.constant 3.14159 + // CHECK-DAG: %[[C1:.*]] = arith.constant 1.5707 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C0:.*]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C0]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C1:.*]]) %[[Q0_1]] - // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C2:.*]]) %[[Q0_2]] + // CHECK: mqtopt.gphase(%[[C1]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C0]]) %[[Q0_0]] // ensure no other operations are inserted // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) - // CHECK: mqtopt.deallocQubit %[[Q0_3]] + // CHECK: mqtopt.deallocQubit %[[Q0_1]] // CHECK: mqtopt.deallocQubit %[[Q1_0]] %q0_0 = mqtopt.allocQubit @@ -283,52 +274,51 @@ module { } } +// ----- +// This test checks if a complex series containing two basis gates is decomposed correctly. + module { // CHECK-LABEL: func.func @testTwoBasisGateDecomposition func.func @testTwoBasisGateDecomposition() { - // CHECK: %[[C0:.*]] = arith.constant -2.356194490 - // CHECK: %[[C1:.*]] = arith.constant -1.070796326 - // CHECK: %[[C2:.*]] = arith.constant -0.370796326 - // CHECK: %[[C3:.*]] = arith.constant -2.526112944 - // CHECK: %[[C4:.*]] = arith.constant 1.0471975511 - // CHECK: %[[C5:.*]] = arith.constant 0.6154797086 - // CHECK: %[[C6:.*]] = arith.constant -3.141592653 - // CHECK: %[[C7:.*]] = arith.constant 2.7707963267 - // CHECK: %[[C8:.*]] = arith.constant -1.570796326 - // CHECK: %[[C9:.*]] = arith.constant 0.7853981633 - // CHECK: %[[C10:.*]] = arith.constant 2.5000 - // CHECK: %[[C11:.*]] = arith.constant 1.570796326 - // CHECK: %[[C12:.*]] = arith.constant -1.57079632 - // CHECK: %[[C13:.*]] = arith.constant 8.881784197 + // CHECK-DAG: %[[C0:.*]] = arith.constant -1.5707963267 + // CHECK-DAG: %[[C1:.*]] = arith.constant -3.141592653 + // CHECK-DAG: %[[C2:.*]] = arith.constant 1.2502369157 + // CHECK-DAG: %[[C3:.*]] = arith.constant 1.8299117708 + // CHECK-DAG: %[[C4:.*]] = arith.constant 2.8733113535 + // CHECK-DAG: %[[C5:.*]] = arith.constant 3.0097427712 + // CHECK-DAG: %[[C6:.*]] = arith.constant 0.2614370492 + // CHECK-DAG: %[[C7:.*]] = arith.constant -0.131849882 + // CHECK-DAG: %[[C8:.*]] = arith.constant 2.500000e+00 + // CHECK-DAG: %[[C9:.*]] = arith.constant -1.570796326 + // CHECK-DAG: %[[C10:.*]] = arith.constant 1.570796326 + // CHECK-DAG: %[[C11:.*]] = arith.constant 0.785398163 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C13]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C12]]) %[[Q0_0]] - // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C11]]) %[[Q0_1]] - // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C11]]) %[[Q0_2]] - // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_0]] - // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C9]]) %[[Q1_1]] - // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C8]]) %[[Q1_2]] - // CHECK: %[[Q1_4:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_3]] - // CHECK: %[[Q0_5:.*]] = mqtopt.rz(%[[C11]]) %[[Q0_4]] - // CHECK: %[[Q0_6:.*]] = mqtopt.ry(%[[C7]]) %[[Q0_5]] - // CHECK: %[[Q0_7:.*]] = mqtopt.rz(%[[C6]]) %[[Q0_6]] - // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C5]]) %[[Q1_4]] - // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C4]]) %[[Q1_5]] - // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C3]]) %[[Q1_6]] - // CHECK: %[[Q1_8:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_7]] - // CHECK: %[[Q0_9:.*]] = mqtopt.ry(%[[C2]]) %[[Q0_8]] - // CHECK: %[[Q0_10:.*]] = mqtopt.rz(%[[C1]]) %[[Q0_9]] - // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_8]] - // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C0]]) %[[Q1_9]] + // CHECK-NOT: mqtopt.gphase(%[[ANY:.*]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C11]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.rz(%[[C10]]) %[[Q0_1]] + // CHECK: %[[Q1_1:.*]] = mqtopt.rx(%[[C9]]) %[[Q1_0]] + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C8]]) %[[Q1_1]] + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q0_2]] ctrl %[[Q1_2]] + // CHECK: %[[Q0_4:.*]] = mqtopt.rz(%[[C7]]) %[[Q0_3]] + // CHECK: %[[Q0_5:.*]] = mqtopt.ry(%[[C6]]) %[[Q0_4]] + // CHECK: %[[Q0_6:.*]] = mqtopt.rz(%[[C5]]) %[[Q0_5]] + // CHECK: %[[Q1_4:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_3]] + // CHECK: %[[Q1_5:.*]] = mqtopt.ry(%[[C10]]) %[[Q1_4]] + // CHECK: %[[Q0_7:.*]], %[[Q1_6:.*]] = mqtopt.x() %[[Q0_6]] ctrl %[[Q1_5]] + // CHECK: %[[Q0_8:.*]] = mqtopt.rz(%[[C4]]) %[[Q0_7]] + // CHECK: %[[Q0_9:.*]] = mqtopt.ry(%[[C3]]) %[[Q0_8]] + // CHECK: %[[Q0_10:.*]] = mqtopt.rz(%[[C2]]) %[[Q0_9]] + // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C1]]) %[[Q1_6]] + // CHECK: %[[Q1_8:.*]] = mqtopt.rx(%[[C0]]) %[[Q1_7]] // ensure no other operations are inserted // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) // CHECK: mqtopt.deallocQubit %[[Q0_10]] - // CHECK: mqtopt.deallocQubit %[[Q1_10]] + // CHECK: mqtopt.deallocQubit %[[Q1_8]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 @@ -346,127 +336,73 @@ module { %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_6 = mqtopt.rz(%cst2) %q0_5: !mqtopt.Qubit // make series longer to enforce decomposition - %q0_7 = mqtopt.i() %q0_6: !mqtopt.Qubit - %q0_8 = mqtopt.i() %q0_7: !mqtopt.Qubit - %q0_9 = mqtopt.i() %q0_8: !mqtopt.Qubit - %q1_5 = mqtopt.i() %q1_4: !mqtopt.Qubit - %q1_6 = mqtopt.i() %q1_5: !mqtopt.Qubit - %q1_7 = mqtopt.i() %q1_6: !mqtopt.Qubit - %q1_8 = mqtopt.i() %q1_7: !mqtopt.Qubit - %q1_9 = mqtopt.i() %q1_8: !mqtopt.Qubit + %q0_7, %q1_5 = mqtopt.i() %q0_6 ctrl %q1_4: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_8, %q1_6 = mqtopt.i() %q0_7 ctrl %q1_5: !mqtopt.Qubit ctrl !mqtopt.Qubit - mqtopt.deallocQubit %q0_9 - mqtopt.deallocQubit %q1_9 + mqtopt.deallocQubit %q0_8 + mqtopt.deallocQubit %q1_6 return } } // ----- -// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. - -module { - // CHECK-LABEL: func.func @testSingleQubitSeries - func.func @testSingleQubitSeries() { - // CHECK: %[[C0:.*]] = arith.constant -1.570796326 - // CHECK: %[[C1:.*]] = arith.constant 2.400000e+00 - // CHECK: %[[C2:.*]] = arith.constant 1.5707963267 - // CHECK: %[[C3:.*]] = arith.constant 2.500000e+00 - - // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - - // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C3]]) %[[Q0_0]] - // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C2]]) %[[Q1_0]] - // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C1]]) %[[Q1_1]] - // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_2]] - - // ensure no other operations are inserted - // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) - - // CHECK: mqtopt.deallocQubit %[[Q0_1]] - // CHECK: mqtopt.deallocQubit %[[Q1_3]] - - %cst0 = arith.constant 2.5 : f64 - %cst1 = arith.constant 1.2 : f64 - - %q0_0 = mqtopt.allocQubit - %q1_0 = mqtopt.allocQubit - - %q0_1, %q1_1 = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_2 = mqtopt.ry(%cst0) %q0_1: !mqtopt.Qubit - %q1_2 = mqtopt.rx(%cst1) %q1_1: !mqtopt.Qubit - %q1_3 = mqtopt.rx(%cst1) %q1_2: !mqtopt.Qubit - - mqtopt.deallocQubit %q0_2 - mqtopt.deallocQubit %q1_3 - - return - } -} - -// ----- -// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. +// This test checks if a complex series containing at least three basis gates is decomposed correctly. module { // CHECK-LABEL: func.func @testThreeBasisGateDecomposition func.func @testThreeBasisGateDecomposition() { - // CHECK: %[[C0:.*]] = arith.constant 2.5762316133983267 : f64 - // CHECK: %[[C1:.*]] = arith.constant 0.7853981633974495 : f64 - // CHECK: %[[C2:.*]] = arith.constant -1.5707963267948981 : f64 - // CHECK: %[[C3:.*]] = arith.constant -2.2830967446466035 : f64 - // CHECK: %[[C4:.*]] = arith.constant 2.7053466041231213 : f64 - // CHECK: %[[C5:.*]] = arith.constant -2.4341917193679428 : f64 - // CHECK: %[[C6:.*]] = arith.constant -0.61547970867038693 : f64 - // CHECK: %[[C7:.*]] = arith.constant 2.0943951023931953 : f64 - // CHECK: %[[C8:.*]] = arith.constant 0.61547970867038737 : f64 - // CHECK: %[[C9:.*]] = arith.constant 0.25938051218841107 : f64 - // CHECK: %[[C10:.*]] = arith.constant -2.5261129449194062 : f64 - // CHECK: %[[C11:.*]] = arith.constant 1.0471975511965979 : f64 - // CHECK: %[[C12:.*]] = arith.constant -2.5261129449194053 : f64 - // CHECK: %[[C13:.*]] = arith.constant -1.5707963267948966 : f64 - // CHECK: %[[C14:.*]] = arith.constant 0.6948042173197494 : f64 - // CHECK: %[[C15:.*]] = arith.constant -1.5707963267948968 : f64 - // CHECK: %[[C16:.*]] = arith.constant 0.45501497985473183 : f64 - // CHECK: %[[C17:.*]] = arith.constant 1.7692140485614463 : f64 - // CHECK: %[[C18:.*]] = arith.constant -1.508911464303158 : f64 - // CHECK: %[[C19:.*]] = arith.constant -0.82190477627456371 : f64 - // CHECK: %[[C20:.*]] = arith.constant 1.5707963267948963 : f64 - // CHECK: %[[C21:.*]] = arith.constant 1.5707963267948966 : f64 - // CHECK: %[[C22:.*]] = arith.constant -0.78539816339744739 : f64 + // CHECK-DAG: %[[C0:.*]] = arith.constant 2.5762316133 + // CHECK-DAG: %[[C1:.*]] = arith.constant 2.3561944901 + // CHECK-DAG: %[[C2:.*]] = arith.constant -1.570796326 + // CHECK-DAG: %[[C3:.*]] = arith.constant 0.8584959089 + // CHECK-DAG: %[[C4:.*]] = arith.constant 0.4362460494 + // CHECK-DAG: %[[C5:.*]] = arith.constant 2.4341917193 + // CHECK-DAG: %[[C6:.*]] = arith.constant -0.615479708 + // CHECK-DAG: %[[C7:.*]] = arith.constant 1.0471975511 + // CHECK-DAG: %[[C8:.*]] = arith.constant 2.5261129449 + // CHECK-DAG: %[[C9:.*]] = arith.constant -0.259380512 + // CHECK-DAG: %[[C10:.*]] = arith.constant 1.570796326 + // CHECK-DAG: %[[C11:.*]] = arith.constant -2.52611294 + // CHECK-DAG: %[[C12:.*]] = arith.constant 2.094395102 + // CHECK-DAG: %[[C13:.*]] = arith.constant -0.61547970 + // CHECK-DAG: %[[C14:.*]] = arith.constant 0.694804217 + // CHECK-DAG: %[[C15:.*]] = arith.constant 0.220207934 + // CHECK-DAG: %[[C16:.*]] = arith.constant 1.125358392 + // CHECK-DAG: %[[C17:.*]] = arith.constant -0.03425899 + // CHECK-DAG: %[[C18:.*]] = arith.constant -1.57079632 + // CHECK-DAG: %[[C19:.*]] = arith.constant -2.39270110 + // CHECK-DAG: %[[C20:.*]] = arith.constant -0.78539816 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C22]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C21]]) %[[Q0_0]] : !mqtopt.Qubit - // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C20]]) %[[Q0_1]] : !mqtopt.Qubit - // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C19]]) %[[Q0_2]] : !mqtopt.Qubit - // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C18]]) %[[Q1_0]] : !mqtopt.Qubit - // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C17]]) %[[Q1_1]] : !mqtopt.Qubit - // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C16]]) %[[Q1_2]] : !mqtopt.Qubit - // CHECK: %[[Q1_4:.*]], %[[Q0_4:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_5:.*]] = mqtopt.rz(%[[C15]]) %[[Q0_4]] : !mqtopt.Qubit - // CHECK: %[[Q0_6:.*]] = mqtopt.ry(%[[C14]]) %[[Q0_5]] : !mqtopt.Qubit - // CHECK: %[[Q0_7:.*]] = mqtopt.rz(%[[C13]]) %[[Q0_6]] : !mqtopt.Qubit - // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_4]] : !mqtopt.Qubit - // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C11]]) %[[Q1_5]] : !mqtopt.Qubit - // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_6]] : !mqtopt.Qubit - // CHECK: %[[Q1_8:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_7]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_9:.*]] = mqtopt.rz(%[[C21]]) %[[Q0_8]] : !mqtopt.Qubit - // CHECK: %[[Q0_10:.*]] = mqtopt.ry(%[[C9]]) %[[Q0_9]] : !mqtopt.Qubit + // CHECK: mqtopt.gphase(%[[C20]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C19]]) %[[Q0_0]] : !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C18]]) %[[Q0_1]] : !mqtopt.Qubit + // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C17]]) %[[Q1_0]] : !mqtopt.Qubit + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C16]]) %[[Q1_1]] : !mqtopt.Qubit + // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C15]]) %[[Q1_2]] : !mqtopt.Qubit + // CHECK: %[[Q1_4:.*]], %[[Q0_3:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_4:.*]] = mqtopt.rx(%[[C14]]) %[[Q0_3]] : !mqtopt.Qubit + // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C13]]) %[[Q1_4]] : !mqtopt.Qubit + // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C12]]) %[[Q1_5]] : !mqtopt.Qubit + // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C11]]) %[[Q1_6]] : !mqtopt.Qubit + // CHECK: %[[Q1_8:.*]], %[[Q0_5:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_6:.*]] = mqtopt.rz(%[[C10]]) %[[Q0_5]] : !mqtopt.Qubit + // CHECK: %[[Q0_7:.*]] = mqtopt.ry(%[[C9]]) %[[Q0_6]] : !mqtopt.Qubit // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C8]]) %[[Q1_8]] : !mqtopt.Qubit // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C7]]) %[[Q1_9]] : !mqtopt.Qubit // CHECK: %[[Q1_11:.*]] = mqtopt.rz(%[[C6]]) %[[Q1_10]] : !mqtopt.Qubit - // CHECK: %[[Q1_12:.*]], %[[Q0_11:.*]] = mqtopt.x() %[[Q1_11]] ctrl %[[Q0_10]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_12:.*]] = mqtopt.rz(%[[C5]]) %[[Q0_11]] : !mqtopt.Qubit - // CHECK: %[[Q0_13:.*]] = mqtopt.ry(%[[C4]]) %[[Q0_12]] : !mqtopt.Qubit - // CHECK: %[[Q0_14:.*]] = mqtopt.rz(%[[C3]]) %[[Q0_13]] : !mqtopt.Qubit + // CHECK: %[[Q1_12:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_11]] ctrl %[[Q0_7]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_9:.*]] = mqtopt.rz(%[[C5]]) %[[Q0_8]] : !mqtopt.Qubit + // CHECK: %[[Q0_10:.*]] = mqtopt.ry(%[[C4]]) %[[Q0_9]] : !mqtopt.Qubit + // CHECK: %[[Q0_11:.*]] = mqtopt.rz(%[[C3]]) %[[Q0_10]] : !mqtopt.Qubit // CHECK: %[[Q1_13:.*]] = mqtopt.rz(%[[C2]]) %[[Q1_12]] : !mqtopt.Qubit // CHECK: %[[Q1_14:.*]] = mqtopt.ry(%[[C1]]) %[[Q1_13]] : !mqtopt.Qubit // CHECK: %[[Q1_15:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_14]] : !mqtopt.Qubit - // CHECK: mqtopt.deallocQubit %[[Q0_14]] + // CHECK: mqtopt.deallocQubit %[[Q0_11]] // CHECK: mqtopt.deallocQubit %[[Q1_15]] %cst0 = arith.constant 2.5 : f64 @@ -504,40 +440,98 @@ module { module { // CHECK-LABEL: func.func @testRepeatedDecomposition func.func @testRepeatedDecomposition() { + return + } +} + +// ----- +// This test checks if two single-qubit series (connected by an identity) remain separate without the insertion of a basis gate. + +module { + // CHECK-LABEL: func.func @testSingleQubitSeries + func.func @testSingleQubitSeries() { + // CHECK-DAG: %[[C0:.*]] = arith.constant 2.400000 + // CHECK-DAG: %[[C1:.*]] = arith.constant 2.500000 + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: %[[Q2_0:.*]] = mqtopt.allocQubit - // TODO + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C1]]) %[[Q0_0]] + // CHECK: %[[Q1_1:.*]] = mqtopt.rx(%[[C0]]) %[[Q1_0]] - // CHECK: mqtopt.deallocQubit %[[Q0_5]] - // CHECK: mqtopt.deallocQubit %[[Q1_4]] - // CHECK: mqtopt.deallocQubit %[[Q2_1]] + // ensure no other operations are inserted + // CHECK-NOT: mqtopt.[[ANY:.*]](%[[ANY:.*]]) + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q1_1]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 - %cst2 = arith.constant 0.5 : f64 %q0_0 = mqtopt.allocQubit %q1_0 = mqtopt.allocQubit - %q2_0 = mqtopt.allocQubit - %q0_x, %q1_x = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_1 = mqtopt.h() %q0_x: !mqtopt.Qubit - %q1_1, %q0_2 = mqtopt.x() %q1_x ctrl %q0_1: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_3, %q1_2 = mqtopt.rzz(%cst0) %q0_2, %q1_1: !mqtopt.Qubit, !mqtopt.Qubit - %q1_3 = mqtopt.ry(%cst1) %q1_2: !mqtopt.Qubit - %q0_4 = mqtopt.rx(%cst1) %q0_3: !mqtopt.Qubit - %q0_5, %q1_4 = mqtopt.x() %q0_4 ctrl %q1_3: !mqtopt.Qubit ctrl !mqtopt.Qubit - %q0_6 = mqtopt.rz(%cst2) %q0_5: !mqtopt.Qubit - %q0_7, %q1_5 = mqtopt.rxx(%cst0) %q0_6, %q1_4: !mqtopt.Qubit, !mqtopt.Qubit - %q0_8, %q1_6 = mqtopt.ryy(%cst2) %q0_7, %q1_5: !mqtopt.Qubit, !mqtopt.Qubit - // make series longer to enforce decomposition - %q0_9, %q1_7 = mqtopt.i() %q0_8 ctrl %q1_6: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_1, %q1_1 = mqtopt.i() %q0_0 ctrl %q1_0: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_2 = mqtopt.ry(%cst0) %q0_1: !mqtopt.Qubit + %q1_2 = mqtopt.rx(%cst1) %q1_1: !mqtopt.Qubit + %q1_3 = mqtopt.rx(%cst1) %q1_2: !mqtopt.Qubit - mqtopt.deallocQubit %q0_9 - mqtopt.deallocQubit %q1_7 - mqtopt.deallocQubit %q2_0 + mqtopt.deallocQubit %q0_2 + mqtopt.deallocQubit %q1_3 + + return + } +} + +// ----- +// This test checks if a two-qubit series starting with two separate single-qubit chains is fully decomposed. + +module { + // CHECK-LABEL: func.func @testSeriesSingleQubitBacktracking + func.func @testSeriesSingleQubitBacktracking() { + // CHECK-DAG: %[[C0:.*]] = arith.constant -2.4269908 + // CHECK-DAG: %[[C1:.*]] = arith.constant 3.14159265 + // CHECK-DAG: %[[C2:.*]] = arith.constant 0.85619449 + // CHECK-DAG: %[[C3:.*]] = arith.constant -2.3561944 + // CHECK-DAG: %[[C4:.*]] = arith.constant -1.5707963 + // CHECK-DAG: %[[C5:.*]] = arith.constant -1.5707963 + // CHECK-DAG: %[[C6:.*]] = arith.constant 2.35619449 + // CHECK-DAG: %[[C7:.*]] = arith.constant -3.1415926 + + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit + + // CHECK: mqtopt.gphase(%[[C7]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C6]]) %[[Q0_0]] : !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C5]]) %[[Q0_1]] : !mqtopt.Qubit + // CHECK: %[[Q1_1:.*]] = mqtopt.ry(%[[C4]]) %[[Q1_0]] : !mqtopt.Qubit + // CHECK: %[[Q1_2:.*]] = mqtopt.rz(%[[C3]]) %[[Q1_1]] : !mqtopt.Qubit + // CHECK: %[[Q0_3:.*]], %[[Q1_3:.*]] = mqtopt.x() %[[Q0_2]] ctrl %[[Q1_2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_4:.*]] = mqtopt.rx(%[[C2]]) %[[Q0_3]] : !mqtopt.Qubit + // CHECK: %[[Q0_5:.*]] = mqtopt.ry(%[[C1]]) %[[Q0_4]] : !mqtopt.Qubit + // CHECK: %[[Q1_4:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_3]] : !mqtopt.Qubit + + // CHECK: mqtopt.deallocQubit %[[Q0_5]] + // CHECK: mqtopt.deallocQubit %[[Q1_4]] + + %q0_0 = mqtopt.allocQubit + %q1_0 = mqtopt.allocQubit + + %cst0 = arith.constant 1.5 : f64 + %cst1 = arith.constant 0.2 : f64 + %cst2 = arith.constant 0.9 : f64 + + %q0_1 = mqtopt.h() %q0_0: !mqtopt.Qubit + %q0_2 = mqtopt.rx(%cst0) %q0_1: !mqtopt.Qubit + %q0_3 = mqtopt.x() %q0_2: !mqtopt.Qubit + %q1_1 = mqtopt.h() %q1_0: !mqtopt.Qubit + %q1_2 = mqtopt.rz(%cst0) %q1_1: !mqtopt.Qubit + %q0_4, %q1_3 = mqtopt.x() %q0_3 ctrl %q1_2: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_4, %q0_5 = mqtopt.i() %q1_3 ctrl %q0_4: !mqtopt.Qubit ctrl !mqtopt.Qubit + %q1_5, %q0_6 = mqtopt.i() %q1_4 ctrl %q0_5: !mqtopt.Qubit ctrl !mqtopt.Qubit + + mqtopt.deallocQubit %q0_6 + mqtopt.deallocQubit %q1_5 return } From 50310f65eeb958d95a3390b21ef75eb3c234d101 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 14:24:24 +0100 Subject: [PATCH 088/100] fix final test --- .../Transforms/GateDecompositionPattern.cpp | 7 +- .../MQTOpt/Transforms/gate-decomposition.mlir | 90 +++++++++---------- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 8e01af1dd..f1f0bcfad 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -86,7 +86,7 @@ struct GateDecompositionPattern final } if (hasGlobalPhase()) { // need to add a global phase gate if a global phase needs to be applied - c += getComplexity(qc::GPhase, 1); + c += getComplexity(qc::GPhase, 0); } return c; } @@ -186,12 +186,15 @@ struct GateDecompositionPattern final protected: static constexpr fp SANITY_CHECK_PRECISION = 1e-12; - [[nodiscard]] static std::size_t getComplexity(qc::OpType /*type*/, + [[nodiscard]] static std::size_t getComplexity(qc::OpType type, std::size_t numOfQubits) { if (numOfQubits > 1) { constexpr std::size_t multiQubitFactor = 10; return (numOfQubits - 1) * multiQubitFactor; } + if (type == qc::GPhase) { + return 2; + } return 1; } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index afb45bab9..c0b1dd17c 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -352,58 +352,56 @@ module { module { // CHECK-LABEL: func.func @testThreeBasisGateDecomposition func.func @testThreeBasisGateDecomposition() { - // CHECK-DAG: %[[C0:.*]] = arith.constant 2.5762316133 - // CHECK-DAG: %[[C1:.*]] = arith.constant 2.3561944901 - // CHECK-DAG: %[[C2:.*]] = arith.constant -1.570796326 - // CHECK-DAG: %[[C3:.*]] = arith.constant 0.8584959089 - // CHECK-DAG: %[[C4:.*]] = arith.constant 0.4362460494 - // CHECK-DAG: %[[C5:.*]] = arith.constant 2.4341917193 - // CHECK-DAG: %[[C6:.*]] = arith.constant -0.615479708 - // CHECK-DAG: %[[C7:.*]] = arith.constant 1.0471975511 - // CHECK-DAG: %[[C8:.*]] = arith.constant 2.5261129449 - // CHECK-DAG: %[[C9:.*]] = arith.constant -0.259380512 - // CHECK-DAG: %[[C10:.*]] = arith.constant 1.570796326 - // CHECK-DAG: %[[C11:.*]] = arith.constant -2.52611294 - // CHECK-DAG: %[[C12:.*]] = arith.constant 2.094395102 - // CHECK-DAG: %[[C13:.*]] = arith.constant -0.61547970 - // CHECK-DAG: %[[C14:.*]] = arith.constant 0.694804217 - // CHECK-DAG: %[[C15:.*]] = arith.constant 0.220207934 - // CHECK-DAG: %[[C16:.*]] = arith.constant 1.125358392 - // CHECK-DAG: %[[C17:.*]] = arith.constant -0.03425899 - // CHECK-DAG: %[[C18:.*]] = arith.constant -1.57079632 - // CHECK-DAG: %[[C19:.*]] = arith.constant -2.39270110 - // CHECK-DAG: %[[C20:.*]] = arith.constant -0.78539816 + // CHECK-DAG: %[[C0:.*]] = arith.constant 1.00543528660 + // CHECK-DAG: %[[C1:.*]] = arith.constant -2.3561944901 + // CHECK-DAG: %[[C2:.*]] = arith.constant 0.85849590894 + // CHECK-DAG: %[[C3:.*]] = arith.constant 0.43624604946 + // CHECK-DAG: %[[C4:.*]] = arith.constant 2.43419171936 + // CHECK-DAG: %[[C5:.*]] = arith.constant -0.6154797086 + // CHECK-DAG: %[[C6:.*]] = arith.constant 1.04719755119 + // CHECK-DAG: %[[C7:.*]] = arith.constant 2.52611294491 + // CHECK-DAG: %[[C8:.*]] = arith.constant -0.2593805121 + // CHECK-DAG: %[[C9:.*]] = arith.constant 1.57079632679 + // CHECK-DAG: %[[C10:.*]] = arith.constant -2.52611294491 + // CHECK-DAG: %[[C11:.*]] = arith.constant 2.094395102393 + // CHECK-DAG: %[[C12:.*]] = arith.constant -0.61547970867 + // CHECK-DAG: %[[C13:.*]] = arith.constant 0.694804217319 + // CHECK-DAG: %[[C14:.*]] = arith.constant 0.220207934068 + // CHECK-DAG: %[[C15:.*]] = arith.constant 1.125358392049 + // CHECK-DAG: %[[C16:.*]] = arith.constant -0.03425899788 + // CHECK-DAG: %[[C17:.*]] = arith.constant -1.57079632679 + // CHECK-DAG: %[[C18:.*]] = arith.constant -2.39270110306 + // CHECK-DAG: %[[C19:.*]] = arith.constant -0.78539816339 // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit // CHECK: %[[Q1_0:.*]] = mqtopt.allocQubit - // CHECK: mqtopt.gphase(%[[C20]]) - // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C19]]) %[[Q0_0]] : !mqtopt.Qubit - // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C18]]) %[[Q0_1]] : !mqtopt.Qubit - // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C17]]) %[[Q1_0]] : !mqtopt.Qubit - // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C16]]) %[[Q1_1]] : !mqtopt.Qubit - // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C15]]) %[[Q1_2]] : !mqtopt.Qubit - // CHECK: %[[Q1_4:.*]], %[[Q0_3:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_2]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_4:.*]] = mqtopt.rx(%[[C14]]) %[[Q0_3]] : !mqtopt.Qubit - // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C13]]) %[[Q1_4]] : !mqtopt.Qubit - // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C12]]) %[[Q1_5]] : !mqtopt.Qubit - // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C11]]) %[[Q1_6]] : !mqtopt.Qubit - // CHECK: %[[Q1_8:.*]], %[[Q0_5:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_6:.*]] = mqtopt.rz(%[[C10]]) %[[Q0_5]] : !mqtopt.Qubit - // CHECK: %[[Q0_7:.*]] = mqtopt.ry(%[[C9]]) %[[Q0_6]] : !mqtopt.Qubit - // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C8]]) %[[Q1_8]] : !mqtopt.Qubit - // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C7]]) %[[Q1_9]] : !mqtopt.Qubit - // CHECK: %[[Q1_11:.*]] = mqtopt.rz(%[[C6]]) %[[Q1_10]] : !mqtopt.Qubit - // CHECK: %[[Q1_12:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_11]] ctrl %[[Q0_7]] : !mqtopt.Qubit ctrl !mqtopt.Qubit - // CHECK: %[[Q0_9:.*]] = mqtopt.rz(%[[C5]]) %[[Q0_8]] : !mqtopt.Qubit - // CHECK: %[[Q0_10:.*]] = mqtopt.ry(%[[C4]]) %[[Q0_9]] : !mqtopt.Qubit - // CHECK: %[[Q0_11:.*]] = mqtopt.rz(%[[C3]]) %[[Q0_10]] : !mqtopt.Qubit - // CHECK: %[[Q1_13:.*]] = mqtopt.rz(%[[C2]]) %[[Q1_12]] : !mqtopt.Qubit - // CHECK: %[[Q1_14:.*]] = mqtopt.ry(%[[C1]]) %[[Q1_13]] : !mqtopt.Qubit - // CHECK: %[[Q1_15:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_14]] : !mqtopt.Qubit + // CHECK: mqtopt.gphase(%[[C19]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.ry(%[[C18]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C17]]) %[[Q0_1]] + // CHECK: %[[Q1_1:.*]] = mqtopt.rz(%[[C16]]) %[[Q1_0]] + // CHECK: %[[Q1_2:.*]] = mqtopt.ry(%[[C15]]) %[[Q1_1]] + // CHECK: %[[Q1_3:.*]] = mqtopt.rz(%[[C14]]) %[[Q1_2]] + // CHECK: %[[Q1_4:.*]], %[[Q0_3:.*]] = mqtopt.x() %[[Q1_3]] ctrl %[[Q0_2]] + // CHECK: %[[Q0_4:.*]] = mqtopt.rx(%[[C13]]) %[[Q0_3]] + // CHECK: %[[Q1_5:.*]] = mqtopt.rz(%[[C12]]) %[[Q1_4]] + // CHECK: %[[Q1_6:.*]] = mqtopt.ry(%[[C11]]) %[[Q1_5]] + // CHECK: %[[Q1_7:.*]] = mqtopt.rz(%[[C10]]) %[[Q1_6]] + // CHECK: %[[Q1_8:.*]], %[[Q0_5:.*]] = mqtopt.x() %[[Q1_7]] ctrl %[[Q0_4]] + // CHECK: %[[Q0_6:.*]] = mqtopt.rz(%[[C9]]) %[[Q0_5]] + // CHECK: %[[Q0_7:.*]] = mqtopt.ry(%[[C8]]) %[[Q0_6]] + // CHECK: %[[Q1_9:.*]] = mqtopt.rz(%[[C7]]) %[[Q1_8]] + // CHECK: %[[Q1_10:.*]] = mqtopt.ry(%[[C6]]) %[[Q1_9]] + // CHECK: %[[Q1_11:.*]] = mqtopt.rz(%[[C5]]) %[[Q1_10]] + // CHECK: %[[Q1_12:.*]], %[[Q0_8:.*]] = mqtopt.x() %[[Q1_11]] ctrl %[[Q0_7]] + // CHECK: %[[Q0_9:.*]] = mqtopt.rz(%[[C4]]) %[[Q0_8]] + // CHECK: %[[Q0_10:.*]] = mqtopt.ry(%[[C3]]) %[[Q0_9]] + // CHECK: %[[Q0_11:.*]] = mqtopt.rz(%[[C2]]) %[[Q0_10]] + // CHECK: %[[Q1_13:.*]] = mqtopt.rx(%[[C1]]) %[[Q1_12]] + // CHECK: %[[Q1_14:.*]] = mqtopt.rz(%[[C0]]) %[[Q1_13]] // CHECK: mqtopt.deallocQubit %[[Q0_11]] - // CHECK: mqtopt.deallocQubit %[[Q1_15]] + // CHECK: mqtopt.deallocQubit %[[Q1_14]] %cst0 = arith.constant 2.5 : f64 %cst1 = arith.constant 1.2 : f64 From 500ccc6b7e4e3e2ed2aad45165d50cfd9ba764a9 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 14:51:34 +0100 Subject: [PATCH 089/100] another minor cleanup --- .../Transforms/GateDecompositionPattern.cpp | 74 +++++-------------- 1 file changed, 17 insertions(+), 57 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index f1f0bcfad..2382d6953 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -643,7 +643,6 @@ struct GateDecompositionPattern final // or "7.1 Kronecker decomposition" in on_gates.pdf // or quantumflow.kronecker_decomposition - helpers::print(specialUnitary, "SPECIAL_UNITARY"); // first quadrant matrix2x2 r{{specialUnitary(0, 0), specialUnitary(0, 1)}, {specialUnitary(1, 0), specialUnitary(1, 1)}}; @@ -659,14 +658,11 @@ struct GateDecompositionPattern final "decompose_two_qubit_product_gate: unable to decompose: det_r < 0.1"}; } r /= std::sqrt(detR); - helpers::print(r, "R"); // transpose with complex conjugate of each element matrix2x2 rTConj = r.transpose().conjugate(); auto temp = helpers::kroneckerProduct(IDENTITY_GATE, rTConj); - helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 1)"); temp = specialUnitary * temp; - helpers::print(temp, "TEMP (decompose_two_qubit_product_gate, 2)"); // [[a, b, c, d], // [e, f, g, h], => [[a, c], @@ -692,10 +688,6 @@ struct GateDecompositionPattern final {C_ZERO, C_ZERO, IM, C_M_ONE}, {C_ONE, M_IM, C_ZERO, C_ZERO}, }; - const matrix4x4 b = FRAC1_SQRT2 * matrix4x4{{C_ONE, C_ZERO, C_ZERO, IM}, - {C_ZERO, IM, C_ONE, C_ZERO}, - {C_ZERO, IM, -C_ONE, C_ZERO}, - {C_ONE, C_ZERO, C_ZERO, -IM}}; const matrix4x4 bNonNormalizedDagger{ {qfp(0.5, 0.), C_ZERO, C_ZERO, qfp(0.5, 0.)}, @@ -703,20 +695,16 @@ struct GateDecompositionPattern final {C_ZERO, qfp(0., -0.5), qfp(0., -0.5), C_ZERO}, {C_ZERO, qfp(0.5, 0.), qfp(-0.5, 0.), C_ZERO}, }; - const matrix4x4 bDagger = b.conjugate().transpose(); - helpers::print(unitary, "UNITARY in MAGIC BASIS TRANSFORM"); if (direction == MagicBasisTransform::OutOf) { - // return bDagger * unitary * b; // TODO: same result? return bNonNormalizedDagger * unitary * bNonNormalized; } if (direction == MagicBasisTransform::Into) { - // return b * unitary * bDagger; return bNonNormalized * unitary * bNonNormalizedDagger; } throw std::logic_error{"Unknown MagicBasisTransform direction!"}; } - static fp traceToFid(const qfp& x) { + static fp traceToFidelity(const qfp& x) { auto xAbs = std::abs(x); return (4.0 + xAbs * xAbs) / 20.0; } @@ -745,8 +733,6 @@ struct GateDecompositionPattern final static matrix2x2 rzMatrix(fp theta) { return matrix2x2{{qfp{std::cos(theta / 2.), -std::sin(theta / 2.)}, 0}, {0, qfp{std::cos(theta / 2.), std::sin(theta / 2.)}}}; - // auto ilam2 = qfp(0., 0.5 * theta); - // return {std::exp(-ilam2), C_ZERO, C_ZERO, std::exp(ilam2)}; } static matrix4x4 rxxMatrix(const fp theta) { @@ -766,7 +752,7 @@ struct GateDecompositionPattern final return matrix4x4{{{cosTheta, 0, 0, {0., sinTheta}}, {0, cosTheta, {0., -sinTheta}, 0}, {0, {0., -sinTheta}, cosTheta, 0}, - {std::complex{0., sinTheta}, 0, 0, cosTheta}}}; + {{0., sinTheta}, 0, 0, cosTheta}}}; } static matrix4x4 rzzMatrix(const fp theta) { @@ -901,15 +887,15 @@ struct GateDecompositionPattern final } } if (gate.type == qc::RXX) { - // TODO: check qubit order + // TODO: check qubit order? return rxxMatrix(gate.parameter[0]); } if (gate.type == qc::RYY) { - // TODO: check qubit order + // TODO: check qubit order? return ryyMatrix(gate.parameter[0]); } if (gate.type == qc::RZZ) { - // TODO: check qubit order + // TODO: check qubit order? return rzzMatrix(gate.parameter[0]); } if (gate.type == qc::I) { @@ -921,6 +907,13 @@ struct GateDecompositionPattern final throw std::logic_error{"Invalid number of qubit IDs in compute_unitary"}; } + /** + * Weyl decomposition of a 2-qubit unitary matrix (4x4). + * The result consists of four 2x2 1-qubit matrices (k1l, k2l, k1r, k2r) and + * three parameters for a canonical gate (a, b, c). The matrices can then be + * decomposed using a single-qubit decomposition into e.g. rotation gates and + * the canonical gate is RXX(-2 * a), RYY(-2 * b), RZZ (-2 * c). + */ struct TwoQubitWeylDecomposition { // a, b, c are the parameters of the canonical gate (CAN) fp a; // rotation of RXX gate in CAN @@ -960,13 +953,10 @@ struct GateDecompositionPattern final auto detU = u.determinant(); auto detPow = std::pow(detU, static_cast(-0.25)); u *= detPow; - helpers::print(u, "U"); auto globalPhase = std::arg(detU) / 4.; auto uP = magicBasisTransform(u, MagicBasisTransform::OutOf); - helpers::print(uP, "U_P"); matrix4x4 m2 = uP.transpose() * uP; auto defaultEulerBasis = EulerBasis::ZYZ; - helpers::print(m2, "M2"); // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. @@ -1008,12 +998,9 @@ struct GateDecompositionPattern final matrix4x4 pInner = pInnerReal; diagonal4x4 dInner = (pInner.transpose() * m2 * pInner).diagonal(); - helpers::print(dInner, "D_INNER"); - helpers::print(pInner, "P_INNER"); matrix4x4 diagD = dInner.asDiagonal(); matrix4x4 compare = pInner * diagD * pInner.transpose(); - helpers::print(compare, "COMPARE"); found = compare.isApprox(m2, 1e-13); if (found) { // p are the eigenvectors which are decomposed into the @@ -1040,13 +1027,11 @@ struct GateDecompositionPattern final // Step 7 Eigen::Vector cs; rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; - helpers::print(dReal, "D_REAL"); dReal(3) = -dReal(0) - dReal(1) - dReal(2); for (int i = 0; i < static_cast(cs.size()); ++i) { assert(i < dReal.size()); cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); } - helpers::print(cs, "CS (1)"); decltype(cs) cstemp; llvm::transform(cs, cstemp.begin(), [](auto&& x) { @@ -1058,20 +1043,12 @@ struct GateDecompositionPattern final // order in eigen decomposition algorithm? llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); - // llvm::stable_sort(order, [&](fp a, fp b) { - // auto tmp1 = remEuclid(cs[a], qc::PI_2); - // tmp1 = std::min(tmp1, qc::PI_2 - tmp1); - // auto tmp2 = remEuclid(cs[b], qc::PI_2); - // tmp2 = std::min(tmp2, qc::PI_2 - tmp2); - // return tmp1 < tmp2; - // }); std::tie(order[0], order[1], order[2]) = std::tuple{order[1], order[2], order[0]}; std::tie(cs[0], cs[1], cs[2]) = std::tuple{cs[order[0]], cs[order[1]], cs[order[2]]}; std::tie(dReal(0), dReal(1), dReal(2)) = std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - helpers::print(dReal, "D_REAL (sorted)"); // swap columns of p according to order matrix4x4 pOrig = p; @@ -1079,7 +1056,6 @@ struct GateDecompositionPattern final p.col(i) = pOrig.col(order[i]); } if (p.determinant().real() < 0.0) { - std::cerr << "SECOND CORRECTION?\n"; auto lastColumnIndex = p.cols() - 1; p.col(lastColumnIndex) *= -1.0; } @@ -1088,20 +1064,13 @@ struct GateDecompositionPattern final temp *= IM; temp = temp.exp(); - // temp = temp.conjugate(); - // temp += matrix4x4::Constant(0.0); - helpers::print(temp, "TEMP"); - helpers::print(p, "P"); assert(std::abs(p.determinant() - 1.0) < SANITY_CHECK_PRECISION); - // https://threeplusone.com/pubs/on_gates.pdf - // uP = V, m2 = V^T*V, temp = D, p = Q1 + matrix4x4 k1 = uP * p * temp; - helpers::print(k1, "K1 (1)"); assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal assert(k1.determinant().real() > 0.0); k1 = magicBasisTransform(k1, MagicBasisTransform::Into); matrix4x4 k2 = p.transpose().conjugate(); - helpers::print(k2, "K2 (1)"); assert((k2.transpose() * k2).isIdentity()); // k2 must be orthogonal assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); @@ -1178,16 +1147,6 @@ struct GateDecompositionPattern final globalPhase -= qc::PI_2; } - helpers::print(K1l, "K1l (1)"); - helpers::print(K2l, "K2l (1)"); - helpers::print(K1r, "K1r (1)"); - helpers::print(K2r, "K2r (1)"); - - helpers::print(cs, "CS (2)"); - helpers::print(K1l, "K1l (2)"); - helpers::print(K2l, "K2l (2)"); - helpers::print(K1r, "K1r (2)"); - helpers::print(K2r, "K2r (2)"); auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); auto getCanonicalMatrix = [](fp a, fp b, fp c) -> matrix4x4 { auto xx = getTwoQubitMatrix({ @@ -1229,7 +1188,7 @@ struct GateDecompositionPattern final qfp(std::cos(da) * std::cos(db) * std::cos(dc), std::sin(da) * std::sin(db) * std::sin(dc)); if (fidelity) { - return traceToFid(tr) >= *fidelity; + return traceToFidelity(tr) >= *fidelity; } return false; }; @@ -1566,7 +1525,7 @@ struct GateDecompositionPattern final std::sin(da) * std::sin(db) * std::sin(dc)); }; auto tr = getTr(); - specialized.calculatedFidelity = traceToFid(tr); + specialized.calculatedFidelity = traceToFidelity(tr); if (specialized.requestedFidelity) { if (specialized.calculatedFidelity + 1.0e-13 < *specialized.requestedFidelity) { @@ -1809,7 +1768,8 @@ struct GateDecompositionPattern final for (int i = 0; i < static_cast(traces.size()); ++i) { // lower fidelity means it becomes easier to choose a lower number of // basis gates - auto value = traceToFid(traces[i]) * std::pow(actualBasisFidelity, i); + auto value = + traceToFidelity(traces[i]) * std::pow(actualBasisFidelity, i); if (value > minValue) { minIndex = i; minValue = value; From 9a4574e806237cf4f93dca570d000fb2d94fb879 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 14:52:49 +0100 Subject: [PATCH 090/100] clean sanity check prints up --- .../Transforms/GateDecompositionPattern.cpp | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 2382d6953..35f6be2f0 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -557,9 +557,6 @@ struct GateDecompositionPattern final } } std::cerr << '\n'; - helpers::print(series.getUnitaryMatrix(), "ORIGINAL UNITARY"); - helpers::print((unitaryMatrix * std::exp(IM * sequence.globalPhase)).eval(), - "RESULT UNITARY MATRIX"); assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)) .isApprox(series.getUnitaryMatrix(), SANITY_CHECK_PRECISION)); @@ -1075,12 +1072,6 @@ struct GateDecompositionPattern final assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); - helpers::print( - (k1 * - magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * - k2) - .eval(), - "SANITY CHECK (1)"); assert((k1 * magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * k2) @@ -1166,16 +1157,6 @@ struct GateDecompositionPattern final }); return zz * yy * xx; }; - helpers::print(getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0), - "SANITY CHECK (2.1)"); - helpers::print(helpers::kroneckerProduct(K1l, K1r), "SANITY CHECK (2.2)"); - helpers::print(helpers::kroneckerProduct(K2l, K2r), "SANITY CHECK (2.3)"); - helpers::print((helpers::kroneckerProduct(K1l, K1r) * - getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * - helpers::kroneckerProduct(K2l, K2r) * - std::exp(IM * globalPhase)) - .eval(), - "SANITY CHECK (2.x)"); assert((helpers::kroneckerProduct(K1l, K1r) * getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) @@ -1537,14 +1518,6 @@ struct GateDecompositionPattern final } specialized.globalPhase += std::arg(tr); - helpers::print( - (helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * - getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, - specialized.c * -2.0) * - helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * - std::exp(IM * specialized.globalPhase)) - .eval(), - "SANITY CHECK (3)"); assert((helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, specialized.c * -2.0) * @@ -2017,9 +1990,6 @@ struct GateDecompositionPattern final OneQubitGateSequence bestCircuit; for (auto targetBasis : targetBasisList) { auto circuit = generateCircuit(targetBasis, unitaryMat, simplify, atol); - helpers::print(circuit.getUnitaryMatrix(), "SANITY CHECK (4.1)"); - helpers::print(helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), - "SANITY CHECK (4.2)"); assert(circuit.getUnitaryMatrix().isApprox( helpers::kroneckerProduct(IDENTITY_GATE, unitaryMat), SANITY_CHECK_PRECISION)); From bf714ac2641da1506f73b8d8df3f33779f05de6d Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 16:46:27 +0100 Subject: [PATCH 091/100] properly add Eigen lib to cmake --- CMakeLists.txt | 9 +++++++++ mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cd8e9551..9ee9e3fbb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,15 @@ include(PreventInSourceBuilds) include(PackageAddTest) include(Cache) include(AddMQTCoreLibrary) +include(FetchContent) + +FetchContent_Declare( + Eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG 3.4.1 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(Eigen) option(BUILD_MQT_CORE_BINDINGS "Build the MQT Core Python bindings" OFF) if(BUILD_MQT_CORE_BINDINGS) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index e7f1f55da..7bff1278b 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt +++ b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt @@ -7,7 +7,7 @@ # Licensed under the MIT License get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) -set(LIBRARIES ${dialect_libs} MQT::CoreIR) +set(LIBRARIES ${dialect_libs} MQT::CoreIR Eigen3::Eigen) add_compile_options(-fexceptions) file(GLOB_RECURSE TRANSFORMS_SOURCES *.cpp) From 0a528b984b6ab8639be4bd98e3d28bf87960a3c2 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 16:46:38 +0100 Subject: [PATCH 092/100] remove unnecessary correction --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 35f6be2f0..dd7e46ca8 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -620,17 +620,6 @@ struct GateDecompositionPattern final auto [U, S] = helpers::selfAdjointEvd(std::forward(a)); - // TODO: not in original code - if (std::real(U.determinant()) < 0.0) { - std::cerr << "CORRECTION!\n"; - // if determinant of eigenvector matrix is -1.0, multiply first - // eigenvector by -1.0 - U.col(0) *= -1.0; - // U.col(U.cols() - 1) *= -1.0; - // U *= -1.0; - // U += std::remove_cvref_t::Constant(0.0); // ensure no -0.0 exists - } - return std::make_pair(U, S); } From 686ed5f9f2ac8a534652e50ec9c1787d796177e7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:47:06 +0000 Subject: [PATCH 093/100] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ee9e3fbb..ab74b0569 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,9 +28,8 @@ include(FetchContent) FetchContent_Declare( Eigen GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git - GIT_TAG 3.4.1 - GIT_SHALLOW TRUE -) + GIT_TAG 3.4.1 + GIT_SHALLOW TRUE) FetchContent_MakeAvailable(Eigen) option(BUILD_MQT_CORE_BINDINGS "Build the MQT Core Python bindings" OFF) From 09d105c18923c8188f51dc028d677cacd9829c33 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 17:24:43 +0100 Subject: [PATCH 094/100] fix eigen compilation issues --- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 6 +++--- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index dd7e46ca8..1deb1bd4e 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -16,8 +16,7 @@ #include #include #include -#include -#include +#include #include #include #include @@ -27,6 +26,7 @@ #include #include #include +#include #include namespace mqt::ir::opt { @@ -1024,7 +1024,7 @@ struct GateDecompositionPattern final auto tmp = remEuclid(x, qc::PI_2); return std::min(tmp, qc::PI_2 - tmp); }); - std::array order{ + std::array order{ 0, 1, 2}; // TODO: needs to be adjusted depending on eigenvector // order in eigen decomposition algorithm? llvm::stable_sort(order, diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index d8ab37346..1167af465 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -14,9 +14,9 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include -#include -#include -#include // TODO: unstable +#include +#include +#include // TODO: unstable #include // TODO: remove #include // TODO: remove #include @@ -207,10 +207,7 @@ getParameters(UnitaryInterface op) { template inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, const Eigen::Matrix2& rhs) { - Eigen::Matrix4 result; - Eigen::KroneckerProduct kroneckerProduct{lhs, rhs}; - kroneckerProduct.evalTo(result); - return result; + return Eigen::kroneckerProduct(lhs, rhs); } template From 91fe42d347f699bc0ad361c9079cca8bc04679c2 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 20 Nov 2025 18:14:03 +0100 Subject: [PATCH 095/100] more cleanup --- .../Transforms/GateDecompositionPattern.cpp | 51 +++++++++++++++---- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 47 ++--------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 1deb1bd4e..3f82ccc23 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -9,6 +9,7 @@ */ #include "Helpers.h" +#include "ir/Definitions.hpp" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" @@ -32,7 +33,9 @@ namespace mqt::ir::opt { /** - * @brief This pattern attempts to cancel consecutive self-inverse operations. + * @brief This pattern attempts to collect as many operations as possible into a + * 4x4 unitary matrix and then decompose it into rotation and given basis + * gates. */ struct GateDecompositionPattern final : mlir::OpInterfaceRewritePattern { @@ -70,7 +73,13 @@ struct GateDecompositionPattern final */ std::vector gates; + /** + * Global phase adjustment required for the sequence. + */ fp globalPhase{}; + /** + * @return true if the global phase adjustment is not zero. + */ [[nodiscard]] bool hasGlobalPhase() const { return std::abs(globalPhase) > DEFAULT_ATOL; } @@ -106,7 +115,14 @@ struct GateDecompositionPattern final return unitaryMatrix; } }; + /** + * Helper type to show that a gate sequence is supposed to only contain + * single-qubit gates. + */ using OneQubitGateSequence = QubitGateSequence; + /** + * Helper type to show that the gate sequence may contain two-qubit gates. + */ using TwoQubitGateSequence = QubitGateSequence; /** @@ -1518,7 +1534,15 @@ struct GateDecompositionPattern final } }; + /** + * Factor by which two matrices are considered to be the same when simplifying + * during a decomposition. + */ static constexpr auto DEFAULT_FIDELITY = 1.0 - 1e-15; + /** + * Largest number that will be assumed as zero for the euler decompositions + * and the global phase. + */ static constexpr auto DEFAULT_ATOL = 1e-12; /** @@ -1922,7 +1946,7 @@ struct GateDecompositionPattern final * necessary to construct an equivalent to the canonical gate. */ [[nodiscard]] std::array - traces(TwoQubitWeylDecomposition target) const { + traces(const TwoQubitWeylDecomposition& target) const { return { static_cast(4.) * qfp(std::cos(target.a) * std::cos(target.b) * std::cos(target.c), @@ -1938,10 +1962,13 @@ struct GateDecompositionPattern final }; } - static OneQubitGateSequence generateCircuit(EulerBasis targetBasis, - const matrix2x2& unitaryMatrix, - bool simplify, - std::optional atol) { + /** + * Perform single-qubit decomposition of a 2x2 unitary matrix based on a + * given euler basis. + */ + [[nodiscard]] static OneQubitGateSequence + generateCircuit(EulerBasis targetBasis, const matrix2x2& unitaryMatrix, + bool simplify, std::optional atol) { auto [theta, phi, lambda, phase] = anglesFromUnitary(unitaryMatrix, targetBasis); @@ -1964,12 +1991,16 @@ struct GateDecompositionPattern final } } - static OneQubitGateSequence unitaryToGateSequenceInner( - matrix2x2 unitaryMat, + /** + * Decompose a single-qubit unitary matrix into a single-qubit gate + * sequence. Multiple euler bases may be specified and the one with the + * least complexity will be chosen. + */ + [[nodiscard]] static OneQubitGateSequence unitaryToGateSequenceInner( + const matrix2x2& unitaryMat, const llvm::SmallVector& targetBasisList, QubitId /*qubit*/, const std::vector>& - /*error_map*/, // TODO: remove error_map+qubit for platform - // independence + /*error_map*/, // per qubit a mapping of operation name to error value bool simplify, std::optional atol) { auto calculateError = [](const OneQubitGateSequence& sequence) -> fp { return static_cast(sequence.complexity()); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 1167af465..1ab09dea1 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -10,27 +10,25 @@ #pragma once +#include "ir/Definitions.hpp" #include "ir/operations/OpType.hpp" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include #include #include -#include // TODO: unstable -#include // TODO: remove -#include // TODO: remove +#include #include #include +#include // TODO: unstable namespace mqt::ir::opt { -using fp = double; +using fp = qc::fp; using qfp = std::complex; using matrix2x2 = Eigen::Matrix2; using matrix4x4 = Eigen::Matrix4; using rmatrix4x4 = Eigen::Matrix4; using diagonal4x4 = Eigen::Vector; using rdiagonal4x4 = Eigen::Vector; -; constexpr qfp C_ZERO{0., 0.}; constexpr qfp C_ONE{1., 0.}; @@ -42,43 +40,6 @@ constexpr qfp M_IM{0., -1.}; namespace mqt::ir::opt::helpers { -inline void print(std::size_t x) { std::cerr << x; } -inline void print(fp x) { std::cerr << x; } - -inline void print(qfp x) { - std::cerr << std::setprecision(17) << x.real() << 'i' << x.imag(); -} - -// TODO: remove -template -void print(Eigen::Matrix matrix, const std::string& s = "", - bool force = false) { - if (!force) { - return; - } - if (!s.empty()) { - llvm::errs() << "=== " << s << " ===\n"; - } - std::cerr << matrix; - llvm::errs() << '\n'; -} - -template -void print(T matrix, const std::string& s = "", bool force = false) { - if (!force) { - return; - } - if (!s.empty()) { - llvm::errs() << "=== " << s << " ===\n"; - } - - for (auto&& a : matrix) { - print(a); - std::cerr << ' '; - } - llvm::errs() << '\n'; -} - std::optional mlirValueToFp(mlir::Value value); template From 1705f83d9bb9d55df5db96cc745fabb349e09047 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 21 Nov 2025 16:48:18 +0100 Subject: [PATCH 096/100] clean up weyl decomposition creation --- .../Transforms/GateDecompositionPattern.cpp | 781 +++++++++--------- 1 file changed, 382 insertions(+), 399 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 3f82ccc23..4cf968f3f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -88,6 +88,8 @@ struct GateDecompositionPattern final * Calculate complexity of sequence according to getComplexity(). */ [[nodiscard]] std::size_t complexity() const { + // TODO: add more sophisticated metric to determine complexity of + // series/sequence // TODO: caching mechanism? std::size_t c{}; for (auto&& gate : gates) { @@ -164,8 +166,8 @@ struct GateDecompositionPattern final } matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); - auto targetDecomposition = TwoQubitWeylDecomposition::create( - unitaryMatrix, DEFAULT_FIDELITY, std::nullopt); + auto targetDecomposition = + TwoQubitWeylDecomposition::create(unitaryMatrix, DEFAULT_FIDELITY); std::optional bestSequence; for (const auto& decomposer : basisDecomposers) { @@ -186,8 +188,6 @@ struct GateDecompositionPattern final // only accept new sequence if it shortens existing series by more than two // gates; this prevents an oscillation with phase gates if (bestSequence->complexity() + 2 >= series.complexity) { - // TODO: add more sophisticated metric to determine complexity of - // series/sequence llvm::errs() << "SEQUENCE LONGER THAN INPUT (" << bestSequence->gates.size() << "; " << bestSequence->complexity() << " vs " << series.complexity @@ -583,13 +583,14 @@ struct GateDecompositionPattern final } enum class Specialization : std::uint8_t { - General, - IdEquiv, - SWAPEquiv, - PartialSWAPEquiv, - PartialSWAPFlipEquiv, - ControlledEquiv, - MirrorControlledEquiv, + General, // canonical gate has no special symmetry. + IdEquiv, // canonical gate is identity. + SWAPEquiv, // canonical gate is SWAP. + PartialSWAPEquiv, // canonical gate is partial SWAP. + PartialSWAPFlipEquiv, // canonical gate is flipped partial SWAP. + ControlledEquiv, // canonical gate is a controlled gate. + MirrorControlledEquiv, // canonical gate is swap + controlled gate. + // These next 3 gates use the definition of fSim from eq (1) in: // https://arxiv.org/pdf/2001.08343.pdf FSimaabEquiv, @@ -933,7 +934,9 @@ struct GateDecompositionPattern final matrix2x2 k2r; // "right" qubit before canonical gate Specialization specialization; // detected symmetries in the matrix EulerBasis defaultEulerBasis; // recommended euler basis for k1l/k2l/k1r/k2r - std::optional requestedFidelity; // desired fidelity + std::optional requestedFidelity; // desired fidelity; + // if set to std::nullopt, no automatic + // specialization will be applied fp calculatedFidelity; // actual fidelity of decomposition matrix4x4 unitaryMatrix; // original matrix for this decomposition @@ -948,9 +951,8 @@ struct GateDecompositionPattern final * gates. * @param specialization Force the use this specialization. */ - static TwoQubitWeylDecomposition - create(matrix4x4 unitaryMatrix, std::optional fidelity, - std::optional specialization) { + static TwoQubitWeylDecomposition create(const matrix4x4& unitaryMatrix, + std::optional fidelity) { auto u = unitaryMatrix; auto detU = u.determinant(); auto detPow = std::pow(detU, static_cast(-0.25)); @@ -960,74 +962,14 @@ struct GateDecompositionPattern final matrix4x4 m2 = uP.transpose() * uP; auto defaultEulerBasis = EulerBasis::ZYZ; - // M2 is a symmetric complex matrix. We need to decompose it as M2 = P D - // P^T where P ∈ SO(4), D is diagonal with unit-magnitude elements. - // - // We can't use raw `eig` directly because it isn't guaranteed to give - // us real or orthogonal eigenvectors. Instead, since `M2` is - // complex-symmetric, - // M2 = A + iB - // for real-symmetric `A` and `B`, and as - // M2^+ @ M2 = A^2 + B^2 + i [A, B] = 1 - // we must have `A` and `B` commute, and consequently they are - // simultaneously diagonalizable. Mixing them together _should_ account - // for any degeneracy problems, but it's not guaranteed, so we repeat it - // a little bit. The fixed seed is to make failures deterministic; the - // value is not important. - auto state = std::mt19937{2023}; - std::normal_distribution dist; - auto found = false; - diagonal4x4 d = diagonal4x4::Zero(); - matrix4x4 p = matrix4x4::Zero(); + // diagonalization yields eigenvectors (p) and eigenvalues (d); + // p is used to calculate K1/K2 (and thus the single-qubit gates + // surrounding the canonical gate); d is is used to determine the weyl + // coordinates and thus the parameters of the canonical gate + // TODO: it may be possible to lower the precision + auto [p, d] = diagonalizeComplexSymmetric(m2, 1e-13); - for (int i = 0; i < 100; ++i) { - fp randA{}; - fp randB{}; - // For debugging the algorithm use the same RNG values from the - // previous Python implementation for the first random trial. - // In most cases this loop only executes a single iteration and - // using the same rng values rules out possible RNG differences - // as the root cause of a test failure - if (i == 0) { - randA = 1.2602066112249388; - randB = 0.22317849046722027; - } else { - randA = dist(state); - randB = dist(state); - } - rmatrix4x4 m2Real = randA * m2.real() + randB * m2.imag(); - rmatrix4x4 pInnerReal = selfAdjointEigenLower(m2Real).first; - matrix4x4 pInner = pInnerReal; - diagonal4x4 dInner = (pInner.transpose() * m2 * pInner).diagonal(); - - matrix4x4 diagD = dInner.asDiagonal(); - - matrix4x4 compare = pInner * diagD * pInner.transpose(); - found = compare.isApprox(m2, 1e-13); - if (found) { - // p are the eigenvectors which are decomposed into the - // single-qubit gates surrounding the canonical gate - p = pInner; - // d is the sqrt of the eigenvalues that are used to determine the - // weyl coordinates and thus the parameters of the canonical gate - d = dInner; - break; - } - } - if (!found) { - throw std::runtime_error{ - "TwoQubitWeylDecomposition: failed to diagonalize M2."}; - } - // check that p is in SO(4) - assert((p.transpose() * p).isIdentity(SANITY_CHECK_PRECISION)); - // make sure determinant of sqrt(eigenvalues) is 1.0 - assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < - SANITY_CHECK_PRECISION); - - // see - // https://github.com/mpham26uchicago/laughing-umbrella/blob/main/background/Full%20Two%20Qubit%20KAK%20Implementation.ipynb, - // Step 7 - Eigen::Vector cs; + Eigen::Vector cs; // weyl coordinates rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; dReal(3) = -dReal(0) - dReal(1) - dReal(2); for (int i = 0; i < static_cast(cs.size()); ++i) { @@ -1035,14 +977,14 @@ struct GateDecompositionPattern final cs[i] = remEuclid((dReal(i) + dReal(3)) / 2.0, qc::TAU); } + // re-order coordinates and according to min(a, pi/2 - a) with + // a = x mod pi/2 for each weyl coordinate x decltype(cs) cstemp; llvm::transform(cs, cstemp.begin(), [](auto&& x) { auto tmp = remEuclid(x, qc::PI_2); return std::min(tmp, qc::PI_2 - tmp); }); - std::array order{ - 0, 1, 2}; // TODO: needs to be adjusted depending on eigenvector - // order in eigen decomposition algorithm? + std::array order{0, 1, 2}; llvm::stable_sort(order, [&](auto a, auto b) { return cstemp[a] < cstemp[b]; }); std::tie(order[0], order[1], order[2]) = @@ -1052,22 +994,22 @@ struct GateDecompositionPattern final std::tie(dReal(0), dReal(1), dReal(2)) = std::tuple{dReal(order[0]), dReal(order[1]), dReal(order[2])}; - // swap columns of p according to order + // update columns of p according to order matrix4x4 pOrig = p; for (int i = 0; i < static_cast(order.size()); ++i) { p.col(i) = pOrig.col(order[i]); } + // apply correction for determinant if necessary if (p.determinant().real() < 0.0) { auto lastColumnIndex = p.cols() - 1; p.col(lastColumnIndex) *= -1.0; } + assert(std::abs(p.determinant() - 1.0) < SANITY_CHECK_PRECISION); matrix4x4 temp = dReal.asDiagonal(); temp *= IM; temp = temp.exp(); - assert(std::abs(p.determinant() - 1.0) < SANITY_CHECK_PRECISION); - matrix4x4 k1 = uP * p * temp; assert((k1.transpose() * k1).isIdentity()); // k1 must be orthogonal assert(k1.determinant().real() > 0.0); @@ -1077,12 +1019,15 @@ struct GateDecompositionPattern final assert(k2.determinant().real() > 0.0); k2 = magicBasisTransform(k2, MagicBasisTransform::Into); + // ensure k1 and k2 are correct assert((k1 * magicBasisTransform(temp.conjugate(), MagicBasisTransform::Into) * k2) .isApprox(u, SANITY_CHECK_PRECISION)); + // calculate k1 = K1l⊗ K1r auto [K1l, K1r, phase_l] = decomposeTwoQubitProductGate(k1); + // decompose k2 = K2l⊗ K2r auto [K2l, K2r, phase_r] = decomposeTwoQubitProductGate(k2); assert(helpers::kroneckerProduct(K1l, K1r).isApprox( k1, SANITY_CHECK_PRECISION)); @@ -1144,79 +1089,13 @@ struct GateDecompositionPattern final } auto [a, b, c] = std::tie(cs[1], cs[0], cs[2]); - auto getCanonicalMatrix = [](fp a, fp b, fp c) -> matrix4x4 { - auto xx = getTwoQubitMatrix({ - .type = qc::RXX, - .parameter = {a}, - .qubitId = {0, 1}, - }); - auto yy = getTwoQubitMatrix({ - .type = qc::RYY, - .parameter = {b}, - .qubitId = {0, 1}, - }); - auto zz = getTwoQubitMatrix({ - .type = qc::RZZ, - .parameter = {c}, - .qubitId = {0, 1}, - }); - return zz * yy * xx; - }; + // make sure decomposition is equal to input assert((helpers::kroneckerProduct(K1l, K1r) * getCanonicalMatrix(a * -2.0, b * -2.0, c * -2.0) * helpers::kroneckerProduct(K2l, K2r) * std::exp(IM * globalPhase)) .isApprox(unitaryMatrix, SANITY_CHECK_PRECISION)); - auto isClose = [&](fp ap, fp bp, fp cp) -> bool { - auto da = a - ap; - auto db = b - bp; - auto dc = c - cp; - auto tr = static_cast(4.) * - qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); - if (fidelity) { - return traceToFidelity(tr) >= *fidelity; - } - return false; - }; - - auto closestAbc = closestPartialSwap(a, b, c); - auto closestAbMinusC = closestPartialSwap(a, b, -c); - auto flippedFromOriginal = false; - auto getDefaultSpecialzation = [&]() { - if (isClose(0., 0., 0.)) { - return Specialization::IdEquiv; - } - if (isClose(qc::PI_4, qc::PI_4, qc::PI_4) || - isClose(qc::PI_4, qc::PI_4, -qc::PI_4)) { - return Specialization::SWAPEquiv; - } - if (isClose(closestAbc, closestAbc, closestAbc)) { - return Specialization::PartialSWAPEquiv; - } - if (isClose(closestAbMinusC, closestAbMinusC, -closestAbMinusC)) { - return Specialization::PartialSWAPFlipEquiv; - } - if (isClose(a, 0., 0.)) { - return Specialization::ControlledEquiv; - } - if (isClose(qc::PI_4, qc::PI_4, c)) { - return Specialization::MirrorControlledEquiv; - } - if (isClose((a + b) / 2., (a + b) / 2., c)) { - return Specialization::FSimaabEquiv; - } - if (isClose(a, (b + c) / 2., (b + c) / 2.)) { - return Specialization::FSimabbEquiv; - } - if (isClose(a, (b - c) / 2., (c - b) / 2.)) { - return Specialization::FSimabmbEquiv; - } - return Specialization::General; - }; - auto actualSpecialization = - specialization.value_or(getDefaultSpecialzation()); - TwoQubitWeylDecomposition general{ + TwoQubitWeylDecomposition decomposition{ .a = a, .b = b, .c = c, @@ -1231,96 +1110,272 @@ struct GateDecompositionPattern final .calculatedFidelity = -1.0, .unitaryMatrix = unitaryMatrix, }; - auto getSpecializedDecomposition = [&]() { + + // determine actual specialization of canonical gate so that the 1q + // matrices can potentially be simplified + auto flippedFromOriginal = decomposition.applySpecialization(); + + auto getTrace = [&]() { + if (flippedFromOriginal) { + auto [da, db, dc] = std::array{ + qc::PI_2 - a - decomposition.a, + b - decomposition.b, + -c - decomposition.c, + }; + return static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + } + auto [da, db, dc] = std::array{a - decomposition.a, b - decomposition.b, + c - decomposition.c}; + return static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + }; + auto trace = getTrace(); + decomposition.calculatedFidelity = traceToFidelity(trace); + // final check if specialization is close enough to the original matrix to + // satisfy the requested fidelity; since no forced specialization is + // allowed, this should never fail + if (decomposition.requestedFidelity) { + if (decomposition.calculatedFidelity + 1.0e-13 < + *decomposition.requestedFidelity) { + throw std::runtime_error{ + "TwoQubitWeylDecomposition: Calculated fidelity of " + "specialization is worse than requested fidelity!"}; + } + } + decomposition.globalPhase += std::arg(trace); + + // final check if decomposition is still valid after specialization + assert((helpers::kroneckerProduct(decomposition.k1l, decomposition.k1r) * + getCanonicalMatrix(decomposition.a * -2.0, decomposition.b * -2.0, + decomposition.c * -2.0) * + helpers::kroneckerProduct(decomposition.k2l, decomposition.k2r) * + std::exp(IM * decomposition.globalPhase)) + .isApprox(unitaryMatrix, SANITY_CHECK_PRECISION)); + + return decomposition; + } + + /** + * Calculate matrix of canonical gate based on its parameters a, b, c. + */ + static matrix4x4 getCanonicalMatrix(fp a, fp b, fp c) { + auto xx = getTwoQubitMatrix({ + .type = qc::RXX, + .parameter = {a}, + .qubitId = {0, 1}, + }); + auto yy = getTwoQubitMatrix({ + .type = qc::RYY, + .parameter = {b}, + .qubitId = {0, 1}, + }); + auto zz = getTwoQubitMatrix({ + .type = qc::RZZ, + .parameter = {c}, + .qubitId = {0, 1}, + }); + return zz * yy * xx; + } + + private: + /** + * Diagonalize given complex symmetric matrix M into (P, d) using a + * randomized algorithm. + * This approach is used in both qiskit and quantumflow. + * + * P is the matrix of real or orthogonal eigenvectors of M with P ∈ SO(4) + * d is a vector containing sqrt(eigenvalues) of M with unit-magnitude + * elements (for each element, complex magnitude is 1.0). + * D is d as a diagonal matrix. + * + * M = P * D * P^T + * + * @return pair of (P, D.diagonal()) + */ + [[nodiscard]] static std::pair + diagonalizeComplexSymmetric(const matrix4x4& m, fp precision) { + // We can't use raw `eig` directly because it isn't guaranteed to give + // us real or orthogonal eigenvectors. Instead, since `M` is + // complex-symmetric, + // M = A + iB + // for real-symmetric `A` and `B`, and as + // M^+ @ M2 = A^2 + B^2 + i [A, B] = 1 + // we must have `A` and `B` commute, and consequently they are + // simultaneously diagonalizable. Mixing them together _should_ account + // for any degeneracy problems, but it's not guaranteed, so we repeat it + // a little bit. The fixed seed is to make failures deterministic; the + // value is not important. + auto state = std::mt19937{2023}; + std::normal_distribution dist; + + for (int i = 0; i < 100; ++i) { + fp randA{}; + fp randB{}; + // For debugging the algorithm use the same RNG values from the + // previous Python implementation for the first random trial. + // In most cases this loop only executes a single iteration and + // using the same rng values rules out possible RNG differences + // as the root cause of a test failure + if (i == 0) { + randA = 1.2602066112249388; + randB = 0.22317849046722027; + } else { + randA = dist(state); + randB = dist(state); + } + rmatrix4x4 m2Real = randA * m.real() + randB * m.imag(); + rmatrix4x4 pInnerReal = selfAdjointEigenLower(m2Real).first; + matrix4x4 p = pInnerReal; + diagonal4x4 d = (p.transpose() * m * p).diagonal(); + + matrix4x4 diagD = d.asDiagonal(); + + matrix4x4 compare = p * diagD * p.transpose(); + if (compare.isApprox(m, precision)) { + // p are the eigenvectors which are decomposed into the + // single-qubit gates surrounding the canonical gate + // d is the sqrt of the eigenvalues that are used to determine the + // weyl coordinates and thus the parameters of the canonical gate + // check that p is in SO(4) + assert((p.transpose() * p).isIdentity(SANITY_CHECK_PRECISION)); + // make sure determinant of sqrt(eigenvalues) is 1.0 + assert(std::abs(matrix4x4{d.asDiagonal()}.determinant() - 1.0) < + SANITY_CHECK_PRECISION); + return std::make_pair(p, d); + } + } + throw std::runtime_error{ + "TwoQubitWeylDecomposition: failed to diagonalize M2."}; + } + + [[nodiscard]] Specialization bestSpecialization() const { + auto isClose = [this](fp ap, fp bp, fp cp) -> bool { + auto da = a - ap; + auto db = b - bp; + auto dc = c - cp; + auto tr = static_cast(4.) * + qfp(std::cos(da) * std::cos(db) * std::cos(dc), + std::sin(da) * std::sin(db) * std::sin(dc)); + if (requestedFidelity) { + return traceToFidelity(tr) >= *requestedFidelity; + } + return false; + }; + + auto closestAbc = closestPartialSwap(a, b, c); + auto closestAbMinusC = closestPartialSwap(a, b, -c); + + if (isClose(0., 0., 0.)) { + return Specialization::IdEquiv; + } + if (isClose(qc::PI_4, qc::PI_4, qc::PI_4) || + isClose(qc::PI_4, qc::PI_4, -qc::PI_4)) { + return Specialization::SWAPEquiv; + } + if (isClose(closestAbc, closestAbc, closestAbc)) { + return Specialization::PartialSWAPEquiv; + } + if (isClose(closestAbMinusC, closestAbMinusC, -closestAbMinusC)) { + return Specialization::PartialSWAPFlipEquiv; + } + if (isClose(a, 0., 0.)) { + return Specialization::ControlledEquiv; + } + if (isClose(qc::PI_4, qc::PI_4, c)) { + return Specialization::MirrorControlledEquiv; + } + if (isClose((a + b) / 2., (a + b) / 2., c)) { + return Specialization::FSimaabEquiv; + } + if (isClose(a, (b + c) / 2., (b + c) / 2.)) { + return Specialization::FSimabbEquiv; + } + if (isClose(a, (b - c) / 2., (c - b) / 2.)) { + return Specialization::FSimabmbEquiv; + } + return Specialization::General; + } + + /** + * @return true if the specialization flipped the original decomposition + */ + bool applySpecialization() { + if (specialization != Specialization::General) { + throw std::logic_error{"Application of specialization only works on " + "general decomposition!"}; + } + bool flippedFromOriginal = false; + auto newSpecialization = bestSpecialization(); + if (newSpecialization == Specialization::General) { + // U has no special symmetry. + // + // This gate binds all 6 possible parameters, so there is no need to + // make the single-qubit pre-/post-gates canonical. + return flippedFromOriginal; + } + specialization = newSpecialization; + + if (newSpecialization == Specialization::IdEquiv) { // :math:`U \sim U_d(0,0,0) \sim Id` // // This gate binds 0 parameters, we make it canonical by // setting // :math:`K2_l = Id` , :math:`K2_r = Id`. - if (actualSpecialization == Specialization::IdEquiv) { - return TwoQubitWeylDecomposition{ - .a = 0., - .b = 0., - .c = 0., - .globalPhase = general.globalPhase, - .k1l = general.k1l * general.k2l, - .k2l = IDENTITY_GATE, - .k1r = general.k1r * general.k2r, - .k2r = IDENTITY_GATE, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + a = 0.; + b = 0.; + c = 0.; + // unmodified global phase + k1l = k1l * k2l; + k2l = IDENTITY_GATE; + k1r = k1r * k2r; + k2r = IDENTITY_GATE; + } else if (newSpecialization == Specialization::SWAPEquiv) { // :math:`U \sim U_d(\pi/4, \pi/4, \pi/4) \sim U(\pi/4, \pi/4, // -\pi/4) \sim \text{SWAP}` // // This gate binds 0 parameters, we make it canonical by // setting // :math:`K2_l = Id` , :math:`K2_r = Id`. - if (actualSpecialization == Specialization::SWAPEquiv) { - if (c > 0.) { - return TwoQubitWeylDecomposition{ - .a = qc::PI_4, - .b = qc::PI_4, - .c = qc::PI_4, - .globalPhase = general.globalPhase, - .k1l = general.k1l * general.k2r, - .k2l = IDENTITY_GATE, - .k1r = general.k1r * general.k2l, - .k2r = IDENTITY_GATE, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + a = qc::PI_4; + b = qc::PI_4; + c = qc::PI_4; + if (c > 0.) { + // unmodified global phase + k1l = k1l * k2r; + k1r = k1r * k2l; + k2l = IDENTITY_GATE; + k2r = IDENTITY_GATE; + } else { flippedFromOriginal = true; - return TwoQubitWeylDecomposition{ - .a = qc::PI_4, - .b = qc::PI_4, - .c = qc::PI_4, - .globalPhase = globalPhase + qc::PI_2, - .k1l = general.k1l * IPZ * general.k2r, - .k2l = IDENTITY_GATE, - .k1r = general.k1r * IPZ * general.k2l, - .k2r = IDENTITY_GATE, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; + + globalPhase += qc::PI_2; + k1l = k1l * IPZ * k2r; + k1r = k1r * IPZ * k2l; + k2l = IDENTITY_GATE; + k2r = IDENTITY_GATE; } + } else if (newSpecialization == Specialization::PartialSWAPEquiv) { // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, \alpha\pi/4) \sim // \text{SWAP}^\alpha` // // This gate binds 3 parameters, we make it canonical by setting: // // :math:`K2_l = Id`. - if (actualSpecialization == Specialization::PartialSWAPEquiv) { - auto closest = closestPartialSwap(a, b, c); - auto k2lDag = general.k2l.transpose().conjugate(); - - return TwoQubitWeylDecomposition{ - .a = closest, - .b = closest, - .c = closest, - .globalPhase = general.globalPhase, - .k1l = general.k1l * general.k2l, - .k2l = IDENTITY_GATE, - .k1r = general.k1r * general.k2l, - .k2r = k2lDag * general.k2r, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto closest = closestPartialSwap(a, b, c); + auto k2lDagger = k2l.transpose().conjugate(); + + a = closest; + b = closest; + c = closest; + // unmodified global phase + k1l = k1l * k2l; + k1r = k1r * k2l; + k2r = k2lDagger * k2r; + k2l = IDENTITY_GATE; + } else if (newSpecialization == Specialization::PartialSWAPFlipEquiv) { // :math:`U \sim U_d(\alpha\pi/4, \alpha\pi/4, -\alpha\pi/4) \sim // \text{SWAP}^\alpha` // @@ -1331,54 +1386,40 @@ struct GateDecompositionPattern final // This gate binds 3 parameters, we make it canonical by setting: // // :math:`K2_l = Id` - if (actualSpecialization == Specialization::PartialSWAPFlipEquiv) { - auto closest = closestPartialSwap(a, b, -c); - auto k2lDag = general.k2l.transpose().conjugate(); - - return TwoQubitWeylDecomposition{ - .a = closest, - .b = closest, - .c = -closest, - .globalPhase = general.globalPhase, - .k1l = general.k1l * general.k2l, - .k2l = IDENTITY_GATE, - .k1r = general.k1r * IPZ * general.k2l * IPZ, - .k2r = IPZ * k2lDag * IPZ * general.k2r, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto closest = closestPartialSwap(a, b, -c); + auto k2lDagger = k2l.transpose().conjugate(); + + a = closest; + b = closest; + c = -closest; + // unmodified global phase + k1l = k1l * k2l; + k1r = k1r * IPZ * k2l * IPZ; + k2r = IPZ * k2lDagger * IPZ * k2r; + k2l = IDENTITY_GATE; + } else if (newSpecialization == Specialization::ControlledEquiv) { // :math:`U \sim U_d(\alpha, 0, 0) \sim \text{Ctrl-U}` // // This gate binds 4 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l) Rx(\lambda_l)` , // :math:`K2_r = Ry(\theta_r) Rx(\lambda_r)` . - if (actualSpecialization == Specialization::ControlledEquiv) { - auto eulerBasis = EulerBasis::XYX; - auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - anglesFromUnitary(general.k2l, eulerBasis); - auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = - anglesFromUnitary(general.k2r, eulerBasis); - return TwoQubitWeylDecomposition{ - .a = a, - .b = 0., - .c = 0., - .globalPhase = globalPhase + k2lphase + k2rphase, - .k1l = general.k1l * rxMatrix(k2lphi), - .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), - .k1r = general.k1r * rxMatrix(k2rphi), - .k2r = ryMatrix(k2rtheta) * rxMatrix(k2rlambda), - .specialization = actualSpecialization, - .defaultEulerBasis = eulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto eulerBasis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + anglesFromUnitary(k2l, eulerBasis); + auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = + anglesFromUnitary(k2r, eulerBasis); + + // unmodified parameter a + b = 0.; + c = 0.; + globalPhase = globalPhase + k2lphase + k2rphase; + k1l = k1l * rxMatrix(k2lphi); + k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda); + k1r = k1r * rxMatrix(k2rphi); + k2r = ryMatrix(k2rtheta) * rxMatrix(k2rlambda); + defaultEulerBasis = eulerBasis; + } else if (newSpecialization == Specialization::MirrorControlledEquiv) { // :math:`U \sim U_d(\pi/4, \pi/4, \alpha) \sim \text{SWAP} \cdot // \text{Ctrl-U}` // @@ -1386,151 +1427,81 @@ struct GateDecompositionPattern final // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)` , :math:`K2_r = // Ry(\theta_r)\cdot Rz(\lambda_r)` - if (actualSpecialization == Specialization::MirrorControlledEquiv) { - auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - anglesFromUnitary(general.k2l, EulerBasis::ZYZ); - auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = - anglesFromUnitary(general.k2r, EulerBasis::ZYZ); - return TwoQubitWeylDecomposition{ - .a = qc::PI_4, - .b = qc::PI_4, - .c = c, - .globalPhase = globalPhase + k2lphase + k2rphase, - .k1l = general.k1l * rzMatrix(k2rphi), - .k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda), - .k1r = general.k1r * rzMatrix(k2lphi), - .k2r = ryMatrix(k2rtheta) * rzMatrix(k2rlambda), - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + anglesFromUnitary(k2l, EulerBasis::ZYZ); + auto [k2rtheta, k2rphi, k2rlambda, k2rphase] = + anglesFromUnitary(k2r, EulerBasis::ZYZ); + + a = qc::PI_4; + b = qc::PI_4; + // unmodified parameter c + globalPhase = globalPhase + k2lphase + k2rphase; + k1l = k1l * rzMatrix(k2rphi); + k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda); + k1r = k1r * rzMatrix(k2lphi); + k2r = ryMatrix(k2rtheta) * rzMatrix(k2rlambda); + } else if (newSpecialization == Specialization::FSimaabEquiv) { // :math:`U \sim U_d(\alpha, \alpha, \beta), \alpha \geq |\beta|` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)\cdot Rz(\lambda_l)`. - if (actualSpecialization == Specialization::FSimaabEquiv) { - auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - anglesFromUnitary(general.k2l, EulerBasis::ZYZ); - return TwoQubitWeylDecomposition{ - .a = (a + b) / 2., - .b = (a + b) / 2., - .c = c, - .globalPhase = globalPhase + k2lphase, - .k1l = general.k1l * rzMatrix(k2lphi), - .k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda), - .k1r = general.k1r * rzMatrix(k2lphi), - .k2r = rzMatrix(-k2lphi) * general.k2r, - .specialization = actualSpecialization, - .defaultEulerBasis = general.defaultEulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + anglesFromUnitary(k2l, EulerBasis::ZYZ); + auto ab = (a + b) / 2.; + + a = ab; + b = ab; + // unmodified parameter c + globalPhase = globalPhase + k2lphase; + k1l = k1l * rzMatrix(k2lphi); + k2l = ryMatrix(k2ltheta) * rzMatrix(k2llambda); + k1r = k1r * rzMatrix(k2lphi); + k2r = rzMatrix(-k2lphi) * k2r; + } else if (newSpecialization == Specialization::FSimabbEquiv) { // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - if (actualSpecialization == Specialization::FSimabbEquiv) { - auto eulerBasis = EulerBasis::XYX; - auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - anglesFromUnitary(general.k2l, eulerBasis); - return TwoQubitWeylDecomposition{ - .a = a, - .b = (b + c) / 2., - .c = (b + c) / 2., - .globalPhase = globalPhase + k2lphase, - .k1l = general.k1l * rxMatrix(k2lphi), - .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), - .k1r = general.k1r * rxMatrix(k2lphi), - .k2r = rxMatrix(-k2lphi) * general.k2r, - .specialization = actualSpecialization, - .defaultEulerBasis = eulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } + auto eulerBasis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + anglesFromUnitary(k2l, eulerBasis); + auto bc = (b + c) / 2.; + + // unmodified parameter a + b = bc; + c = bc; + globalPhase = globalPhase + k2lphase; + k1l = k1l * rxMatrix(k2lphi); + k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda); + k1r = k1r * rxMatrix(k2lphi); + k2r = rxMatrix(-k2lphi) * k2r; + defaultEulerBasis = eulerBasis; + } else if (newSpecialization == Specialization::FSimabmbEquiv) { // :math:`U \sim U_d(\alpha, \beta, -\beta), \alpha \geq \beta \geq 0` // // This gate binds 5 parameters, we make it canonical by setting: // // :math:`K2_l = Ry(\theta_l)Rx(\lambda_l)` - if (actualSpecialization == Specialization::FSimabmbEquiv) { - auto eulerBasis = EulerBasis::XYX; - auto [k2ltheta, k2lphi, k2llambda, k2lphase] = - anglesFromUnitary(general.k2l, eulerBasis); - return TwoQubitWeylDecomposition{ - .a = a, - .b = (b - c) / 2., - .c = -((b - c) / 2.), - .globalPhase = globalPhase + k2lphase, - .k1l = general.k1l * rxMatrix(k2lphi), - .k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda), - .k1r = general.k1r * IPZ * rxMatrix(k2lphi) * IPZ, - .k2r = IPZ * rxMatrix(-k2lphi) * IPZ * general.k2r, - .specialization = actualSpecialization, - .defaultEulerBasis = eulerBasis, - .requestedFidelity = general.requestedFidelity, - .calculatedFidelity = general.calculatedFidelity, - .unitaryMatrix = general.unitaryMatrix, - }; - } - // U has no special symmetry. - // - // This gate binds all 6 possible parameters, so there is no need to - // make the single-qubit pre-/post-gates canonical. - if (actualSpecialization == Specialization::General) { - return general; - } + auto eulerBasis = EulerBasis::XYX; + auto [k2ltheta, k2lphi, k2llambda, k2lphase] = + anglesFromUnitary(k2l, eulerBasis); + auto bc = (b - c) / 2.; + + // unmodified parameter a + b = bc; + c = -bc; + globalPhase = globalPhase + k2lphase; + k1l = k1l * rxMatrix(k2lphi); + k2l = ryMatrix(k2ltheta) * rxMatrix(k2llambda); + k1r = k1r * IPZ * rxMatrix(k2lphi) * IPZ; + k2r = IPZ * rxMatrix(-k2lphi) * IPZ * k2r; + defaultEulerBasis = eulerBasis; + } else { throw std::logic_error{"Unknown specialization"}; - }; - - TwoQubitWeylDecomposition specialized = getSpecializedDecomposition(); - - auto getTr = [&]() { - if (flippedFromOriginal) { - auto [da, db, dc] = std::array{ - qc::PI_2 - a - specialized.a, - b - specialized.b, - -c - specialized.c, - }; - return static_cast(4.) * - qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); - } - auto [da, db, dc] = - std::array{a - specialized.a, b - specialized.b, c - specialized.c}; - return static_cast(4.) * - qfp(std::cos(da) * std::cos(db) * std::cos(dc), - std::sin(da) * std::sin(db) * std::sin(dc)); - }; - auto tr = getTr(); - specialized.calculatedFidelity = traceToFidelity(tr); - if (specialized.requestedFidelity) { - if (specialized.calculatedFidelity + 1.0e-13 < - *specialized.requestedFidelity) { - throw std::runtime_error{ - "Specialization: {:?} calculated fidelity: {} is worse than " - "requested fidelity: {}", - }; - } } - specialized.globalPhase += std::arg(tr); - - assert((helpers::kroneckerProduct(specialized.k1l, specialized.k1r) * - getCanonicalMatrix(specialized.a * -2.0, specialized.b * -2.0, - specialized.c * -2.0) * - helpers::kroneckerProduct(specialized.k2l, specialized.k2r) * - std::exp(IM * specialized.globalPhase)) - .isApprox(unitaryMatrix, SANITY_CHECK_PRECISION)); - - return specialized; + return flippedFromOriginal; } }; @@ -1575,6 +1546,15 @@ struct GateDecompositionPattern final matrix2x2 q2r; public: + /** + * Create decomposer that allows two-qubit decompositions based on the + * specified basis gate. + * This basis gate will appear between 0 and 3 times in each decompositions. + * The order of qubits is relevant and will change the results accordingly. + * The decomposer cannot handle different basis gates in the same + * decomposition (different order of the qubits also counts as a different + * basis gate). + */ static TwoQubitBasisDecomposer create(const OneQubitGateSequence::Gate& basisGate = {.type = qc::X, .parameter = {}, @@ -1616,7 +1596,7 @@ struct GateDecompositionPattern final }; auto basisDecomposer = TwoQubitWeylDecomposition::create( - getTwoQubitMatrix(basisGate), basisFidelity, std::nullopt); + getTwoQubitMatrix(basisGate), basisFidelity); auto superControlled = relativeEq(basisDecomposer.a, qc::PI_4, 1e-13, 1e-09) && relativeEq(basisDecomposer.c, 0.0, 1e-13, 1e-09); @@ -1763,7 +1743,7 @@ struct GateDecompositionPattern final } return minIndex; }; - // number of basis gates that need to be inserted + // number of basis gates that need to be used in the decomposition auto bestNbasis = numBasisGateUses.value_or(getDefaultNbasis()); auto chooseDecomposition = [&]() { if (bestNbasis == 0) { @@ -1816,12 +1796,15 @@ struct GateDecompositionPattern final }; for (std::size_t i = 0; i < bestNbasis; ++i) { + // add single-qubit decompositions before basis gate addEulerDecomposition(2 * i, 0); addEulerDecomposition((2 * i) + 1, 1); + // add basis gate gates.gates.push_back(basisGate); } + // add single-qubit decompositions after basis gate addEulerDecomposition(2UL * bestNbasis, 0); addEulerDecomposition((2UL * bestNbasis) + 1, 1); From 49d512f2deb86d7fe96928c633cbaff8b2e0132a Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 21 Nov 2025 17:15:09 +0100 Subject: [PATCH 097/100] remove openqasm code generation --- .../Transforms/GateDecompositionPattern.cpp | 65 ++----------------- 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 4cf968f3f..359b09142 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -149,11 +149,6 @@ struct GateDecompositionPattern final matchAndRewrite(UnitaryInterface op, mlir::PatternRewriter& rewriter) const override { auto series = TwoQubitSeries::getTwoQubitSeries(op); - llvm::errs() << "SERIES SIZE: " << series.gates.size() << '\n'; - for (auto&& gate : series.gates) { - std::cerr << gate.op->getName().stripDialect().str() << ", "; - } - std::cerr << '\n'; if (series.gates.size() < 3) { // too short @@ -182,16 +177,11 @@ struct GateDecompositionPattern final } } if (!bestSequence) { - llvm::errs() << "NO SEQUENCE GENERATED!\n"; return mlir::failure(); } // only accept new sequence if it shortens existing series by more than two // gates; this prevents an oscillation with phase gates if (bestSequence->complexity() + 2 >= series.complexity) { - llvm::errs() << "SEQUENCE LONGER THAN INPUT (" - << bestSequence->gates.size() << "; " - << bestSequence->complexity() << " vs " << series.complexity - << ")\n"; return mlir::failure(); } @@ -483,57 +473,13 @@ struct GateDecompositionPattern final {}); } - std::cerr << "SERIES: "; - for (auto&& gate : series.gates) { - auto name = gate.op->getName().stripDialect().str(); - if (name == "x" && gate.qubitIds.size() == 2) { - // controls come first - std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitIds[1], - gate.qubitIds[0]); - } else if (name == "i") { - } else if (gate.op.getParams().empty()) { - std::cerr << std::format( - "{}() q[{}] {};", name, gate.qubitIds[0], - (gate.qubitIds.size() > 1 - ? (", q[" + std::to_string(gate.qubitIds[1]) + "]") - : std::string{})); - } else { - auto parameter = helpers::getParameters(gate.op)[0]; - std::cerr << std::format( - "{}({}*pi) q[{}] {};", name, parameter / qc::PI, gate.qubitIds[0], - (gate.qubitIds.size() > 1 - ? (", q[" + std::to_string(gate.qubitIds[1]) + "]") - : std::string{})); - } - } - std::cerr << '\n'; - std::cerr << "GATE SEQUENCE!: gphase(" << sequence.globalPhase / qc::PI - << "*pi); \n"; matrix4x4 unitaryMatrix = helpers::kroneckerProduct(IDENTITY_GATE, IDENTITY_GATE); for (auto&& gate : sequence.gates) { auto gateMatrix = getTwoQubitMatrix(gate); unitaryMatrix = gateMatrix * unitaryMatrix; - if (gate.type == qc::X && gate.qubitId.size() == 2) { - // controls come first - std::cerr << std::format("cx() q[{}], q[{}];", gate.qubitId[1], - gate.qubitId[0]); - } else if (gate.parameter.empty()) { - std::cerr << std::format( - "{}() q[{}] {};", qc::toString(gate.type), gate.qubitId[0], - (gate.qubitId.size() > 1 - ? (", q[" + std::to_string(gate.qubitId[1]) + "]") - : std::string{})); - } else { - std::cerr << std::format( - "{}({}*pi) q[{}] {};", qc::toString(gate.type), - gate.parameter[0] / qc::PI, gate.qubitId[0], - (gate.qubitId.size() > 1 - ? (", q[" + std::to_string(gate.qubitId[1]) + "]") - : std::string{})); - } - std::cerr << '\n'; + // TODO: need to add each basis gate we want to use if (gate.type == qc::X) { mlir::SmallVector inCtrlQubits; if (gate.qubitId.size() > 1) { @@ -572,7 +518,6 @@ struct GateDecompositionPattern final throw std::runtime_error{"Unknown gate type!"}; } } - std::cerr << '\n'; assert((unitaryMatrix * std::exp(IM * sequence.globalPhase)) .isApprox(series.getUnitaryMatrix(), SANITY_CHECK_PRECISION)); @@ -1769,8 +1714,10 @@ struct GateDecompositionPattern final decomp, target1qEulerBases, 0, {}, true, std::nullopt); eulerDecompositions.push_back(eulerDecomp); } - TwoQubitGateSequence gates{.globalPhase = - targetDecomposition.globalPhase}; + TwoQubitGateSequence gates{ + .gates = {}, + .globalPhase = targetDecomposition.globalPhase, + }; // Worst case length is 5x 1q gates for each 1q decomposition + 1x 2q // gate We might overallocate a bit if the euler basis is different but // the worst case is just 16 extra elements with just a String and 2 From d8ab299fdffc2ac98a19c7b1ab3ec721a5428325 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 21 Nov 2025 17:30:39 +0100 Subject: [PATCH 098/100] fix includes and use SmallVector instead of std::vector --- .../Transforms/GateDecompositionPattern.cpp | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 359b09142..c2d377f12 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -10,6 +10,7 @@ #include "Helpers.h" #include "ir/Definitions.hpp" +#include "ir/operations/OpType.hpp" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +29,7 @@ #include #include #include -#include +#include // NOLINT(misc-include-cleaner) #include namespace mqt::ir::opt { @@ -71,7 +73,7 @@ struct GateDecompositionPattern final /** * Container sorting the gate sequence in order. */ - std::vector gates; + llvm::SmallVector gates; /** * Global phase adjustment required for the sequence. @@ -1777,7 +1779,7 @@ struct GateDecompositionPattern final * * which is optimal for all targets and bases */ - [[nodiscard]] static std::vector + [[nodiscard]] static llvm::SmallVector decomp0(const TwoQubitWeylDecomposition& target) { return { target.k1r * target.k2r, @@ -1799,7 +1801,7 @@ struct GateDecompositionPattern final * * which is optimal for all targets and bases with ``z==0`` or ``c==0``. */ - [[nodiscard]] std::vector + [[nodiscard]] llvm::SmallVector decomp1(const TwoQubitWeylDecomposition& target) const { // FIXME: fix for z!=0 and c!=0 using closest reflection (not always in // the Weyl chamber) @@ -1834,7 +1836,7 @@ struct GateDecompositionPattern final * and target :math:`\sim U_d(x, y, 0)`. No guarantees for * non-supercontrolled basis. */ - [[nodiscard]] std::vector + [[nodiscard]] llvm::SmallVector decomp2Supercontrolled(const TwoQubitWeylDecomposition& target) const { return { q2r * target.k2r, @@ -1855,7 +1857,7 @@ struct GateDecompositionPattern final * :math:`\sim U_d(\pi/4, b, 0)`, all b, and any target. No guarantees for * non-supercontrolled basis. */ - [[nodiscard]] std::vector + [[nodiscard]] llvm::SmallVector decomp3Supercontrolled(const TwoQubitWeylDecomposition& target) const { return { u3r * target.k2r, @@ -1975,21 +1977,22 @@ struct GateDecompositionPattern final angleZeroEpsilon = -1.0; } - fp globalPhase = phase - ((phi + lambda) / 2.); - - std::vector gates; + OneQubitGateSequence sequence{ + .gates = {}, + .globalPhase = phase - ((phi + lambda) / 2.), + }; if (std::abs(theta) <= angleZeroEpsilon) { lambda += phi; lambda = mod2pi(lambda); if (std::abs(lambda) > angleZeroEpsilon) { - gates.push_back({kGate, {lambda}}); - globalPhase += lambda / 2.0; + sequence.gates.push_back({.type = kGate, .parameter = {lambda}}); + sequence.globalPhase += lambda / 2.0; } - return {gates, globalPhase}; + return sequence; } if (std::abs(theta - qc::PI) <= angleZeroEpsilon) { - globalPhase += phi; + sequence.globalPhase += phi; lambda -= phi; phi = 0.0; } @@ -2001,16 +2004,16 @@ struct GateDecompositionPattern final } lambda = mod2pi(lambda); if (std::abs(lambda) > angleZeroEpsilon) { - globalPhase += lambda / 2.0; - gates.push_back({kGate, {lambda}}); + sequence.globalPhase += lambda / 2.0; + sequence.gates.push_back({.type = kGate, .parameter = {lambda}}); } - gates.push_back({aGate, {theta}}); + sequence.gates.push_back({.type = aGate, .parameter = {theta}}); phi = mod2pi(phi); if (std::abs(phi) > angleZeroEpsilon) { - globalPhase += phi / 2.0; - gates.push_back({kGate, {phi}}); + sequence.globalPhase += phi / 2.0; + sequence.gates.push_back({.type = kGate, .parameter = {phi}}); } - return {gates, globalPhase}; + return sequence; } }; From 4ea6f02355cc4f300a698e1570ce9354b52b19bc Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 21 Nov 2025 17:55:18 +0100 Subject: [PATCH 099/100] fix or silence linter --- .../Transforms/GateDecompositionPattern.cpp | 150 ++++++++++-------- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 23 ++- 2 files changed, 98 insertions(+), 75 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index c2d377f12..35c0f1991 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -14,22 +14,29 @@ #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" +#include // NOLINT(misc-include-cleaner) #include #include +#include #include +#include #include #include +#include #include +#include #include #include #include +#include #include #include #include #include +#include #include -#include -#include // NOLINT(misc-include-cleaner) +#include +#include // TODO: unstable, NOLINT(misc-include-cleaner) #include namespace mqt::ir::opt { @@ -162,8 +169,8 @@ struct GateDecompositionPattern final return mlir::failure(); } - matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); - auto targetDecomposition = + const matrix4x4 unitaryMatrix = series.getUnitaryMatrix(); + const auto targetDecomposition = TwoQubitWeylDecomposition::create(unitaryMatrix, DEFAULT_FIDELITY); std::optional bestSequence; @@ -357,10 +364,11 @@ struct GateDecompositionPattern final ? *firstQubitIt : *secondQubitIt); // new qubit ID based on position in outQubits - QubitId newInQubitId = std::distance(outQubits.begin(), it); + const QubitId newInQubitId = std::distance(outQubits.begin(), it); // position in operation input; since there are only two qubits, it must // be the "not old" one - QubitId newOpInQubitId = 1 - std::distance(opInQubits.begin(), it2); + const QubitId newOpInQubitId = + 1 - std::distance(opInQubits.begin(), it2); // update inQubit and update dangling iterator, then proceed as usual inQubits[newInQubitId] = opInQubits[newOpInQubitId]; @@ -371,8 +379,10 @@ struct GateDecompositionPattern final // possible to collect other single-qubit operations backtrackSingleQubitSeries(newInQubitId); } - QubitId firstQubitId = std::distance(outQubits.begin(), firstQubitIt); - QubitId secondQubitId = std::distance(outQubits.begin(), secondQubitIt); + const QubitId firstQubitId = + std::distance(outQubits.begin(), firstQubitIt); + const QubitId secondQubitId = + std::distance(outQubits.begin(), secondQubitIt); *firstQubitIt = nextGate->getResult(0); *secondQubitIt = nextGate->getResult(1); @@ -609,7 +619,7 @@ struct GateDecompositionPattern final } r /= std::sqrt(detR); // transpose with complex conjugate of each element - matrix2x2 rTConj = r.transpose().conjugate(); + const matrix2x2 rTConj = r.transpose().conjugate(); auto temp = helpers::kroneckerProduct(IDENTITY_GATE, rTConj); temp = specialUnitary * temp; @@ -734,24 +744,24 @@ struct GateDecompositionPattern final } static std::array paramsZyzInner(const matrix2x2& matrix) { - auto detArg = std::arg(matrix.determinant()); - auto phase = 0.5 * detArg; - auto theta = + const auto detArg = std::arg(matrix.determinant()); + const auto phase = 0.5 * detArg; + const auto theta = 2. * std::atan2(std::abs(matrix(1, 0)), std::abs(matrix(0, 0))); - auto ang1 = std::arg(matrix(1, 1)); - auto ang2 = std::arg(matrix(1, 0)); - auto phi = ang1 + ang2 - detArg; - auto lam = ang1 - ang2; + const auto ang1 = std::arg(matrix(1, 1)); + const auto ang2 = std::arg(matrix(1, 0)); + const auto phi = ang1 + ang2 - detArg; + const auto lam = ang1 - ang2; return {theta, phi, lam, phase}; } static std::array paramsZxzInner(const matrix2x2& matrix) { - auto [theta, phi, lam, phase] = paramsZyzInner(matrix); + const auto [theta, phi, lam, phase] = paramsZyzInner(matrix); return {theta, phi + (qc::PI / 2.), lam - (qc::PI / 2.), phase}; } static std::array paramsXyxInner(const matrix2x2& matrix) { - matrix2x2 matZyz{ + const matrix2x2 matZyz{ {static_cast(0.5) * (matrix(0, 0) + matrix(0, 1) + matrix(1, 0) + matrix(1, 1)), static_cast(0.5) * @@ -906,7 +916,7 @@ struct GateDecompositionPattern final u *= detPow; auto globalPhase = std::arg(detU) / 4.; auto uP = magicBasisTransform(u, MagicBasisTransform::OutOf); - matrix4x4 m2 = uP.transpose() * uP; + const matrix4x4 m2 = uP.transpose() * uP; auto defaultEulerBasis = EulerBasis::ZYZ; // diagonalization yields eigenvectors (p) and eigenvalues (d); @@ -943,7 +953,7 @@ struct GateDecompositionPattern final // update columns of p according to order matrix4x4 pOrig = p; - for (int i = 0; i < static_cast(order.size()); ++i) { + for (int i = 0; std::cmp_less(i, order.size()); ++i) { p.col(i) = pOrig.col(order[i]); } // apply correction for determinant if necessary @@ -1173,14 +1183,14 @@ struct GateDecompositionPattern final randA = dist(state); randB = dist(state); } - rmatrix4x4 m2Real = randA * m.real() + randB * m.imag(); - rmatrix4x4 pInnerReal = selfAdjointEigenLower(m2Real).first; - matrix4x4 p = pInnerReal; - diagonal4x4 d = (p.transpose() * m * p).diagonal(); + const rmatrix4x4 m2Real = randA * m.real() + randB * m.imag(); + const rmatrix4x4 pReal = selfAdjointEigenLower(m2Real).first; + const matrix4x4 p = pReal; + const diagonal4x4 d = (p.transpose() * m * p).diagonal(); - matrix4x4 diagD = d.asDiagonal(); + const matrix4x4 diagD = d.asDiagonal(); - matrix4x4 compare = p * diagD * p.transpose(); + const matrix4x4 compare = p * diagD * p.transpose(); if (compare.isApprox(m, precision)) { // p are the eigenvectors which are decomposed into the // single-qubit gates surrounding the canonical gate @@ -1542,9 +1552,9 @@ struct GateDecompositionPattern final {qfp(-0.5, 0.5), qfp(0.5, -0.5)}, }; - auto basisDecomposer = TwoQubitWeylDecomposition::create( + const auto basisDecomposer = TwoQubitWeylDecomposition::create( getTwoQubitMatrix(basisGate), basisFidelity); - auto superControlled = + const auto superControlled = relativeEq(basisDecomposer.a, qc::PI_4, 1e-13, 1e-09) && relativeEq(basisDecomposer.c, 0.0, 1e-13, 1e-09); @@ -1552,72 +1562,72 @@ struct GateDecompositionPattern final // expand as Ui = Ki1.Ubasis.Ki2 auto b = basisDecomposer.b; auto temp = qfp(0.5, -0.5); - matrix2x2 k11l{ + const matrix2x2 k11l{ {temp * (M_IM * std::exp(qfp(0., -b))), temp * std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * -std::exp(qfp(0., b))}}; - matrix2x2 k11r{{FRAC1_SQRT2 * (IM * std::exp(qfp(0., -b))), - FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, - {FRAC1_SQRT2 * std::exp(qfp(0., b)), - FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; - matrix2x2 k32lK21l{{FRAC1_SQRT2 * qfp(1., std::cos(2. * b)), - FRAC1_SQRT2 * (IM * std::sin(2. * b))}, - {FRAC1_SQRT2 * (IM * std::sin(2. * b)), - FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; + const matrix2x2 k11r{{FRAC1_SQRT2 * (IM * std::exp(qfp(0., -b))), + FRAC1_SQRT2 * -std::exp(qfp(0., -b))}, + {FRAC1_SQRT2 * std::exp(qfp(0., b)), + FRAC1_SQRT2 * (M_IM * std::exp(qfp(0., b)))}}; + const matrix2x2 k32lK21l{{FRAC1_SQRT2 * qfp(1., std::cos(2. * b)), + FRAC1_SQRT2 * (IM * std::sin(2. * b))}, + {FRAC1_SQRT2 * (IM * std::sin(2. * b)), + FRAC1_SQRT2 * qfp(1., -std::cos(2. * b))}}; temp = qfp(0.5, 0.5); - matrix2x2 k21r{ + const matrix2x2 k21r{ {temp * (M_IM * std::exp(qfp(0., -2. * b))), temp * std::exp(qfp(0., -2. * b))}, {temp * (IM * std::exp(qfp(0., 2. * b))), temp * std::exp(qfp(0., 2. * b))}, }; - const matrix2x2 k22LArr{ + const matrix2x2 k22l{ {qfp(FRAC1_SQRT2, 0.), qfp(-FRAC1_SQRT2, 0.)}, {qfp(FRAC1_SQRT2, 0.), qfp(FRAC1_SQRT2, 0.)}, }; - const matrix2x2 k22RArr{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; - matrix2x2 k31l{ + const matrix2x2 k22r{{C_ZERO, C_ONE}, {C_M_ONE, C_ZERO}}; + const matrix2x2 k31l{ {FRAC1_SQRT2 * std::exp(qfp(0., -b)), FRAC1_SQRT2 * std::exp(qfp(0., -b))}, {FRAC1_SQRT2 * -std::exp(qfp(0., b)), FRAC1_SQRT2 * std::exp(qfp(0., b))}, }; - matrix2x2 k31r{ + const matrix2x2 k31r{ {IM * std::exp(qfp(0., b)), C_ZERO}, {C_ZERO, M_IM * std::exp(qfp(0., -b))}, }; temp = qfp(0.5, 0.5); - matrix2x2 k32r{ + const matrix2x2 k32r{ {temp * std::exp(qfp(0., b)), temp * -std::exp(qfp(0., -b))}, {temp * (M_IM * std::exp(qfp(0., b))), temp * (M_IM * std::exp(qfp(0., -b)))}, }; - auto k1ld = basisDecomposer.k1l.transpose().conjugate(); - auto k1rd = basisDecomposer.k1r.transpose().conjugate(); - auto k2ld = basisDecomposer.k2l.transpose().conjugate(); - auto k2rd = basisDecomposer.k2r.transpose().conjugate(); + auto k1lDagger = basisDecomposer.k1l.transpose().conjugate(); + auto k1rDagger = basisDecomposer.k1r.transpose().conjugate(); + auto k2lDagger = basisDecomposer.k2l.transpose().conjugate(); + auto k2rDagger = basisDecomposer.k2r.transpose().conjugate(); // Pre-build the fixed parts of the matrices used in 3-part // decomposition - auto u0l = k31l * k1ld; - auto u0r = k31r * k1rd; - auto u1l = k2ld * k32lK21l * k1ld; - auto u1ra = k2rd * k32r; - auto u1rb = k21r * k1rd; - auto u2la = k2ld * k22LArr; - auto u2lb = k11l * k1ld; - auto u2ra = k2rd * k22RArr; - auto u2rb = k11r * k1rd; - auto u3l = k2ld * k12LArr; - auto u3r = k2rd * k12RArr; + auto u0l = k31l * k1lDagger; + auto u0r = k31r * k1rDagger; + auto u1l = k2lDagger * k32lK21l * k1lDagger; + auto u1ra = k2rDagger * k32r; + auto u1rb = k21r * k1rDagger; + auto u2la = k2lDagger * k22l; + auto u2lb = k11l * k1lDagger; + auto u2ra = k2rDagger * k22r; + auto u2rb = k11r * k1rDagger; + auto u3l = k2lDagger * k12LArr; + auto u3r = k2rDagger * k12RArr; // Pre-build the fixed parts of the matrices used in the 2-part // decomposition - auto q0l = k12LArr.transpose().conjugate() * k1ld; - auto q0r = k12RArr.transpose().conjugate() * IPZ * k1rd; - auto q1la = k2ld * k11l.transpose().conjugate(); - auto q1lb = k11l * k1ld; - auto q1ra = k2rd * IPZ * k11r.transpose().conjugate(); - auto q1rb = k11r * k1rd; - auto q2l = k2ld * k12LArr; - auto q2r = k2rd * k12RArr; + auto q0l = k12LArr.transpose().conjugate() * k1lDagger; + auto q0r = k12RArr.transpose().conjugate() * IPZ * k1rDagger; + auto q1la = k2lDagger * k11l.transpose().conjugate(); + auto q1lb = k11l * k1lDagger; + auto q1ra = k2rDagger * IPZ * k11r.transpose().conjugate(); + auto q1rb = k11r * k1rDagger; + auto q2l = k2lDagger * k12LArr; + auto q2r = k2rDagger * k12RArr; return TwoQubitBasisDecomposer{ .basisGate = basisGate, @@ -1678,7 +1688,7 @@ struct GateDecompositionPattern final auto getDefaultNbasis = [&]() { auto minValue = std::numeric_limits::min(); auto minIndex = -1; - for (int i = 0; i < static_cast(traces.size()); ++i) { + for (int i = 0; std::cmp_less(i, traces.size()); ++i) { // lower fidelity means it becomes easier to choose a lower number of // basis gates auto value = @@ -1713,7 +1723,7 @@ struct GateDecompositionPattern final for (auto&& decomp : decomposition) { assert(helpers::isUnitaryMatrix(decomp)); auto eulerDecomp = unitaryToGateSequenceInner( - decomp, target1qEulerBases, 0, {}, true, std::nullopt); + decomp, target1qEulerBases, 0, true, std::nullopt); eulerDecompositions.push_back(eulerDecomp); } TwoQubitGateSequence gates{ @@ -1931,8 +1941,8 @@ struct GateDecompositionPattern final [[nodiscard]] static OneQubitGateSequence unitaryToGateSequenceInner( const matrix2x2& unitaryMat, const llvm::SmallVector& targetBasisList, QubitId /*qubit*/, - const std::vector>& - /*error_map*/, // per qubit a mapping of operation name to error value + // TODO: add error map here: per qubit a mapping of operation to error + // value for better calculateError() bool simplify, std::optional atol) { auto calculateError = [](const OneQubitGateSequence& sequence) -> fp { return static_cast(sequence.complexity()); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index 1ab09dea1..bf00c80f4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -14,21 +14,32 @@ #include "ir/operations/OpType.hpp" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" -#include -#include +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) #include +#include +#include +#include +#include #include +#include #include -#include // TODO: unstable +#include +#include +#include +#include +#include // TODO: unstable, NOLINT(misc-include-cleaner) namespace mqt::ir::opt { using fp = qc::fp; using qfp = std::complex; +// NOLINTBEGIN(misc-include-cleaner) using matrix2x2 = Eigen::Matrix2; using matrix4x4 = Eigen::Matrix4; using rmatrix4x4 = Eigen::Matrix4; using diagonal4x4 = Eigen::Vector; using rdiagonal4x4 = Eigen::Vector; +// NOLINTEND(misc-include-cleaner) constexpr qfp C_ZERO{0., 0.}; constexpr qfp C_ONE{1., 0.}; @@ -145,7 +156,7 @@ getParameters(UnitaryInterface op) { [[nodiscard]] inline bool isSingleQubitOperation(UnitaryInterface op) { auto&& inQubits = op.getInQubits(); auto&& outQubits = op.getOutQubits(); - bool isSingleQubitOp = + const bool isSingleQubitOp = inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled(); return isSingleQubitOp; } @@ -161,10 +172,11 @@ getParameters(UnitaryInterface op) { auto&& outNegCtrlQubits = op.getNegCtrlInQubits(); auto outQubitSize = outQubits.size() + outPosCtrlQubits.size() + outNegCtrlQubits.size(); - bool isTwoQubitOp = inQubitSize == 2 && outQubitSize == 2; + const bool isTwoQubitOp = inQubitSize == 2 && outQubitSize == 2; return isTwoQubitOp; } +// NOLINTBEGIN(misc-include-cleaner) template inline Eigen::Matrix4 kroneckerProduct(const Eigen::Matrix2& lhs, const Eigen::Matrix2& rhs) { @@ -185,5 +197,6 @@ template isUnitaryMatrix(const Eigen::Matrix& matrix) { return (matrix.transpose().conjugate() * matrix).isIdentity(); } +// NOLINTEND(misc-include-cleaner) } // namespace mqt::ir::opt::helpers From 37b30e33d27b8f0ca494446d05dc5ea5058ed803 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 21 Nov 2025 19:15:55 +0100 Subject: [PATCH 100/100] incorporate coderabbitai feedback --- mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td | 3 +++ .../Dialect/MQTOpt/Transforms/GateDecomposition.cpp | 4 +++- .../MQTOpt/Transforms/GateDecompositionPattern.cpp | 2 ++ mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 10 +++++----- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 2540b796c..0f241bc5c 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -44,8 +44,11 @@ def GateElimination : Pass<"gate-elimination", "mlir::ModuleOp"> { } def GateDecomposition : Pass<"gate-decomposition", "mlir::ModuleOp"> { + let dependentDialects = [ "mlir::arith::ArithDialect", "mqt::ir::opt::MQTOptDialect" ]; let summary = "This pass performs various gate decompositions to translate quantum gates being used."; let description = [{ + Decomposes series of operations that operate on up to two qubits into a sequence of up to three + two-qubit basis gates and single-qubit operations. }]; } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index 7c60ab259..7db137855 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -21,7 +21,9 @@ namespace mqt::ir::opt { #include "mlir/Dialect/MQTOpt/Transforms/Passes.h.inc" /** - * @brief This pass attempts to cancel consecutive self-inverse operations. + * @brief This pass attempts to collect as many operations as possible into a + * 4x4 unitary matrix and then decompose it into 1q rotations and 2q + * basis gates. */ struct GateDecomposition final : impl::GateDecompositionBase { diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 35c0f1991..8a0d48607 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -926,6 +927,7 @@ struct GateDecompositionPattern final // TODO: it may be possible to lower the precision auto [p, d] = diagonalizeComplexSymmetric(m2, 1e-13); + // NOLINTNEXTLINE(misc-include-cleaner) Eigen::Vector cs; // weyl coordinates rdiagonal4x4 dReal = -1.0 * d.cwiseArg() / 2.0; dReal(3) = -dReal(0) - dReal(1) - dReal(2); diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index bf00c80f4..bb0fa3357 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -119,15 +119,15 @@ inline std::optional mlirValueToFp(mlir::Value value) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](fp a, fp b) { return a + b; })) { + value, [](fp a, fp b) { return a * b; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](fp a, fp b) { return a + b; })) { + value, [](fp a, fp b) { return a / b; })) { return result; } if (auto result = performMlirFloatBinaryOp( - value, [](fp a, fp b) { return a + b; })) { + value, [](fp a, fp b) { return a - b; })) { return result; } return std::nullopt; @@ -168,8 +168,8 @@ getParameters(UnitaryInterface op) { auto inQubitSize = inQubits.size() + inPosCtrlQubits.size() + inNegCtrlQubits.size(); auto&& outQubits = op.getOutQubits(); - auto&& outPosCtrlQubits = op.getPosCtrlInQubits(); - auto&& outNegCtrlQubits = op.getNegCtrlInQubits(); + auto&& outPosCtrlQubits = op.getPosCtrlOutQubits(); + auto&& outNegCtrlQubits = op.getNegCtrlOutQubits(); auto outQubitSize = outQubits.size() + outPosCtrlQubits.size() + outNegCtrlQubits.size(); const bool isTwoQubitOp = inQubitSize == 2 && outQubitSize == 2;