Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include "GateKind.h"

#include <llvm/ADT/SmallVector.h>
#include <mlir/Support/LLVM.h>

#include <cstdint>

namespace mlir::qco::decomposition {
/**
* Default absolute tolerance used to treat small Euler angles as zero during
* simplification.
*/
inline constexpr auto DEFAULT_ATOL = 1e-12;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already define a default tolerance in mlir/include/mlir/Dialect/Utils/Utils.h. Does it make sense to define a new one here?


/**
* Supported single-qubit Euler-style output bases.
*
* The listed values describe the gate alphabet that `EulerDecomposition`
* targets when converting a 2x2 unitary into a `OneQubitGateSequence`.
* Several entries share the angle-extraction routine and only differ in how
* the final circuit is emitted (e.g. `U3` vs `U321`, or `ZSX` vs `ZSXX`).
*/
enum class EulerBasis : std::uint8_t {
U3 = 0, ///< Single `u(theta, phi, lambda)` gate.
U321 = 1, ///< `u1`/`u2`/`u3` family — picks the smallest form per angles.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to keep things simple, let's drop the U321 decomposition. I doubt it will find applications in practice.

U = 2, ///< Same ZYZ angle extraction as `U3`, emitted as a single `u`.
Comment on lines +36 to +38
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

U3 and U are just the same aren't they? Can we remove one?

ZYZ = 3, ///< `rz · ry · rz`.
ZXZ = 4, ///< `rz · rx · rz`.
XZX = 5, ///< `rx · rz · rx`.
XYX = 6, ///< `rx · ry · rx`.
ZSXX = 7, ///< `rz · sx` chain, with `sx · rz(+/- pi) · sx` collapsed to `x`.
ZSX = 8, ///< Like `ZSXX` but without the `x` shortcut.
Comment on lines +43 to +44
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These docstrings feel off compared to the rest.

};

/**
* Return the gate types that may appear in a circuit emitted for `eulerBasis`.
*
* The result describes the basis alphabet, not the exact gate count. Some
* decompositions emit fewer than three gates after simplification.
*/
[[nodiscard]] SmallVector<GateKind>
getGateTypesForEulerBasis(EulerBasis eulerBasis);
Comment on lines +47 to +54
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function feels like it is not necessary now and depending on the resolution for the other comments, might not be necessary at all.


} // namespace mlir::qco::decomposition
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include "EulerBasis.h"
#include "GateSequence.h"

#include <Eigen/Core>

#include <array>
#include <optional>

namespace mlir::qco::decomposition {

/**
* Decompose a single-qubit unitary into a selected Euler-style gate basis.
*
* The returned sequence tracks both the emitted gates and the scalar phase
* needed to reconstruct the input matrix exactly. This is stronger than the
* usual "up to global phase" contract and is relied on by downstream
* canonicalization and testing code.
*/
class EulerDecomposition {
public:
/**
* Decompose a 2x2 unitary into the gate alphabet described by
* `targetBasis`.
*
* When `simplify` is true, near-zero angles are removed using `atol` (or
* `DEFAULT_ATOL` if no override is provided). The returned global phase keeps
* the decomposition exactly equal to `unitaryMatrix`.
*/
[[nodiscard]] static OneQubitGateSequence
generateCircuit(EulerBasis targetBasis, const Eigen::Matrix2cd& unitaryMatrix,
bool simplify, std::optional<double> atol);
Comment on lines +33 to +43
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help but feel that this should be a pattern rewrite rule. This doesn't really feel "MLIR" yet.


/**
* Extract canonical Euler parameters for `matrix` in the requested basis.
*
* Some target bases reuse the same parameter extraction routine and differ
* only during circuit emission. The returned array always contains
* `(theta, phi, lambda, phase)` in this order.
*/
[[nodiscard]] static std::array<double, 4>
anglesFromUnitary(const Eigen::Matrix2cd& matrix, EulerBasis basis);

private:
/// Extract parameters for a `rz(phi) · ry(theta) · rz(lambda)` factorization.
[[nodiscard]] static std::array<double, 4>
paramsZyz(const Eigen::Matrix2cd& matrix);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should functions like these be stylized as paramsZYZ? The current capitalization looks unusual and a bit odd.


/// Extract parameters for a `rz(phi) · rx(theta) · rz(lambda)` factorization.
[[nodiscard]] static std::array<double, 4>
paramsZxz(const Eigen::Matrix2cd& matrix);

/// Extract parameters for a `rx(phi) · ry(theta) · rx(lambda)` factorization.
[[nodiscard]] static std::array<double, 4>
paramsXyx(const Eigen::Matrix2cd& matrix);

/// Extract parameters for a `rx(phi) · rz(theta) · rx(lambda)` factorization.
[[nodiscard]] static std::array<double, 4>
paramsXzx(const Eigen::Matrix2cd& matrix);

/**
* Extract parameters for a `u1`/`p` + `sx` factorization.
*
* The returned angles are identical to `paramsZyz` but the phase is shifted
* by `-0.5 * (theta + phi + lambda)` so that the `rz`/`sx` circuits emitted
* by `decomposePsxGen` match the input matrix exactly (not only up to a
* global phase).
*
* @note Adapted from `params_u1x_inner` 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
* https://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<double, 4>
paramsU1x(const Eigen::Matrix2cd& matrix);

/**
* Emit a K-A-K circuit from already extracted Euler parameters.
*
* `kGate` is used for the outer rotations and `aGate` for the middle
* rotation.
*
* @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
* https://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
decomposeKAK(double theta, double phi, double lambda, double phase,
GateKind kGate, GateKind aGate, bool simplify,
std::optional<double> atol);

/**
* Emit an `rz`/`sx`-style circuit for the `ZSX` and `ZSXX` bases.
*
* The emitted sequence is structurally identical to the one produced by
* Qiskit's `circuit_psx_gen`. When `simplify` is enabled the number of `sx`
* gates shrinks based on `theta`: zero `sx` gates for `theta ~= 0`, one
* `sx` gate for `theta ~= pi/2`, and two `sx` gates otherwise.
*
* When `allowXShortcut` is true (i.e. for `ZSXX`), the general-case 2-`sx`
* path additionally collapses `sx · rz(+/- pi) · sx` into a single `x`
* gate when the middle rotation is congruent to +/- pi modulo 2 pi.
*
* @note Adapted from `circuit_psx_gen` 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
* https://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
decomposePsxGen(double theta, double phi, double lambda, double phase,
bool allowXShortcut, bool simplify,
std::optional<double> atol);
Comment on lines +142 to +145
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other parts of the library, the tolerance is passed as a double with a suitable default. The interfaces should be consistent across the library. I'd have a personal preference for avoiding optional here.

};
} // namespace mlir::qco::decomposition
42 changes: 42 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/Gate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include "GateKind.h"

#include <llvm/ADT/SmallVector.h>
#include <mlir/Support/LLVM.h>

#include <cstddef>

namespace mlir::qco::decomposition {

using QubitId = std::size_t;

/**
* Lightweight decomposition-time gate record.
*
* This struct is intentionally independent from MLIR operations so helper code
* can build and manipulate abstract one- and two-qubit circuits before they
* are materialized back into the IR.
*/
struct Gate {
/// Operation kind represented by this gate.
GateKind type{GateKind::I};

/// Gate parameters in operation-specific order.
SmallVector<double> parameter;

/// Logical qubit ids used by the gate, in operand order.
SmallVector<QubitId> qubitId = {0};
};
Comment on lines +24 to +40
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have hoped that we can implement the decomposition logic itself without adding a dedicated class that duplicates information.
At least for the single-qubit decomposition I don't really see why this would be necessary.
Is this really so much more compact than the respective gate operations?


} // namespace mlir::qco::decomposition
44 changes: 44 additions & 0 deletions mlir/include/mlir/Dialect/QCO/Transforms/Decomposition/GateKind.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include <cstdint>

namespace mlir::qco::decomposition {

/**
* Lightweight gate identifiers used by decomposition utilities.
*
* These kinds intentionally stay independent from the core IR `qc::OpType`
* enum so the MLIR/QCO decomposition layer does not depend on the `ir`
* package.
*/
enum class GateKind : std::uint8_t {
I = 0,
H,
P,
U,
U2,
X,
Y,
Z,
SX,
RX,
RY,
RZ,
R,
RXX,
RYY,
RZZ,
GPhase,
};
Comment on lines +17 to +42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hope that we do not need this kind of enumeration as we can simply reuse the type information of the MLIR operations. I'd really like to avoid adding too many separate enumerations and classes for concepts that are already well represented in the IR.


} // namespace mlir::qco::decomposition
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) 2023 - 2026 Chair for Design Automation, TUM
* Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH
* All rights reserved.
*
* SPDX-License-Identifier: MIT
*
* Licensed under the MIT License
*/

#pragma once

#include "Gate.h"

#include <Eigen/Core>
#include <llvm/ADT/SmallVector.h>
#include <mlir/Support/LLVM.h>

#include <cstddef>

namespace mlir::qco::decomposition {

/**
* Sequence of abstract decomposition gates plus a residual global phase.
*
* `gates` is stored in execution order: for a column state vector, the first
* gate in the vector is applied first. The reconstructed 4x4 unitary
* is therefore `U = e^{i * phi} * M_{n-1} * ... * M_0`, where `M_i` is the
* two-qubit matrix for `gates[i]` and `phi` is `globalPhase` in radians (via
* `helpers::globalPhaseFactor`).
*/
struct QubitGateSequence {
/// Gates in execution order (see struct comment).
SmallVector<Gate> gates;

/// Residual global phase in radians, not represented by explicit gates.
double globalPhase{};

/// True when `std::abs(globalPhase)` exceeds `DEFAULT_ATOL` in
/// `EulerBasis.h`.
[[nodiscard]] bool hasGlobalPhase() const;

/// Heuristic complexity from `helpers::getComplexity()` for each gate, plus a
/// synthetic global-phase term when `hasGlobalPhase()` is true.
[[nodiscard]] std::size_t complexity() const;

/**
* Reconstruct the overall two-qubit unitary represented by the sequence.
*
* Single-qubit gates are expanded to the two-qubit workspace convention used
* throughout the decomposition utilities.
*/
[[nodiscard]] Eigen::Matrix4cd getUnitaryMatrix() const;
};

Comment on lines +22 to +55
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you probably already gathered from the other comments, I am not too big of a fan of adding so many extra classes and structs here and I would rather explore the "MLIR-way" of doing this.

A couple of thoughts on that

  • We could define a qco.unitary operation and write a pass that first collects runs of single-qubit gates and combines them into a single qco.unitary gate with the respective matrix stored explicitly as part of the IR. Could also be specialized for 2x2 and 4x4 matrices as qco.unitary1q and qco.unitary2q. Then, a subsequent pass could synthesize the respective unitaries to a specific gate set. This could be great for parallelization.
  • It would be desirable if the decomposition (particularly the 1qb one because I believe there it is moderately simple) to also apply to sequences of gates with angles that are not known at compile time. Similarly to the QuaternionMerge pass, this would require to actually embed the computation to be performed into the IR. The quaternion-merging pass is written exactly like that and relies on folds and canonicalizations to actually evaluate the respective angles of the resulting gates. We already noted in the respective PR that this is probably slower than computing the decomposition in "actual code" and not as part of the IR. However, both could be valuable to have in order to also support parametric compilation. Qiskit has some support for this kind of decomposition.
  • I would hope that the WireIterator class helps the iteration of the individual qubit wires

/// Documents intent only; same type as `QubitGateSequence`.
/// `QubitGateSequence::getUnitaryMatrix()` still returns an `Eigen::Matrix4cd`
/// in the shared two-qubit workspace convention, even for one-qubit sequences.
using OneQubitGateSequence = QubitGateSequence;
Comment on lines +56 to +59
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reads like LLM slop


} // namespace mlir::qco::decomposition
Loading