From b35a016b5129d72cc9f18e97c79e66c820917b2c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 5 Sep 2025 15:11:53 +0200 Subject: [PATCH 01/13] first snapshot --- .../mlir/Dialect/MQTOpt/Transforms/Passes.h | 1 + .../mlir/Dialect/MQTOpt/Transforms/Passes.td | 6 + .../MQTOpt/Transforms/GateDecomposition.cpp | 44 ++++ .../Transforms/GateDecompositionPattern.cpp | 249 ++++++++++++++++++ .../MQTOpt/Transforms/gate-decomposition.mlir | 15 ++ 5 files changed, 315 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/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h index 23ef1c3114..a00b5783d6 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h @@ -26,6 +26,7 @@ namespace mqt::ir::opt { #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 populateElidePermutationsPatterns(mlir::RewritePatternSet& patterns); void populateQuantumSinkShiftPatterns(mlir::RewritePatternSet& patterns); diff --git a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td index 48fbd4bd52..b8d56a4ed3 100644 --- a/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td +++ b/mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td @@ -35,6 +35,12 @@ def GateElimination : Pass<"gate-elimination", "mlir::ModuleOp"> { }]; } +def GateDecomposition : Pass<"gate-decomposition", "mlir::ModuleOp"> { + let summary = "This pass will perform various gate decompositions to simplify the used gate set."; + 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 = [{ diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp new file mode 100644 index 0000000000..3defc28010 --- /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/Common/Compat.h" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" + +#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(APPLY_PATTERNS_GREEDILY(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 0000000000..3ac368399a --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -0,0 +1,249 @@ +/* + * 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/IR/MQTOptDialect.h" +#include "mlir/Dialect/MQTOpt/Transforms/Passes.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mqt::ir::opt { + +/** + * @brief This pattern replaces all negative controls by negations combined with + * positively controlled operations. + */ +struct NegCtrlDecompositionPattern final + : mlir::OpInterfaceRewritePattern { + + explicit NegCtrlDecompositionPattern(mlir::MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + mlir::LogicalResult + matchAndRewrite(UnitaryInterface op, + mlir::PatternRewriter& rewriter) const override { + return mlir::failure(); + } +}; + +/** + * @brief This pattern TODO. + */ +struct EulerDecompositionPattern final + : mlir::OpInterfaceRewritePattern { + + explicit EulerDecompositionPattern(mlir::MLIRContext* context) + : OpInterfaceRewritePattern(context) {} + + mlir::LogicalResult + matchAndRewrite(UnitaryInterface op, + mlir::PatternRewriter& rewriter) const override { + if (!isSingleQubitOperation(op)) { + return mlir::failure(); + } + + auto series = getSingleQubitSeries(op); + + return mlir::failure(); + } + + [[nodiscard]] static llvm::SmallVector + getSingleQubitSeries(UnitaryInterface op) { + llvm::SmallVector result = {op}; + while (true) { + auto nextOp = getNextOperation(op); + if (isSingleQubitOperation(nextOp)) { + result.push_back(nextOp); + } else { + return result; + } + } + } + + [[nodiscard]] static bool isSingleQubitOperation(UnitaryInterface op) { + auto&& inQubits = op.getInQubits(); + auto&& outQubits = op.getOutQubits(); + return inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled(); + } + + [[nodiscard]] static UnitaryInterface getNextOperation(UnitaryInterface op) { + // since there is only one output qubit, there should only be one user + auto&& users = op->getUsers(); + assert(std::distance(users.begin(), users.end()) == 1); + return llvm::dyn_cast(*users.begin()); + } + + /** + * @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 createRotationGate(mlir::PatternRewriter& rewriter, + mlir::Value inQubit, qc::fp angle) { + auto location = inQubit.getLoc(); + auto qubitType = inQubit.getType(); + + auto angleValue = rewriter.create( + location, rewriter.getF64Type(), rewriter.getF64FloatAttr(angle)); + + return rewriter.create( + location, mlir::TypeRange{qubitType}, mlir::TypeRange{}, + mlir::TypeRange{}, mlir::DenseF64ArrayAttr{}, + mlir::DenseBoolArrayAttr{}, mlir::ValueRange{angleValue}, + mlir::ValueRange{inQubit}, mlir::ValueRange{}, mlir::ValueRange{}); + } + + [[nodiscard]] static llvm::SmallVector createMlirGates( + mlir::PatternRewriter& rewriter, + const llvm::SmallVector, 3>& schematic, + mlir::Value inQubit) { + llvm::SmallVector result; + for (auto [type, angle] : schematic) { + if (type == qc::RZ) { + auto newRz = createRotationGate(rewriter, inQubit, angle); + result.push_back(newRz); + } else if (type == qc::RY) { + auto newRy = createRotationGate(rewriter, inQubit, angle); + result.push_back(newRy); + } else { + throw std::logic_error{"Unable to create MLIR gate in Euler " + "Decomposition (unsupported gate)"}; + } + inQubit = result.back().getOutQubits().front(); + } + return result; + } + + /** + * @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 std::pair< + llvm::SmallVector, 3>, qc::fp> + calculateRotationGates(std::array unitaryMatrix) { + auto [lambda, theta, phi, phase] = paramsZyzInner(unitaryMatrix); + qc::fp globalPhase = phase - ((phi + lambda) / 2.); + constexpr qc::fp angleZeroEpsilon = 1e-12; + + auto remEuclid = [](qc::fp a, qc::fp b) { + auto r = std::fmod(a, b); + return (r < 0.0) ? r + std::abs(b) : r; + }; + 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; + }; + + llvm::SmallVector, 3> gates; + if (std::abs(theta) < angleZeroEpsilon) { + lambda += phi; + lambda = mod2pi(lambda); + if (std::abs(lambda) > angleZeroEpsilon) { + gates.push_back({qc::RZ, 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({qc::RZ, lambda}); + } + gates.push_back({qc::RY, theta}); + phi = mod2pi(phi); + if (std::abs(phi) > angleZeroEpsilon) { + globalPhase += phi / 2.0; + gates.push_back({qc::RZ, phi}); + } + return {gates, globalPhase}; + } + + /** + * @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 std::array + paramsZyzInner(std::array unitaryMatrix) { + auto getIndex = [](auto x, auto y) { return (x * 2) + y; }; + 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 = determinant(unitaryMatrix); + auto phase = 0.5 * detArg; + auto theta = 2. * std::atan2(std::abs(unitaryMatrix.at(getIndex(1, 0))), + std::abs(unitaryMatrix.at(getIndex(0, 0)))); + auto ang1 = unitaryMatrix.at(getIndex(1, 1)); + auto ang2 = unitaryMatrix.at(getIndex(1, 0)); + auto phi = ang1 + ang2 - detArg; + auto lam = ang1 - ang2; + return {theta, phi, lam, phase}; + } +}; + +/** + * @brief Populates the given pattern set with patterns for gate elimination. + * + * @param patterns The pattern set to populate. + */ +void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { + patterns.add(patterns.getContext()); + patterns.add(patterns.getContext()); +} + +} // namespace mqt::ir::opt 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 0000000000..7fff7648d6 --- /dev/null +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -0,0 +1,15 @@ +// 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 single-qubit consecutive self-inverses are canceled correctly. + +module { +} From 5523e4a1297c61162b051cfa9f5c20524d5ca02c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:44:52 +0000 Subject: [PATCH 02/13] =?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 --- mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index 3defc28010..43ea0e72c9 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. From 1f0ede18738205640d77835b8a8959b9dfbc4d8e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 10 Sep 2025 21:07:19 +0200 Subject: [PATCH 03/13] second snapshot --- .../Transforms/GateDecompositionPattern.cpp | 44 +++-- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 167 ++++++++++++++++++ 2 files changed, 198 insertions(+), 13 deletions(-) create mode 100644 mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 3ac368399a..24f750cba9 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -8,6 +8,7 @@ * Licensed under the MIT License */ +#include "Helpers.h" #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" @@ -52,11 +53,34 @@ struct EulerDecompositionPattern final mlir::LogicalResult matchAndRewrite(UnitaryInterface op, mlir::PatternRewriter& rewriter) const override { - if (!isSingleQubitOperation(op)) { + if (!helpers::isSingleQubitOperation(op)) { return mlir::failure(); } auto series = getSingleQubitSeries(op); + // TODO: find better stop condition? + if (series.size() < 4) { + // decomposing this series of single qubit gates would not reduce the + // number of gates + return mlir::failure(); + } + + dd::GateMatrix unitaryMatrix = dd::opToSingleQubitGateMatrix(qc::I); + for (auto&& gate : series) { + if (auto gateMatrix = helpers::getUnitaryMatrix(gate)) { + unitaryMatrix = helpers::ddMultiply(unitaryMatrix, *gateMatrix); + } + } + + auto decomposedGateSchematic = calculateRotationGates(unitaryMatrix); + auto newGates = createMlirGates(rewriter, decomposedGateSchematic.first, + op.getInQubits().front()); + if (newGates.empty()) { + return mlir::failure(); + } + + // vector cannot be empty since there is at least the current gate + rewriter.replaceAllOpUsesWith(series.back(), newGates.back()); return mlir::failure(); } @@ -66,7 +90,7 @@ struct EulerDecompositionPattern final llvm::SmallVector result = {op}; while (true) { auto nextOp = getNextOperation(op); - if (isSingleQubitOperation(nextOp)) { + if (helpers::isSingleQubitOperation(nextOp)) { result.push_back(nextOp); } else { return result; @@ -74,12 +98,6 @@ struct EulerDecompositionPattern final } } - [[nodiscard]] static bool isSingleQubitOperation(UnitaryInterface op) { - auto&& inQubits = op.getInQubits(); - auto&& outQubits = op.getOutQubits(); - return inQubits.size() == 1 && outQubits.size() == 1 && !op.isControlled(); - } - [[nodiscard]] static UnitaryInterface getNextOperation(UnitaryInterface op) { // since there is only one output qubit, there should only be one user auto&& users = op->getUsers(); @@ -147,7 +165,7 @@ struct EulerDecompositionPattern final */ [[nodiscard]] static std::pair< llvm::SmallVector, 3>, qc::fp> - calculateRotationGates(std::array unitaryMatrix) { + calculateRotationGates(dd::GateMatrix unitaryMatrix) { auto [lambda, theta, phi, phase] = paramsZyzInner(unitaryMatrix); qc::fp globalPhase = phase - ((phi + lambda) / 2.); constexpr qc::fp angleZeroEpsilon = 1e-12; @@ -217,19 +235,19 @@ struct EulerDecompositionPattern final * indicating that they have been altered from the originals. */ [[nodiscard]] static std::array - paramsZyzInner(std::array unitaryMatrix) { + paramsZyzInner(dd::GateMatrix unitaryMatrix) { auto getIndex = [](auto x, auto y) { return (x * 2) + y; }; 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 = determinant(unitaryMatrix); + auto detArg = std::arg(determinant(unitaryMatrix)); auto phase = 0.5 * detArg; auto theta = 2. * std::atan2(std::abs(unitaryMatrix.at(getIndex(1, 0))), std::abs(unitaryMatrix.at(getIndex(0, 0)))); - auto ang1 = unitaryMatrix.at(getIndex(1, 1)); - auto ang2 = unitaryMatrix.at(getIndex(1, 0)); + auto ang1 = std::arg(unitaryMatrix.at(getIndex(1, 1))); + auto ang2 = std::arg(unitaryMatrix.at(getIndex(1, 0))); auto phi = ang1 + ang2 - detArg; auto lam = ang1 - ang2; return {theta, phi, lam, phase}; diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h new file mode 100644 index 0000000000..da4e2a84d3 --- /dev/null +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -0,0 +1,167 @@ +/* + * 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 std::optional +getUnitaryMatrix(UnitaryInterface op) { + auto type = getQcType(op); + auto parameters = getParameters(op); + + if (isSingleQubitOperation(op)) { + return dd::opToSingleQubitGateMatrix(type, parameters); + } + return std::nullopt; +} + +template +[[nodiscard]] inline TargetType into(const dd::CMat& inMatrix) { + TargetType result; + std::size_t startIndex{0}; + for (auto&& matrixRow : inMatrix) { + assert(startIndex + matrixRow.size() <= result.size()); + std::ranges::copy(matrixRow, result.begin() + startIndex); + startIndex += matrixRow.size(); + } + return result; +} + +[[nodiscard]] inline dd::GateMatrix ddMultiply(dd::GateMatrix lhs, + dd::GateMatrix rhs) { + dd::Package p; + auto&& lhsDD = p.makeGateDD(lhs, 0); + auto&& rhsDD = p.makeGateDD(rhs, 0); + auto&& product = p.multiply(lhsDD, rhsDD); + auto&& productMatrix = product.getMatrix(1); + + return into(productMatrix); +} +} // namespace mqt::ir::opt::helpers From 53c3d5f1f1f239a18d56ed04069faecd41394866 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 10 Sep 2025 21:49:09 +0200 Subject: [PATCH 04/13] link against CoreDD --- mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt b/mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt index 5e86b17e08..0d196e92a6 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 TRANSFORMS_SOURCES *.cpp) From c7b243ef828aa9274496f0dc2225c710d15f9e11 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 10 Sep 2025 21:51:50 +0200 Subject: [PATCH 05/13] manual 2x2 matrix multiplication --- .../Transforms/GateDecompositionPattern.cpp | 15 ++++++----- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 27 +++++-------------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 24f750cba9..3fd7408164 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -68,7 +68,7 @@ struct EulerDecompositionPattern final dd::GateMatrix unitaryMatrix = dd::opToSingleQubitGateMatrix(qc::I); for (auto&& gate : series) { if (auto gateMatrix = helpers::getUnitaryMatrix(gate)) { - unitaryMatrix = helpers::ddMultiply(unitaryMatrix, *gateMatrix); + unitaryMatrix = helpers::multiply(unitaryMatrix, *gateMatrix); } } @@ -82,7 +82,7 @@ struct EulerDecompositionPattern final // vector cannot be empty since there is at least the current gate rewriter.replaceAllOpUsesWith(series.back(), newGates.back()); - return mlir::failure(); + return mlir::success(); } [[nodiscard]] static llvm::SmallVector @@ -90,18 +90,21 @@ struct EulerDecompositionPattern final llvm::SmallVector result = {op}; while (true) { auto nextOp = getNextOperation(op); - if (helpers::isSingleQubitOperation(nextOp)) { + if (nextOp && helpers::isSingleQubitOperation(nextOp)) { result.push_back(nextOp); } else { - return result; + break; } } + return result; } [[nodiscard]] static UnitaryInterface getNextOperation(UnitaryInterface op) { - // since there is only one output qubit, there should only be one user + // since there is only one output qubit in single qubit gates, there should + // only be one user + assert(op->hasOneUse()); + llvm::errs() << &op << '\n'; auto&& users = op->getUsers(); - assert(std::distance(users.begin(), users.end()) == 1); return llvm::dyn_cast(*users.begin()); } diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index da4e2a84d3..ca1963b70a 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -142,26 +142,13 @@ getUnitaryMatrix(UnitaryInterface op) { return std::nullopt; } -template -[[nodiscard]] inline TargetType into(const dd::CMat& inMatrix) { - TargetType result; - std::size_t startIndex{0}; - for (auto&& matrixRow : inMatrix) { - assert(startIndex + matrixRow.size() <= result.size()); - std::ranges::copy(matrixRow, result.begin() + startIndex); - startIndex += matrixRow.size(); - } - return result; -} - -[[nodiscard]] inline dd::GateMatrix ddMultiply(dd::GateMatrix lhs, +[[nodiscard]] inline dd::GateMatrix multiply(dd::GateMatrix lhs, dd::GateMatrix rhs) { - dd::Package p; - auto&& lhsDD = p.makeGateDD(lhs, 0); - auto&& rhsDD = p.makeGateDD(rhs, 0); - auto&& product = p.multiply(lhsDD, rhsDD); - auto&& productMatrix = product.getMatrix(1); - - return into(productMatrix); + return { + lhs.at(0) * rhs.at(0) + lhs.at(1) * rhs.at(2), + lhs.at(0) * rhs.at(1) + lhs.at(1) * rhs.at(3), + lhs.at(2) * rhs.at(0) + lhs.at(3) * rhs.at(2), + lhs.at(2) * rhs.at(1) + lhs.at(3) * rhs.at(3), + }; } } // namespace mqt::ir::opt::helpers From c22408a9f14fe18dfa2b97a54e95728d1ba9a708 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Wed, 10 Sep 2025 21:55:06 +0200 Subject: [PATCH 06/13] fix --- .../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 3fd7408164..7e5076a970 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -89,9 +89,9 @@ struct EulerDecompositionPattern final getSingleQubitSeries(UnitaryInterface op) { llvm::SmallVector result = {op}; while (true) { - auto nextOp = getNextOperation(op); - if (nextOp && helpers::isSingleQubitOperation(nextOp)) { - result.push_back(nextOp); + op = getNextOperation(op); + if (op && helpers::isSingleQubitOperation(op)) { + result.push_back(op); } else { break; } From 3357c02607b199869ee88ee364acbe4bdebae1f7 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 11 Sep 2025 11:22:11 +0200 Subject: [PATCH 07/13] minor fix and simple tests --- .../Transforms/GateDecompositionPattern.cpp | 3 +- .../MQTOpt/Transforms/gate-decomposition.mlir | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 7e5076a970..4656ceadc2 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -88,7 +88,7 @@ struct EulerDecompositionPattern final [[nodiscard]] static llvm::SmallVector getSingleQubitSeries(UnitaryInterface op) { llvm::SmallVector result = {op}; - while (true) { + while (op->hasOneUse()) { op = getNextOperation(op); if (op && helpers::isSingleQubitOperation(op)) { result.push_back(op); @@ -103,7 +103,6 @@ struct EulerDecompositionPattern final // since there is only one output qubit in single qubit gates, there should // only be one user assert(op->hasOneUse()); - llvm::errs() << &op << '\n'; auto&& users = op->getUsers(); return llvm::dyn_cast(*users.begin()); } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 7fff7648d6..64e0c7ba64 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -12,4 +12,57 @@ // This test checks if single-qubit consecutive self-inverses are canceled correctly. module { + // CHECK-LABEL: func.func @testNegationSeries + func.func @testNegationSeries() { + // CHECK: %[[Q0_0]] = mqtopt.allocQubit + + // CHECK: %[[C_0:.*]] = arith.constant 0.000000e+00 : f64 + // CHECK: %[[Q0_1:.*]] = mqtopt.rz + + // CHECK-NOT: %[[ANY:.*]] = mqtopt.x + + // CHECK: mqtopt.deallocQubit %[[Q0_1]] + + %q0_0 = mqtopt.allocQubit + + %q0_1 = mqtopt.x() %q0_0 : !mqtopt.Qubit + %q0_2 = mqtopt.x() %q0_1 : !mqtopt.Qubit + %q0_3 = mqtopt.x() %q0_2 : !mqtopt.Qubit + %q0_4 = mqtopt.x() %q0_3 : !mqtopt.Qubit + %q0_5 = mqtopt.x() %q0_4 : !mqtopt.Qubit + %q0_6 = mqtopt.x() %q0_5 : !mqtopt.Qubit + + mqtopt.deallocQubit %q0_6 + + return + } +} + + +module { + // CHECK-LABEL: func.func @testNoMergeSmallSeries + func.func @testNoMergeSmallSeries() { + // CHECK: %[[Q0_0]] = mqtopt.allocQubit + + // CHECK: %[[C_0:.*]] = arith.constant 3.000000e+00 : f64 + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_3:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + + // CHECK-NOT: %[[ANY:.*]] = mqtopt.rz + // CHECK-NOT: %[[ANY:.*]] = mqtopt.ry + + // CHECK: mqtopt.deallocQubit %[[Q0_3]] + + %q0_0 = mqtopt.allocQubit + + %c_0 = arith.constant 1.000000e+00 : f64 + %q0_1 = mqtopt.rx(%c_0) %q0_0 : !mqtopt.Qubit + %q0_2 = mqtopt.rz(%c_0) %q0_1 : !mqtopt.Qubit + %q0_3 = mqtopt.ry(%c_0) %q0_2 : !mqtopt.Qubit + + mqtopt.deallocQubit %q0_3 + + return + } } From fdfc4921e86371641c9a9d0f11893cd980458c12 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 11 Sep 2025 14:01:01 +0200 Subject: [PATCH 08/13] fix erase --- .../Transforms/GateDecompositionPattern.cpp | 19 +++++++----- .../MQTOpt/Transforms/gate-decomposition.mlir | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 4656ceadc2..57134e1c8f 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -58,10 +58,8 @@ struct EulerDecompositionPattern final } auto series = getSingleQubitSeries(op); - // TODO: find better stop condition? - if (series.size() < 4) { - // decomposing this series of single qubit gates would not reduce the - // number of gates + if (series.size() <= 3) { + // TODO: find better way to prevent endless optimization loop return mlir::failure(); } @@ -75,12 +73,17 @@ struct EulerDecompositionPattern final auto decomposedGateSchematic = calculateRotationGates(unitaryMatrix); auto newGates = createMlirGates(rewriter, decomposedGateSchematic.first, op.getInQubits().front()); - if (newGates.empty()) { - return mlir::failure(); + if (!newGates.empty()) { + rewriter.replaceAllOpUsesWith(series.back(), newGates.back()); + } else { + rewriter.replaceAllOpUsesWith(series.back(), op->getOperands()); } - // vector cannot be empty since there is at least the current gate - rewriter.replaceAllOpUsesWith(series.back(), newGates.back()); + // delete in reverse order since last use has been replaced and for the + // others the only use will be deleted before the operation + for (auto&& gate : llvm::reverse(series)) { + rewriter.eraseOp(gate); + } return mlir::success(); } diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 64e0c7ba64..25518791ae 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -39,6 +39,36 @@ module { } +module { + // CHECK-LABEL: func.func @testMergeRotationSeries + func.func @testMergeRotationSeries() { + // CHECK: %[[Q0_0]] = mqtopt.allocQubit + + // CHECK: %[[C_0:.*]] = arith.constant 3.000000e+00 : f64 + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_3:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + + // CHECK-NOT: %[[ANY:.*]] = mqtopt.rz + // CHECK-NOT: %[[ANY:.*]] = mqtopt.ry + + // CHECK: mqtopt.deallocQubit %[[Q0_3]] + + %q0_0 = mqtopt.allocQubit + + %c_0 = arith.constant 1.000000e+00 : f64 + %q0_1 = mqtopt.rx(%c_0) %q0_0 : !mqtopt.Qubit + %q0_2 = mqtopt.rz(%c_0) %q0_1 : !mqtopt.Qubit + %q0_3 = mqtopt.ry(%c_0) %q0_2 : !mqtopt.Qubit + %q0_4 = mqtopt.ry(%c_0) %q0_3 : !mqtopt.Qubit + + mqtopt.deallocQubit %q0_4 + + return + } +} + + module { // CHECK-LABEL: func.func @testNoMergeSmallSeries func.func @testNoMergeSmallSeries() { From ac48a9398c4149932f65f8a782fe533c5165e55f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 11 Sep 2025 15:26:58 +0200 Subject: [PATCH 09/13] first working version --- .../Transforms/GateDecompositionPattern.cpp | 46 +++++++++++------- .../MQTOpt/Transforms/gate-decomposition.mlir | 47 +++++++++++-------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 57134e1c8f..4e8cfc2cb8 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -70,12 +70,19 @@ struct EulerDecompositionPattern final } } - auto decomposedGateSchematic = calculateRotationGates(unitaryMatrix); - auto newGates = createMlirGates(rewriter, decomposedGateSchematic.first, + auto [decomposedGateSchematic, globalPhase] = + calculateRotationGates(unitaryMatrix); + + // apply global phase + createOneParameterGate(rewriter, op->getLoc(), globalPhase, {}); + + auto newGates = createMlirGates(rewriter, decomposedGateSchematic, op.getInQubits().front()); if (!newGates.empty()) { + // attach new gates by replacing the uses of the last gate of the series rewriter.replaceAllOpUsesWith(series.back(), newGates.back()); } else { + // gate series is equal to identity; remove it entirely rewriter.replaceAllOpUsesWith(series.back(), op->getOperands()); } @@ -119,19 +126,18 @@ struct EulerDecompositionPattern final * @return A new rotation gate. */ template - static OpType createRotationGate(mlir::PatternRewriter& rewriter, - mlir::Value inQubit, qc::fp angle) { - auto location = inQubit.getLoc(); - auto qubitType = inQubit.getType(); - - auto angleValue = rewriter.create( - location, rewriter.getF64Type(), rewriter.getF64FloatAttr(angle)); + 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, mlir::TypeRange{qubitType}, mlir::TypeRange{}, - mlir::TypeRange{}, mlir::DenseF64ArrayAttr{}, - mlir::DenseBoolArrayAttr{}, mlir::ValueRange{angleValue}, - mlir::ValueRange{inQubit}, mlir::ValueRange{}, mlir::ValueRange{}); + location, inQubits.getType(), mlir::TypeRange{}, mlir::TypeRange{}, + mlir::DenseF64ArrayAttr{}, mlir::DenseBoolArrayAttr{}, + mlir::ValueRange{parameterValue}, inQubits, mlir::ValueRange{}, + mlir::ValueRange{}); } [[nodiscard]] static llvm::SmallVector createMlirGates( @@ -141,10 +147,12 @@ struct EulerDecompositionPattern final llvm::SmallVector result; for (auto [type, angle] : schematic) { if (type == qc::RZ) { - auto newRz = createRotationGate(rewriter, inQubit, angle); + auto newRz = createOneParameterGate(rewriter, inQubit.getLoc(), + angle, {inQubit}); result.push_back(newRz); } else if (type == qc::RY) { - auto newRy = createRotationGate(rewriter, inQubit, angle); + auto newRy = createOneParameterGate(rewriter, inQubit.getLoc(), + angle, {inQubit}); result.push_back(newRy); } else { throw std::logic_error{"Unable to create MLIR gate in Euler " @@ -171,14 +179,13 @@ struct EulerDecompositionPattern final [[nodiscard]] static std::pair< llvm::SmallVector, 3>, qc::fp> calculateRotationGates(dd::GateMatrix unitaryMatrix) { - auto [lambda, theta, phi, phase] = paramsZyzInner(unitaryMatrix); - qc::fp globalPhase = phase - ((phi + lambda) / 2.); constexpr qc::fp angleZeroEpsilon = 1e-12; 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 @@ -190,6 +197,9 @@ struct EulerDecompositionPattern final return wrapped; }; + auto [theta, phi, lambda, phase] = paramsZyzInner(unitaryMatrix); + qc::fp globalPhase = phase - ((phi + lambda) / 2.); + llvm::SmallVector, 3> gates; if (std::abs(theta) < angleZeroEpsilon) { lambda += phi; @@ -241,7 +251,7 @@ struct EulerDecompositionPattern final */ [[nodiscard]] static std::array paramsZyzInner(dd::GateMatrix unitaryMatrix) { - auto getIndex = [](auto x, auto y) { return (x * 2) + y; }; + 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))); diff --git a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir index 25518791ae..1e23a5d70f 100644 --- a/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir +++ b/mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir @@ -14,14 +14,16 @@ module { // CHECK-LABEL: func.func @testNegationSeries func.func @testNegationSeries() { - // CHECK: %[[Q0_0]] = mqtopt.allocQubit + // CHECK: %[[ANY:.*]] = arith.constant 0.000000e+00 - // CHECK: %[[C_0:.*]] = arith.constant 0.000000e+00 : f64 - // CHECK: %[[Q0_1:.*]] = mqtopt.rz + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + + // CHECK: %[[Q0_1:.*]] = mqtopt.x() %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.x() %[[Q0_1]] // CHECK-NOT: %[[ANY:.*]] = mqtopt.x - // CHECK: mqtopt.deallocQubit %[[Q0_1]] + // CHECK: mqtopt.deallocQubit %[[Q0_2]] %q0_0 = mqtopt.allocQubit @@ -38,16 +40,20 @@ module { } } - module { // CHECK-LABEL: func.func @testMergeRotationSeries func.func @testMergeRotationSeries() { - // CHECK: %[[Q0_0]] = mqtopt.allocQubit + // CHECK: %[[C_0:.*]] = arith.constant 1.5707963267948966 + // CHECK: %[[C_1:.*]] = arith.constant 2.2831853071795867 + // CHECK: %[[C_2:.*]] = arith.constant -1.5707963267948966 + // CHECK: %[[C_3:.*]] = arith.constant -3.1415926535897931 - // CHECK: %[[C_0:.*]] = arith.constant 3.000000e+00 : f64 - // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit - // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit - // CHECK: %[[Q0_3:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit + + // CHECK: mqtopt.gphase(%[[C_3]]) + // CHECK: %[[Q0_1:.*]] = mqtopt.rz(%[[C_2]]) %[[Q0_0]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C_1]]) %[[Q0_1]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C_0]]) %[[Q0_2]] // CHECK-NOT: %[[ANY:.*]] = mqtopt.rz // CHECK-NOT: %[[ANY:.*]] = mqtopt.ry @@ -58,9 +64,9 @@ module { %c_0 = arith.constant 1.000000e+00 : f64 %q0_1 = mqtopt.rx(%c_0) %q0_0 : !mqtopt.Qubit - %q0_2 = mqtopt.rz(%c_0) %q0_1 : !mqtopt.Qubit - %q0_3 = mqtopt.ry(%c_0) %q0_2 : !mqtopt.Qubit - %q0_4 = mqtopt.ry(%c_0) %q0_3 : !mqtopt.Qubit + %q0_2 = mqtopt.rx(%c_0) %q0_1 : !mqtopt.Qubit + %q0_3 = mqtopt.rx(%c_0) %q0_2 : !mqtopt.Qubit + %q0_4 = mqtopt.rx(%c_0) %q0_3 : !mqtopt.Qubit mqtopt.deallocQubit %q0_4 @@ -72,12 +78,13 @@ module { module { // CHECK-LABEL: func.func @testNoMergeSmallSeries func.func @testNoMergeSmallSeries() { - // CHECK: %[[Q0_0]] = mqtopt.allocQubit + // CHECK: %[[C_0:.*]] = arith.constant 1.000000e+00 + + // CHECK: %[[Q0_0:.*]] = mqtopt.allocQubit - // CHECK: %[[C_0:.*]] = arith.constant 3.000000e+00 : f64 - // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit - // CHECK: %[[Q0_2:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit - // CHECK: %[[Q0_3:.*]] = mqtopt.rx(%[[C_0]]) %[[ANY:.*]] : !mqtopt.Qubit + // CHECK: %[[Q0_1:.*]] = mqtopt.rx(%[[C_0]]) %[[Q0_0:.*]] + // CHECK: %[[Q0_2:.*]] = mqtopt.ry(%[[C_0]]) %[[Q0_1:.*]] + // CHECK: %[[Q0_3:.*]] = mqtopt.rz(%[[C_0]]) %[[Q0_2:.*]] // CHECK-NOT: %[[ANY:.*]] = mqtopt.rz // CHECK-NOT: %[[ANY:.*]] = mqtopt.ry @@ -88,8 +95,8 @@ module { %c_0 = arith.constant 1.000000e+00 : f64 %q0_1 = mqtopt.rx(%c_0) %q0_0 : !mqtopt.Qubit - %q0_2 = mqtopt.rz(%c_0) %q0_1 : !mqtopt.Qubit - %q0_3 = mqtopt.ry(%c_0) %q0_2 : !mqtopt.Qubit + %q0_2 = mqtopt.ry(%c_0) %q0_1 : !mqtopt.Qubit + %q0_3 = mqtopt.rz(%c_0) %q0_2 : !mqtopt.Qubit mqtopt.deallocQubit %q0_3 From 7fddcb580846245769488f1a5bb40f303e8edf64 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:27:23 +0000 Subject: [PATCH 10/13] =?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/GateDecompositionPattern.cpp | 3 ++- mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index 4e8cfc2cb8..e3704c0610 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -185,7 +185,8 @@ struct EulerDecompositionPattern final 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 -π + // 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 diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h index ca1963b70a..80a76d28f4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h +++ b/mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h @@ -145,10 +145,10 @@ getUnitaryMatrix(UnitaryInterface op) { [[nodiscard]] inline dd::GateMatrix multiply(dd::GateMatrix lhs, dd::GateMatrix rhs) { return { - lhs.at(0) * rhs.at(0) + lhs.at(1) * rhs.at(2), - lhs.at(0) * rhs.at(1) + lhs.at(1) * rhs.at(3), - lhs.at(2) * rhs.at(0) + lhs.at(3) * rhs.at(2), - lhs.at(2) * rhs.at(1) + lhs.at(3) * rhs.at(3), + lhs.at(0) * rhs.at(0) + lhs.at(1) * rhs.at(2), + lhs.at(0) * rhs.at(1) + lhs.at(1) * rhs.at(3), + lhs.at(2) * rhs.at(0) + lhs.at(3) * rhs.at(2), + lhs.at(2) * rhs.at(1) + lhs.at(3) * rhs.at(3), }; } } // namespace mqt::ir::opt::helpers From 46f2aa52929bba6a84dc96b6e4cf33e85f2ff27c Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 11 Sep 2025 15:44:14 +0200 Subject: [PATCH 11/13] fix compilation --- mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp index 43ea0e72c9..a612aca957 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp @@ -8,11 +8,11 @@ * Licensed under the MIT License */ -#include "mlir/Dialect/Common/Compat.h" #include "mlir/Dialect/MQTOpt/Transforms/Passes.h" #include #include +#include #include namespace mqt::ir::opt { @@ -36,7 +36,7 @@ struct GateDecomposition final populateGateDecompositionPatterns(patterns); // Apply patterns in an iterative and greedy manner. - if (mlir::failed(APPLY_PATTERNS_GREEDILY(op, std::move(patterns)))) { + if (mlir::failed(mlir::applyPatternsGreedily(op, std::move(patterns)))) { signalPassFailure(); } } From bfe102b562d27081cf6e14959851903be8d99746 Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Thu, 11 Sep 2025 15:45:46 +0200 Subject: [PATCH 12/13] remove unimplemented NegCtrlDecompositionPattern --- .../Transforms/GateDecompositionPattern.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index e3704c0610..d952ddd139 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -23,24 +23,6 @@ #include namespace mqt::ir::opt { - -/** - * @brief This pattern replaces all negative controls by negations combined with - * positively controlled operations. - */ -struct NegCtrlDecompositionPattern final - : mlir::OpInterfaceRewritePattern { - - explicit NegCtrlDecompositionPattern(mlir::MLIRContext* context) - : OpInterfaceRewritePattern(context) {} - - mlir::LogicalResult - matchAndRewrite(UnitaryInterface op, - mlir::PatternRewriter& rewriter) const override { - return mlir::failure(); - } -}; - /** * @brief This pattern TODO. */ From 1a39eb643ba0c29aa3c9c0cc23c069f25b073b7e Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Fri, 12 Sep 2025 17:34:16 +0200 Subject: [PATCH 13/13] fix NegCtrlDecompositionPattern removal --- mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp index d952ddd139..ec950c25d4 100644 --- a/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp +++ b/mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp @@ -258,7 +258,6 @@ struct EulerDecompositionPattern final * @param patterns The pattern set to populate. */ void populateGateDecompositionPatterns(mlir::RewritePatternSet& patterns) { - patterns.add(patterns.getContext()); patterns.add(patterns.getContext()); }