Skip to content

✨ Implement single- and two-qubit gate decomposition pass#1206

Open
taminob wants to merge 244 commits intomainfrom
taminob/mlir-two-qubit-gate-decomposition-pass
Open

✨ Implement single- and two-qubit gate decomposition pass#1206
taminob wants to merge 244 commits intomainfrom
taminob/mlir-two-qubit-gate-decomposition-pass

Conversation

@taminob
Copy link
Collaborator

@taminob taminob commented Sep 16, 2025

Description

This PR introduces a decomposition pass that is used as an optimization to shorten a one- or two-qubit gate series if possible.

Related to #1122

Checklist:

  • The pull request only contains commits that are focused and relevant to this change.
  • I have added appropriate tests that cover the new/changed functionality.
  • I have updated the documentation to reflect these changes.
  • I have added entries to the changelog for any noteworthy additions, changes, fixes, or removals.
  • I have added migration instructions to the upgrade guide (if needed).
  • The changes follow the project's style guidelines and introduce no new warnings.
  • The changes are fully tested and pass the CI checks.
  • I have reviewed my own code changes.

@taminob taminob self-assigned this Sep 16, 2025
@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch from d7f49cc to 4bc6f15 Compare October 22, 2025 17:22
@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch from c4f6399 to ffb96f3 Compare November 18, 2025 13:36
@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch from 2e3a48f to f5ae2b0 Compare November 20, 2025 12:07
@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch 3 times, most recently from bbcec1e to 49d512f Compare November 21, 2025 16:15
@taminob
Copy link
Collaborator Author

taminob commented Nov 21, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a gate-decomposition pass and a decomposition subsystem (Weyl, basis, Euler), numeric/matrix helpers, TableGen/CMake/pass wiring for a new QcoPasses library, compiler-pipeline hookup, and comprehensive unit tests for decomposition components.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Updated release notes to include additional PR references (#1206, #1426).
Top-level & MLIR build
mlir/include/mlir/CMakeLists.txt, mlir/lib/CMakeLists.txt
Reordered subdirectory inclusion and added Passes subtree to the build graph.
Compiler pipeline & linking
mlir/lib/Compiler/CMakeLists.txt, mlir/include/mlir/Compiler/CompilerPipeline.h, mlir/lib/Compiler/CompilerPipeline.cpp
Added QcoPasses to public link libs; declared private static addOptimizationPasses(PassManager&) and invoked it in pipeline Stage 5; included Passes.h.
Passes TableGen & header
mlir/include/mlir/Passes/CMakeLists.txt, mlir/include/mlir/Passes/Passes.td, mlir/include/mlir/Passes/Passes.h
New TableGen incgen target QcoPassesIncGen; TableGen pass GateDecompositionPass and declaration populateGateDecompositionPatterns.
QcoPasses library build
mlir/lib/Passes/CMakeLists.txt
New MLIR library QcoPasses, public header exposure, dependencies (MQT::CoreIR, Eigen), file-set headers, and Eigen alignment compile-def.
Public decomposition headers
mlir/include/mlir/Passes/Decomposition/*
Added public types/APIs: Gate, GateSequence aliases, EulerBasis enum, EulerDecomposition, TwoQubitBasisDecomposer, TwoQubitWeylDecomposition, helpers, and unitary matrix utilities.
Pass implementation & pattern registration
mlir/lib/Passes/Patterns/GateDecompositionPattern.cpp
New GateDecompositionPattern class: matching 1–2 qubit series, assembling 4x4 unitaries, choosing decomposition path (Euler or Weyl+basis), synthesizing gate sequences, and registering pattern via populateGateDecompositionPatterns.
Decomposition implementations
mlir/lib/Passes/Patterns/Decomposition/*.cpp, mlir/lib/Passes/Patterns/Decomposition/EulerBasis.cpp
Implemented Weyl decomposition, TwoQubitBasisDecomposer, Euler/KAK decomposition, unitary/gate matrix builders, trace/fidelity heuristics, GateSequence logic, and helpers.
Unit tests & test infra
mlir/unittests/*, mlir/unittests/Passes/Decomposition/*
Added mqt-core-mlir-decomposition-test target, decomposition tests (test_basis_decomposer.cpp, test_euler_decomposition.cpp, test_weyl_decomposition.cpp), randomUnitaryMatrix util, and CMake test wiring.
Dialect compile flags & small test tweak
mlir/lib/Dialect/QCO/IR/CMakeLists.txt, mlir/unittests/Dialect/QCO/IR/test_unitary_op_interface.cpp
Set EIGEN_MAX_STATIC_ALIGN_BYTES=16 for relevant targets and a minor SmallVector allocator tweak in a unit test.

Sequence Diagram(s)

sequenceDiagram
    participant PM as PassManager
    participant GD as GateDecompositionPass
    participant WD as TwoQubitWeylDecomposition
    participant BD as TwoQubitBasisDecomposer
    participant ED as EulerDecomposition
    participant GS as GateSequence

    PM->>GD: run on Module
    activate GD
    loop each 1‑2 qubit candidate
        GD->>GD: assemble 4x4 unitary
        alt two-qubit
            GD->>WD: create(unitary, fidelity)
            WD-->>GD: (a,b,c, k1/k2, phase)
            GD->>BD: create(basisGate, fidelity)
            BD-->>GD: TwoQubitGateSequence
            BD->>ED: synthesize 1q pieces
            ED-->>BD: OneQubitGateSequence
        else single-qubit
            GD->>ED: generateCircuit(basis, unitary)
            ED-->>GD: OneQubitGateSequence
        end
        GD->>GS: assemble & apply sequence (incl. global phase)
    end
    GD-->>PM: rewritten Module
    deactivate GD
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • burgholzer
  • denialhaag

Poem

🐇
I hopped through matrices, angles spun so bright,
Weyl and Euler twirled into the night,
Passes stitched the pipeline, tests took flight,
Gates fell in order — precise, polite,
A rabbit cheers decomposition tonight!

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (24 files):

⚔️ .pre-commit-config.yaml (content)
⚔️ CHANGELOG.md (content)
⚔️ cmake/SetupMLIR.cmake (content)
⚔️ mlir/.clang-tidy (content)
⚔️ mlir/include/mlir/CMakeLists.txt (content)
⚔️ mlir/include/mlir/Compiler/CompilerPipeline.h (content)
⚔️ mlir/include/mlir/Dialect/QC/Builder/QCProgramBuilder.h (content)
⚔️ mlir/include/mlir/Dialect/QC/IR/QCOps.td (content)
⚔️ mlir/include/mlir/Dialect/QCO/Builder/QCOProgramBuilder.h (content)
⚔️ mlir/include/mlir/Dialect/QCO/IR/QCOOps.td (content)
⚔️ mlir/lib/CMakeLists.txt (content)
⚔️ mlir/lib/Compiler/CMakeLists.txt (content)
⚔️ mlir/lib/Compiler/CompilerPipeline.cpp (content)
⚔️ mlir/lib/Conversion/QCOToQC/QCOToQC.cpp (content)
⚔️ mlir/lib/Conversion/QCToQCO/QCToQCO.cpp (content)
⚔️ mlir/lib/Dialect/QC/Builder/QCProgramBuilder.cpp (content)
⚔️ mlir/lib/Dialect/QCO/Builder/QCOProgramBuilder.cpp (content)
⚔️ mlir/lib/Dialect/QCO/IR/CMakeLists.txt (content)
⚔️ mlir/unittests/CMakeLists.txt (content)
⚔️ mlir/unittests/Compiler/test_compiler_pipeline.cpp (content)
⚔️ mlir/unittests/Dialect/QCO/IR/test_unitary_matrix.cpp (content)
⚔️ mlir/unittests/Dialect/QCO/IR/test_unitary_op_interface.cpp (content)
⚔️ pyproject.toml (content)
⚔️ uv.lock (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately describes the primary change: implementing a single- and two-qubit gate decomposition pass as an optimization feature.
Description check ✅ Passed The description covers the main change (decomposition pass for optimizing gate series), references issue #1122, and completes most checklist items including tests, changelog, and CI validation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch taminob/mlir-two-qubit-gate-decomposition-pass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b35d7be and 4ea6f02.

📒 Files selected for processing (8)
  • CMakeLists.txt (1 hunks)
  • mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h (1 hunks)
  • mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td (1 hunks)
  • mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt (1 hunks)
  • mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp (1 hunks)
  • mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp (1 hunks)
  • mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h (1 hunks)
  • mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/test/Dialect/MQTOpt/Transforms/lift-measurements.mlir:269-288
Timestamp: 2025-10-09T13:20:11.483Z
Learning: In the MQT MLIR dialect, the `rz` gate should not be included in the `DIAGONAL_GATES` set for the `ReplaceBasisStateControlsWithIfPattern` because its operator matrix does not have the required shape | 1 0 | / | 0 x | for the targets-as-controls optimization. It is only included in `LiftMeasurementsAboveGatesPatterns` where the matrix structure requirement differs.
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/DeadGateEliminationPattern.cpp:42-45
Timestamp: 2025-10-09T13:28:29.237Z
Learning: In the MQTOpt MLIR dialect, linear types enforce single-use semantics where each qubit value can only be consumed once, preventing duplicate deallocations and making RAUW operations safe when replacing output qubits with corresponding input qubits in transformation patterns.
📚 Learning: 2025-10-09T13:20:11.483Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/test/Dialect/MQTOpt/Transforms/lift-measurements.mlir:269-288
Timestamp: 2025-10-09T13:20:11.483Z
Learning: In the MQT MLIR dialect, the `rz` gate should not be included in the `DIAGONAL_GATES` set for the `ReplaceBasisStateControlsWithIfPattern` because its operator matrix does not have the required shape | 1 0 | / | 0 x | for the targets-as-controls optimization. It is only included in `LiftMeasurementsAboveGatesPatterns` where the matrix structure requirement differs.

Applied to files:

  • mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.td
  • mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir
  • mlir/lib/Dialect/MQTOpt/Transforms/GateDecomposition.cpp
  • mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h
  • mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h
  • mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp
📚 Learning: 2025-10-09T13:28:29.237Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/DeadGateEliminationPattern.cpp:42-45
Timestamp: 2025-10-09T13:28:29.237Z
Learning: In the MQTOpt MLIR dialect, linear types enforce single-use semantics where each qubit value can only be consumed once, preventing duplicate deallocations and making RAUW operations safe when replacing output qubits with corresponding input qubits in transformation patterns.

Applied to files:

  • mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir
  • mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h
📚 Learning: 2025-10-09T13:13:51.224Z
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:171-180
Timestamp: 2025-10-09T13:13:51.224Z
Learning: In MQT Core MLIR, UnitaryInterface operations guarantee 1-1 correspondence between input and output qubits in the same order. When cloning or modifying unitary operations (e.g., removing controls), this correspondence is maintained by construction, so yielding getAllInQubits() in else-branches matches the result types from the operation's outputs.

Applied to files:

  • mlir/test/Dialect/MQTOpt/Transforms/gate-decomposition.mlir
  • mlir/lib/Dialect/MQTOpt/Transforms/Helpers.h
  • mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp
📚 Learning: 2025-11-03T23:09:26.881Z
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1287
File: test/qdmi/dd/CMakeLists.txt:9-21
Timestamp: 2025-11-03T23:09:26.881Z
Learning: The CMake functions `generate_device_defs_executable` and `generate_prefixed_qdmi_headers` used in QDMI device test CMakeLists.txt files are provided by the external QDMI library (fetched via FetchContent from https://github.com/Munich-Quantum-Software-Stack/qdmi.git), specifically in the cmake/PrefixHandling.cmake module of the QDMI repository.

Applied to files:

  • CMakeLists.txt
🪛 GitHub Check: 🇨‌ Lint / 🚨 Lint
mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp

[warning] 1689-1689: mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp:1689:30 [misc-include-cleaner]
no header providing "std::numeric_limits" is directly included


[warning] 929-929: mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp:929:14 [misc-include-cleaner]
no header providing "Eigen::Vector" is directly included

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: 🐍 Test (macos-14) / 🐍 macos-14
  • GitHub Check: 🐍 Test (ubuntu-24.04-arm) / 🐍 ubuntu-24.04-arm
  • GitHub Check: 🐍 Test (ubuntu-24.04) / 🐍 ubuntu-24.04
  • GitHub Check: 🐍 Test (windows-2022) / 🐍 windows-2022
  • GitHub Check: 🐉 Test / 🍎 macos-15-intel with LLVM@21
🔇 Additional comments (3)
mlir/lib/Dialect/MQTOpt/Transforms/CMakeLists.txt (1)

10-10: Confirm that Eigen3::Eigen is the correct target from your Eigen FetchContent setup

Linking against Eigen3::Eigen here is fine as long as the FetchContent configuration really defines that target on all supported toolchains; otherwise this will fail to configure or link in some environments. Please double‑check the Eigen CMake export to ensure the target name and namespace match what you use here (or add an alias/guard if necessary).

mlir/include/mlir/Dialect/MQTOpt/Transforms/Passes.h (1)

38-38: New pattern population hook looks consistent with existing transforms

The populateGateDecompositionPatterns(RewritePatternSet&) declaration fits cleanly alongside the other populate* helpers and matches the usage in GateDecomposition.cpp. Just make sure the implementation in GateDecompositionPattern.cpp is actually compiled into MLIRMQTOptTransforms so the call site links correctly.

mlir/lib/Dialect/MQTOpt/Transforms/GateDecompositionPattern.cpp (1)

460-540: Creation of GPhaseOp ignores potential control qubits.

When applying a series, the pattern adds a global phase gate via:

if (sequence.hasGlobalPhase()) {
  createOneParameterGate<GPhaseOp>(rewriter, location, sequence.globalPhase, {});
}

This always creates a GPhase with empty inQubits, even if the original series might have had controlled global phases. Given the dialect definition allows GPhaseOp to be controlled by qubits, this may drop control wiring when decomposing controlled global-phase structures. (mqt.readthedocs.io)

Please double‑check whether controlled GPhaseOps can appear in practice. If they can, you likely want to thread the relevant control qubits into the newly created GPhaseOp (and its types) instead of using an empty ValueRange.

⛔ Skipped due to learnings
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/test/Dialect/MQTOpt/Transforms/lift-measurements.mlir:269-288
Timestamp: 2025-10-09T13:20:11.483Z
Learning: In the MQT MLIR dialect, the `rz` gate should not be included in the `DIAGONAL_GATES` set for the `ReplaceBasisStateControlsWithIfPattern` because its operator matrix does not have the required shape | 1 0 | / | 0 x | for the targets-as-controls optimization. It is only included in `LiftMeasurementsAboveGatesPatterns` where the matrix structure requirement differs.
Learnt from: DRovara
Repo: munich-quantum-toolkit/core PR: 1108
File: mlir/lib/Dialect/MQTOpt/Transforms/ReplaceBasisStateControlsWithIfPattern.cpp:171-180
Timestamp: 2025-10-09T13:13:51.224Z
Learning: In MQT Core MLIR, UnitaryInterface operations guarantee 1-1 correspondence between input and output qubits in the same order. When cloning or modifying unitary operations (e.g., removing controls), this correspondence is maintained by construction, so yielding getAllInQubits() in else-branches matches the result types from the operation's outputs.
Learnt from: burgholzer
Repo: munich-quantum-toolkit/core PR: 1283
File: src/qir/runtime/QIR.cpp:196-201
Timestamp: 2025-11-01T15:57:31.153Z
Learning: In the QIR runtime (src/qir/runtime/QIR.cpp), the PRX gate (__quantum__qis__prx__body) is an alias for the R gate (Phased X-Rotation) and should call runtime.apply<qc::R>(theta, phi, qubit), not runtime.apply<qc::RX>() which is a single-parameter rotation gate.

Copy link
Collaborator

@flowerthrower flowerthrower left a comment

Choose a reason for hiding this comment

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

Thank you @taminob for all your work. The math and logic appear to be correctly implemented. I loosely checked the expected K1, K2, ... decomposition values with a small debug script on my Mac and was able to reproduce (roughtly) the same values as the Qiskit decomposition.

The three remaining points are:

  1. Set up a proper test to automate exactly this verification process.
  2. Improve comments — just because the Rust implementation is poorly documented does not mean we should do the same.
    Especially since parts of your code are already nicely commented, it makes sense to extend this good style to the copied code.
  3. Revisit MLIR logic after dialect revamp

@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch 2 times, most recently from 7fc67db to 18cf6b0 Compare November 26, 2025 20:27
@taminob
Copy link
Collaborator Author

taminob commented Nov 27, 2025

I reworked the tests now (added unittests and made the MLIR tests more robust/less precise).

A couple of questions:

  • Is this approach to testing good now or do you think the MLIR tests are too imprecise?
  • Should I adjust this PR to also include the single-qubit decomposition in the pattern or should we do this in a follow-up PR? The full code already exists and it would be the most performant way to add the single-qubit decomposition in the same pass because the qubit series is collected anyway (although the pattern is quite big aleady).
  • The Windows ARM build has an internal alignment issue in Eigen (https://libeigen.gitlab.io/eigen/docs-nightly/group__TopicUnalignedArrayAssert.html; the link in the build log is broken). Will you resolve this issue already in the dialect rewrite that'll introduce Eigen?
  • I currently use two unsupported (so experimental) headers of Eigen. Is this fine or do we want to implement that ourselves? It's for kroneckerProduct() and Eigen::Matrix::exp()

@burgholzer
Copy link
Member

I reworked the tests now (added unittests and made the MLIR tests more robust/less precise).

A couple of questions:

  • Is this approach to testing good now or do you think the MLIR tests are too imprecise?

I just skimmed through both the lit tests and the unit tests and I think the overall direction is fine. I am pretty sure that I will have some more detailed comments when I check the PR in more detail.

  • Should I adjust this PR to also include the single-qubit decomposition in the pattern or should we do this in a follow-up PR? The full code already exists and it would be the most performant way to add the single-qubit decomposition in the same pass because the qubit series is collected anyway (although the pattern is quite big aleady).

Hm. multiple thoughts on this.
I foresee two different use cases for the synthesis routines here:

  • collection and resynthesis of arbitrary runs of two-qubit blocks with no real constraints on the gate set (maybe with a parameter that let's one choose the entangling gate; like CX or CZ or RZZ or ...)
  • synthesis of two-qubit operations (or blocks thereof) to a dedicated/native gateset of a divide (where only those operations are permitted in the decomposition).

These two might actually be more or less the same, the first might just be the second with some reasonable and relaxed defaults on the native gate-set.
I think it could make sense to include the single-qubit synthesis here as well because, as you said, the series are collected anyway, so one might as well apply this to those sequences in the same go.
The only concern that I would probably have is performance. I would assume that single-qubit synthesis is much more efficient than the two-qubit one. If all one does is two-qubit synthesis, then this might become costly at some point.

I am not quite sure that I follow this. That link says:

There are 4 known causes for this issue. If you can target [c++17] only with a recent compiler (e.g., GCC>=7, clang>=5, MSVC>=19.12), then you're lucky: enabling c++17 should be enough (if not, please report to us). Otherwise, please read on to understand those issues and learn how to fix them.

We are using more modern compilers than this (by far). The current CI runs report

-- The C compiler identification is MSVC 19.44.35217.0
-- The CXX compiler identification is MSVC 19.44.35217.0

This seems to indicate to me that you might need to dig a little deeper there.
I am not yet 100% sure if the first version of the dialect rewrite will immediately include the Eigen dependency. At the moment, the following signature is used in the unitary interface

InterfaceMethod<
            "Attempts to extract the static unitary matrix. "
            "Returns std::nullopt if the operation is symbolic or dynamic.",
            "DenseElementsAttr", "tryGetStaticMatrix", (ins)
        >,

An example of how that Attr is constructed would be

inline DenseElementsAttr getMatrixX(MLIRContext* ctx) {
  const auto& complexType = ComplexType::get(Float64Type::get(ctx));
  const auto& type = RankedTensorType::get({2, 2}, complexType);
  const auto& matrix = {0.0 + 0i, 1.0 + 0i,  // row 0
                        1.0 + 0i, 0.0 + 0i}; // row 1
  return DenseElementsAttr::get(type, matrix);
}

This might not be final yet; mostly because I still want to explore some options that might allow us to make the gates/gate matrices singletons and avoid repeated constructions of matrices for gates where this is definitely not necessary.

  • I currently use two unsupported (so experimental) headers of Eigen. Is this fine or do we want to implement that ourselves? It's for kroneckerProduct() and Eigen::Matrix::exp()

I am not too familiar with the details of Eigen, but just from the name alone, I have an aversion to using such headers. Is there any kind of intuition for how well these are tested/work/can be used?
Kronecker Product shouldn't be particularly hard to implement, I suppose.
The Matrix exponential certainly is a little more complicated.
This is definitely not a decision, but just a little bit of caution.

@taminob
Copy link
Collaborator Author

taminob commented Nov 28, 2025

I am not too familiar with the details of Eigen, but just from the name alone, I have an aversion to using such headers. Is there any kind of intuition for how well these are tested/work/can be used?
Kronecker Product shouldn't be particularly hard to implement, I suppose.
The Matrix exponential certainly is a little more complicated.
This is definitely not a decision, but just a little bit of caution.

The documentation of unsupported states:

This is the API documentation for Eigen's unsupported modules.

These modules are contributions from various users. They are provided "as is", without any support.

I think it might be mainly about the stability of the interfaces - so it could happen that different Eigen versions will break API/ABI. However, since they are user-provided, I guess it also heavily depends on the actual function to be used (see also this stackoverflow question.
Looking at the history of MatrixExponential.h, it has been a while since the last actual change, so I don't think that it is likely to have any functionality issues. Same for the KroneckerProduct.

I am not quite sure that I follow this. That link says:

There are 4 known causes for this issue. If you can target [c++17] only with a recent compiler (e.g., GCC>=7, clang>=5, MSVC>=19.12), then you're lucky: enabling c++17 should be enough (if not, please report to us). Otherwise, please read on to understand those issues and learn how to fix them.

We are using more modern compilers than this (by far). The current CI runs report

I'll look into it, although it's a bit hard on a different architecture. Could well be that it is caused by differences in alignment on ARM.

@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch 4 times, most recently from a9d1fc9 to 7efae47 Compare November 28, 2025 12:52
@taminob
Copy link
Collaborator Author

taminob commented Nov 28, 2025

I'll look into it, although it's a bit hard on a different architecture. Could well be that it is caused by differences in alignment on ARM.

Unfortunately, I couldn't really find the source of the error - also because I didn't manage to get a stacktrace in the CI and so can't really pinpoint the issue. I tried using C++23 stacktrace which did compile, but I didn't manage to override Eigen's eigen_assert at the place this is triggered. I didn't manage to reproduce this locally.

I think the cause might be one of https://libeigen.gitlab.io/eigen/docs-nightly/group__TopicPassingByValue.html or https://libeigen.gitlab.io/eigen/docs-nightly/group__TopicStlContainers.html (which should be fixed since C++11, but maybe LLVM+MSVC behaves buggy here - who knows) which are quite serious and need to be considered when using Eigen (at least I wasn't aware of it).

I'd suggest to simply set EIGEN_MAX_STATIC_ALIGN_BYTES to zero for ARM+Windows as advised in the documentation. The drawback is potentially non-optimal vectorization. I did this by defining EIGEN_DONT_ALIGN_STATICALLY.

@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch 2 times, most recently from 888deb2 to 7a80d30 Compare December 1, 2025 09:14
@taminob taminob force-pushed the taminob/mlir-two-qubit-gate-decomposition-pass branch from f45852b to c818f56 Compare December 20, 2025 11:01
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@mlir/lib/Passes/CMakeLists.txt`:
- Around line 13-24: The add_mlir_library invocation for QcoPasses has an extra
PRIVATE token before DEPENDS which incorrectly suggests DEPENDS and
QcoPassesIncGen are link items; remove the redundant PRIVATE so DEPENDS and
QcoPassesIncGen are specified as top-level arguments. Locate the
add_mlir_library block that defines QcoPasses and adjust it by deleting the
second PRIVATE line immediately preceding DEPENDS, leaving DEPENDS
QcoPassesIncGen as standalone arguments.

In `@mlir/lib/Passes/Patterns/GateDecompositionPattern.cpp`:
- Around line 622-644: The loop over sequence.gates in applySeries only handles
X (as a CX), RX, RY, RZ and falls back to llvm::reportFatalInternalError; extend
the dispatch to cover other expected basis gates (e.g., H, S, T, Z, CZ, SWAP,
controlled variants) by adding new else-if branches that: for single-qubit gates
call the appropriate helper (e.g., addSingleQubitGate or reuse
addSingleQubitRotationGate where applicable) and then call
updateInQubits(gate.qubitId, newGate); for multi-qubit/controlled gates use
createControlledGate<...>(...) like the existing createControlledGate<XOp>
pattern and preserve the unitaryMatrix update via
decomposition::fixTwoQubitMatrixQubitOrder when relevant; finally remove or
limit llvm::reportFatalInternalError to unreachable debug-only assertion and
instead log/return an explicit unsupported-gate error so unknown future basis
gates won't crash at runtime (refer to sequence.gates, gate.type,
createControlledGate<XOp>, addSingleQubitRotationGate, updateInQubits,
unitaryMatrix, decomposition::fixTwoQubitMatrixQubitOrder).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@mlir/lib/Passes/CMakeLists.txt`:
- Around line 45-46: Replace the unconditional PUBLIC definition of
EIGEN_MAX_STATIC_ALIGN_BYTES=16 in the target_compile_definitions call for
QcoPasses with a platform-conditional definition that only sets the macro on
impacted targets (e.g., Windows ARM/others that caused the CI failures),
preserving PUBLIC scope when applied; do the same conditional guard for the
identical definition in the MLIRQCODialect target so both places (the
target_compile_definitions entries for QcoPasses and MLIRQCODialect) only inject
EIGEN_MAX_STATIC_ALIGN_BYTES=16 on the specific platforms rather than globally,
ensuring AVX/AVX-512 hosts keep full alignment for Eigen types exposed in public
headers like mlir/include/mlir/Passes/Decomposition/UnitaryMatrices.h.

In `@mlir/unittests/Passes/Decomposition/test_weyl_decomposition.cpp`:
- Around line 64-73: TestApproximation currently passes the magic literal 1.0 -
1e-12 into TwoQubitWeylDecomposition::create but then compares restoredMatrix
with originalMatrix using the default isApprox tolerance (same as TestExact),
making the distinction unclear; replace the literal with a named constant (e.g.,
kNearPerfectFidelity = 1.0 - 1e-12) and add a one-line comment above
TEST_P(WeylDecompositionTest, TestApproximation) explaining that this value
represents a near-perfect fidelity to exercise the "approximate" code path, or
alternatively add a small explicit assertion that demonstrates the test is
exercising the approximation branch (if TwoQubitWeylDecomposition exposes such a
flag), referencing TwoQubitWeylDecomposition::create, TestApproximation,
TestExact, and isApprox to locate the change.
- Around line 54-62: Change local bindings that currently use const auto& for
temporaries returned by GetParam()() to take them by value instead; specifically
in the TEST_P(WeylDecompositionTest, TestExact) test replace const auto&
originalMatrix = GetParam()(); (and the similar binding later on around the
restore/compare use) with const auto originalMatrix = GetParam()(); so
temporaries returned by GetParam()() are owned by the local variable (affecting
the variables used with TwoQubitWeylDecomposition::create and restore).

@taminob
Copy link
Collaborator Author

taminob commented Feb 8, 2026

@burgholzer this PR should be ready for your review. I'm sorry this PR is so huge, but it contains now (as discussed before) both the logic for single- and two-qubit decomposition. To avoid bloating this PR even more (I think this also makes the coderabbit reviews worse), I did not add more extensive tests - I'd maybe suggest doing this in a follow-up in #1499 where coderabbit generated some more edge case test cases. I'm not sure about how to test it in the context of the pipeline since it will be hard to test passes in isolation once we start adding lots of optimizations to the pipeline.

I'll also mention you in all of the remaining open coderabbit comments since the history of this PR is quite long by now.

Once this PR is merged and we agree on the project structure for optimizations, I'd start working on the swap-reconstruction-and-elision pass.

@taminob taminob changed the title ✨ Implement two-qubit gate decomposition pass ✨ Implement single- and two-qubit gate decomposition pass Feb 8, 2026
@burgholzer
Copy link
Member

Great to see this being ready for review 🎉

It's on my pile of PRs to go through. More likely than not, I am going to push some changes directly to the branch as I work through it. I'll try to get to the PR asap.

@burgholzer burgholzer self-requested a review February 9, 2026 10:33
@mergify mergify bot added the conflict label Feb 16, 2026
@burgholzer
Copy link
Member

FYI, the WireIterator is back (#1510) and now again available for the QCO dialect.

@mergify mergify bot removed the conflict label Feb 23, 2026
@taminob
Copy link
Collaborator Author

taminob commented Feb 23, 2026

I updated the branch and got stuck on some new segfaults due to alignment issues in Eigen again (no idea why the merge caused this).
I think the reason is that some files (like the QCO operation source files like CtrlOp.cpp) are (for an unknown reason) compiled without the alignment fix according to my compile_commands.json - when I try to add it globally via add_compile_definitions(EIGEN_MAX_STATIC_ALIGN_BYTES=16) in cmake/ExternalDependencies.cmake, it somehow is added twice like this: -DEIGEN_MAX_STATIC_ALIGN_BYTES=16 -DEIGEN_MAX_STATIC_ALIGN_BYTES=\"16 -D_DEBUG -D_GLIBCXX_ASSERTIONS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS\"
Looks like some quoting issue in CMake to me, but I can't figure out the reason - do you have an idea @burgholzer?

I don't think this needs to block the review, the issue is already merged in main.

FYI, the WireIterator is back (#1510) and now again available for the QCO dialect.

I had a look at this and played around with it a little bit.
I don't think this will actually simplify the collection since I need to keep track of the out-qubit anyway to have a fixed qubit ID and I don't want to cross any control-flow statements anyway for the decomposition.

@burgholzer
Copy link
Member

burgholzer commented Feb 23, 2026

I updated the branch and got stuck on some new segfaults due to alignment issues in Eigen again (no idea why the merge caused this). I think the reason is that some files (like the QCO operation source files like CtrlOp.cpp) are (for an unknown reason) compiled without the alignment fix according to my compile_commands.json - when I try to add it globally via add_compile_definitions(EIGEN_MAX_STATIC_ALIGN_BYTES=16) in cmake/ExternalDependencies.cmake, it somehow is added twice like this: -DEIGEN_MAX_STATIC_ALIGN_BYTES=16 -DEIGEN_MAX_STATIC_ALIGN_BYTES=\"16 -D_DEBUG -D_GLIBCXX_ASSERTIONS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS\" Looks like some quoting issue in CMake to me, but I can't figure out the reason - do you have an idea @burgholzer?

You should not have to pass this option at all, anywhere.
I also encountered this during my work on the test infrastructure.
See 107f3b6 and, in particular, the respective commit message, which I'll copy here for simplicity

🐛 Consistently apply the MQT::ProjectOptions to our MLIR targets
this was one of the culprits causing segfaults with Eigen because MLIR likes to create object libraries. But these object libraries do not inherit the LINK_LIBS setting from the parent target being built. This should also give us a bit of a performance boost since it is building with -march=native now.

I suspect that a very similar thing is going on here. That being said, the setting has been successfully removed on main and all tests and builds work like a charm.
So I would hope that the same holds for this PR

FYI, the WireIterator is back (#1510) and now again available for the QCO dialect.

I had a look at this and played around with it a little bit. I don't think this will actually simplify the collection since I need to keep track of the out-qubit anyway to have a fixed qubit ID and I don't want to cross any control-flow statements anyway for the decomposition.

👍 okay.

@taminob
Copy link
Collaborator Author

taminob commented Feb 25, 2026

You should not have to pass this option at all, anywhere.
I also encountered this during my work on the test infrastructure.
See 107f3b6 and, in particular, the respective commit message, which I'll copy here for simplicity

Not sure if there is something wrong with my system, but I experience inconsistent crashes due to alignment issues - I'm using my thesis evaluation script which runs the 27 benchmarks and 5 to 7 of these crash for me for each execution. I just pushed a commit that removes the alignment limit, but I think the CI might not support AVX and thus doesn't experience the issue.

I suspect that a very similar thing is going on here. That being said, the setting has been successfully removed on main and all tests and builds work like a charm.

Maybe it worked before because we didn't actually use matrix operations on the returned matrices?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Anything related to C++ code feature New feature or request MLIR Anything related to MLIR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants