+
+# MQT Core Catalyst MLIR Plugin
+
+This sub-package of MQT Core provides a [Catalyst](https://github.com/PennyLaneAI/catalyst) plugin based on [MLIR](https://mlir.llvm.org/).
+It allows you to use MQT Core's MLIR dialects and transformations within the Catalyst framework, enabling advanced quantum circuit optimizations and transformations.
+
+If you have any questions, feel free to create a [discussion](https://github.com/munich-quantum-toolkit/core/discussions) or an [issue](https://github.com/munich-quantum-toolkit/core/issues) on [GitHub](https://github.com/munich-quantum-toolkit/core).
+
+## Contributors and Supporters
+
+The _[Munich Quantum Toolkit (MQT)](https://mqt.readthedocs.io)_ is developed by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/) and supported by the [Munich Quantum Software Company (MQSC)](https://munichquantum.software).
+Among others, it is part of the [Munich Quantum Software Stack (MQSS)](https://www.munich-quantum-valley.de/research/research-areas/mqss) ecosystem, which is being developed as part of the [Munich Quantum Valley (MQV)](https://www.munich-quantum-valley.de) initiative.
+
+
+
+
+
+
+
+
+Thank you to all the contributors who have helped make MQT Core a reality!
+
+
+
+## Getting Started
+
+`mqt.core.catalyst` is **NOT YET** available on [PyPI](https://pypi.org/project/mqt.core/).
+
+Because `pennylane-catalyst` pins to a specific LLVM/MLIR revision, you must build that LLVM/MLIR locally and point CMake at it.
+
+### 1) Build the exact LLVM/MLIR revision (locally)
+
+```bash
+# Pick a workspace (optional)
+mkdir -p ~/dev && cd ~/dev
+
+# Clone the exact LLVM revision Catalyst expects
+git clone https://github.com/llvm/llvm-project.git
+cd llvm-project
+git checkout f8cb7987c64dcffb72414a40560055cb717dbf74
+
+# Configure & build MLIR (Release is recommended)
+cmake -S llvm -B build_llvm -G Ninja \
+ -DLLVM_ENABLE_PROJECTS=mlir \
+ -DLLVM_BUILD_EXAMPLES=OFF \
+ -DLLVM_BUILD_TESTS=OFF \
+ -DLLVM_INCLUDE_TESTS=OFF \
+ -DLLVM_INCLUDE_EXAMPLES=OFF \
+ -DLLVM_ENABLE_ASSERTIONS=ON \
+ -DLLVM_ENABLE_ZLIB=FORCE_ON \
+ -DLLVM_ENABLE_ZSTD=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_CXX_VISIBILITY_PRESET=default
+
+cmake --build build_llvm --config Release
+
+# Export these for your shell/session
+export MLIR_DIR="$PWD/build_llvm/lib/cmake/mlir"
+export LLVM_DIR="$PWD/build_llvm/lib/cmake/llvm"
+```
+
+### 2) Create a local env and build the plugin
+
+```console
+# From your repo root
+cd /path/to/your/core/plugins/catalyst
+
+# Create and activate a venv (optional)
+uv venv .venv
+. .venv/bin/activate
+
+# Install Catalyst and build the plugin
+uv pip install pennylane-catalyst>0.12.0
+
+uv sync --verbose --active
+ --config-settings=cmake.define.CMAKE_BUILD_TYPE=Release
+ --config-settings=cmake.define.Python3_EXECUTABLE="$(which python)"
+ --config-settings=cmake.define.MLIR_DIR="$MLIR_DIR"
+ --config-settings=cmake.define.LLVM_DIR="$LLVM_DIR"
+```
+
+### 3) Use the MQT plugin with your PennyLane code
+
+The MQT plugin provides device configuration utilities to prevent Catalyst from decomposing gates into unitary matrices, enabling lossless roundtrip conversions.
+
+**Important:** Use `get_device()` from the MQT plugin instead of `qml.device()` directly:
+
+```python3
+import catalyst
+import pennylane as qml
+from catalyst.passes import apply_pass
+from mqt.core.plugins.catalyst import get_device
+
+# Use get_device() to configure the device for MQT plugin compatibility
+# This prevents gates from being decomposed into unitary matrices
+device = get_device("lightning.qubit", wires=2)
+
+
+@apply_pass("mqt.mqtopt-to-catalystquantum")
+@apply_pass("mqt.catalystquantum-to-mqtopt")
+@qml.qnode(device)
+def circuit() -> None:
+ qml.Hadamard(wires=[0])
+ qml.CNOT(wires=[0, 1])
+ # Controlled gates will NOT be decomposed to matrices
+ qml.ctrl(qml.PauliX(wires=0), control=1)
+ catalyst.measure(0)
+ catalyst.measure(1)
+
+
+@qml.qjit(target="mlir", autograph=True)
+def module() -> None:
+ return circuit()
+
+
+# Get the optimized MLIR representation
+mlir_output = module.mlir_opt
+```
+
+**Alternative:** You can also configure an existing device:
+
+```python3
+from mqt.core.plugins.catalyst import configure_device_for_mqt
+
+device = qml.device("lightning.qubit", wires=2)
+device = configure_device_for_mqt(device)
+```
+
+## System Requirements
+
+Building the MQT Core MLIR Catalyst Plugin requires a C++ compiler with support for C++20 and CMake 3.24 or newer.
+Building (and running) is continuously tested under Linux and macOS using the [latest available system versions for GitHub Actions](https://github.com/actions/runner-images).
+The MQT Core MLIR Catalyst Plugin is compatible with Python version 3.11 and newer.
+
+The MQT Core MLIR Catalyst Plugin relies on some external dependencies:
+
+- [llvm/llvm-project](https://github.com/llvm/llvm-project): A toolkit for the construction of highly optimized compilers, optimizers, and run-time environments (specific revision: `f8cb7987c64dcffb72414a40560055cb717dbf74`).
+- [PennyLaneAI/catalyst](https://github.com/PennyLaneAI/catalyst): A package that enables just-in-time (JIT) compilation of hybrid quantum-classical programs implemented with PennyLane (version > 0.12.0).
+- [MQT Core](https://github.com/munich-quantum-toolkit/core): Provides the MQTOpt MLIR dialect and supporting infrastructure.
+
+Note, both LLVM/MLIR and Catalyst are currently restricted to specific versions. You must build LLVM/MLIR locally from the exact revision specified above and configure CMake to use it (see installation instructions).
+
+## Cite This
+
+If you want to cite MQT Core's MLIR Plugin, please use the following BibTeX entry:
+
+```bibtex
+@inproceedings{Hopf_2026,
+author = {Hopf, Patrick and Ochoa, Erick and Stade, Yannick and Rovara, Damian and Quetschlich, Nils and Florea, Ioan Albert and Izaac, Josh and Wille, Robert and Burgholzer, Lukas},
+title = {Integrating Quantum Software Tools with(in) {MLIR}},
+year = {2026},
+publisher = {Association for Computing Machinery},
+address = {New York, NY, USA},
+url = {https://doi.org/10.1145/3773656.3773658},
+doi = {10.1145/3773656.3773658},
+booktitle = {Proceedings of the International Conference on High Performance Computing in Asia-Pacific Region},
+keywords = {quantum software development, quantum compilation, intermediate representation, MLIR},
+series = {HPCASIA '26}
+}
+```
+
+---
+
+## Acknowledgements
+
+The Munich Quantum Toolkit has been supported by the European
+Research Council (ERC) under the European Union's Horizon 2020 research and innovation program (grant agreement
+No. 101001318), the Bavarian State Ministry for Science and Arts through the Distinguished Professorship Program, as well as the
+Munich Quantum Valley, which is supported by the Bavarian state government with funds from the Hightech Agenda Bayern Plus.
+
+
+
+
+
+
+
diff --git a/plugins/catalyst/cmake/ExternalDependencies.cmake b/plugins/catalyst/cmake/ExternalDependencies.cmake
new file mode 100644
index 0000000000..cab53f33f2
--- /dev/null
+++ b/plugins/catalyst/cmake/ExternalDependencies.cmake
@@ -0,0 +1,119 @@
+# 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
+
+# Declare all external dependencies and make sure that they are available.
+
+include(FetchContent)
+
+if(DEFINED Python3_EXECUTABLE AND Python3_EXECUTABLE)
+ set(CATALYST_VERSION 0.13.0)
+ # Check if the pennylane-catalyst package is installed in the python environment.
+ execute_process(
+ COMMAND "${Python3_EXECUTABLE}" -c "import catalyst; print(catalyst.__version__)"
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ OUTPUT_VARIABLE FOUND_CATALYST_VERSION)
+ if(FOUND_CATALYST_VERSION)
+ message(STATUS "Found pennylane-catalyst ${FOUND_CATALYST_VERSION} in python environment.")
+ # Check if the version is compatible.
+ if(FOUND_CATALYST_VERSION VERSION_LESS ${CATALYST_VERSION})
+ message(
+ WARNING
+ "pennylane-catalyst version ${FOUND_CATALYST_VERSION} in python environment is not compatible."
+ )
+ else()
+ # Detect the installed catalyst include files.
+ execute_process(
+ COMMAND "${Python3_EXECUTABLE}" -c
+ "import catalyst.utils.runtime_environment as c; print(c.get_include_path())"
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ OUTPUT_VARIABLE CATALYST_INCLUDE_DIRS)
+
+ message(STATUS "Catalyst include path resolved to: ${CATALYST_INCLUDE_DIRS}")
+
+ string(FIND "${CATALYST_INCLUDE_DIRS}" "site-packages" SITEPKG_IDX)
+
+ if(SITEPKG_IDX EQUAL -1)
+ # In case of a installation from source Assume include path looks like: /mlir/include
+ # Derive /mlir/build/include and /mlir/build/lib/cmake/catalyst
+ get_filename_component(CATALYST_MLIR_ROOT "${CATALYST_INCLUDE_DIRS}/.." ABSOLUTE)
+ set(CATALYST_BUILD_DIR "${CATALYST_MLIR_ROOT}/build")
+ set(CATALYST_BUILD_INCLUDE_DIR "${CATALYST_BUILD_DIR}/include")
+ set(Catalyst_DIR "${CATALYST_BUILD_DIR}/lib/cmake/catalyst")
+
+ include_directories("${CATALYST_INCLUDE_DIRS}")
+ include_directories("${CATALYST_BUILD_INCLUDE_DIR}")
+ include_directories("${CATALYST_INCLUDE_DIRS}")
+ include_directories("${CATALYST_BUILD_INCLUDE_DIR}")
+ else()
+ # In case of an installation from PyPI, the include path looks like:
+ # /site-packages/catalyst/include Derive the site-packages root and add it to the
+ # CMAKE_PREFIX_PATH.
+ get_filename_component(CATALYST_SP_ROOT "${CATALYST_INCLUDE_DIRS}/../.." ABSOLUTE)
+ list(APPEND CMAKE_PREFIX_PATH "${CATALYST_SP_ROOT}")
+ message(STATUS "Adding Catalyst site-packages to CMAKE_PREFIX_PATH: ${CATALYST_SP_ROOT}")
+ endif()
+
+ endif()
+ else()
+ # Unfortunately, the download for an individual package cannot be turned off. To avoid
+ # downloading the entire package, we use `find_package` instead.
+ find_package(Catalyst ${CATALYST_VERSION} REQUIRED)
+ endif()
+
+ if(NOT CATALYST_INCLUDE_DIRS)
+ message(
+ FATAL_ERROR
+ "The include directory of the pennylane-catalyst package could not be retrieved. Please ensure that the catalyst is installed correctly."
+ )
+ endif()
+
+else()
+ find_package(Catalyst ${CATALYST_VERSION} QUIET)
+ if(NOT Catalyst_FOUND)
+ message(
+ FATAL_ERROR
+ "Python3 interpreter not found and Catalyst not discoverable. Either set Python3_EXECUTABLE for in-env detection or provide Catalyst via CMAKE_PREFIX_PATH."
+ )
+ endif()
+endif()
+
+# cmake-format: off
+set(MQT_CORE_MINIMUM_VERSION 3.1.0
+ CACHE STRING "MQT Core minimum version")
+set(MQT_CORE_VERSION 3.3.3
+ CACHE STRING "MQT Core version")
+set(MQT_CORE_REV "v3.3.3"
+ CACHE STRING "MQT Core identifier (tag, branch or commit hash)")
+set(MQT_CORE_REPO_OWNER "munich-quantum-toolkit"
+ CACHE STRING "MQT Core repository owner (change when using a fork)")
+# cmake-format: on
+set(BUILD_MQT_CORE_TESTS
+ OFF
+ CACHE BOOL "Build MQT Core tests")
+set(BUILD_MQT_CORE_SHARED_LIBS
+ OFF
+ CACHE BOOL "Build MQT Core shared libraries")
+set(BUILD_MQT_CORE_MLIR
+ ON
+ CACHE BOOL "Build MQT Core MLIR support")
+set(CMAKE_POSITION_INDEPENDENT_CODE
+ ON
+ CACHE BOOL "Enable position independent code (PIC) for MQT Core")
+# Fetch mqt-core from the source tree
+set(FETCHCONTENT_SOURCE_DIR_MQT-CORE
+ "${CMAKE_CURRENT_SOURCE_DIR}/../.."
+ CACHE PATH "Source directory for MQT Core")
+FetchContent_Declare(
+ mqt-core
+ GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git
+ GIT_TAG ${MQT_CORE_REV}
+ FIND_PACKAGE_ARGS ${MQT_CORE_MINIMUM_VERSION})
+list(APPEND FETCH_PACKAGES mqt-core)
+
+# Make all declared dependencies available.
+FetchContent_MakeAvailable(${FETCH_PACKAGES})
diff --git a/plugins/catalyst/include/CMakeLists.txt b/plugins/catalyst/include/CMakeLists.txt
new file mode 100644
index 0000000000..f674bdbc32
--- /dev/null
+++ b/plugins/catalyst/include/CMakeLists.txt
@@ -0,0 +1,9 @@
+# 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
+
+add_subdirectory(mlir)
diff --git a/plugins/catalyst/include/mlir/CMakeLists.txt b/plugins/catalyst/include/mlir/CMakeLists.txt
new file mode 100644
index 0000000000..6a65e12703
--- /dev/null
+++ b/plugins/catalyst/include/mlir/CMakeLists.txt
@@ -0,0 +1,9 @@
+# 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
+
+add_subdirectory(Conversion)
diff --git a/plugins/catalyst/include/mlir/Conversion/CMakeLists.txt b/plugins/catalyst/include/mlir/Conversion/CMakeLists.txt
new file mode 100644
index 0000000000..6acd44dea6
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/CMakeLists.txt
@@ -0,0 +1,10 @@
+# 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
+
+add_subdirectory(MQTOptToCatalystQuantum)
+add_subdirectory(CatalystQuantumToMQTOpt)
diff --git a/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt
new file mode 100644
index 0000000000..dcfbcd0323
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt
@@ -0,0 +1,13 @@
+# 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
+
+set(LLVM_TARGET_DEFINITIONS CatalystQuantumToMQTOpt.td)
+mlir_tablegen(CatalystQuantumToMQTOpt.h.inc -gen-pass-decls -name CatalystQuantumToMQTOpt)
+add_public_tablegen_target(CatalystQuantumToMQTOptIncGen)
+
+add_mlir_doc(CatalystQuantumToMQTOpt CatalystQuantumToMQTOpt ./ -gen-pass-doc)
diff --git a/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h
new file mode 100644
index 0000000000..a1ec8cec28
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h
@@ -0,0 +1,23 @@
+/*
+ * 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 // from @llvm-project
+
+namespace mqt::ir::conversions {
+
+#define GEN_PASS_DECL
+#include "mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc"
+
+#define GEN_PASS_REGISTRATION
+#include "mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc"
+
+} // namespace mqt::ir::conversions
diff --git a/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.td b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.td
new file mode 100644
index 0000000000..b4cde9a45d
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.td
@@ -0,0 +1,32 @@
+// 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/Pass/PassBase.td"
+
+def CatalystQuantumToMQTOpt : Pass<"catalystquantum-to-mqtopt"> {
+ let summary = "Convert Catalyst's `Quantum` to MQT's `MQTOpt` dialect.";
+
+ let description = [{
+ This pass converts Catalyst's `Quantum` to MQT's `MQTOpt` dialect.
+ The following operations are currently NOT converted (and instead marked legal):
+ - DeviceInitOp
+ - DeviceReleaseOp
+ - NamedObsOp
+ - ExpvalOp
+ - FinalizeOp
+ - ComputationalBasisOp
+ - StateOp
+ - InitializeOp
+ }];
+
+ // Define dependent dialects
+ let dependentDialects = [
+ "catalyst::quantum::QuantumDialect",
+ "::mqt::ir::opt::MQTOptDialect"
+ ];
+}
diff --git a/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt
new file mode 100644
index 0000000000..efe8759c1e
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt
@@ -0,0 +1,13 @@
+# 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
+
+set(LLVM_TARGET_DEFINITIONS MQTOptToCatalystQuantum.td)
+mlir_tablegen(MQTOptToCatalystQuantum.h.inc -gen-pass-decls -name MQTOptToCatalystQuantum)
+add_public_tablegen_target(MQTOptToCatalystQuantumIncGen)
+
+add_mlir_doc(MQTOptToCatalystQuantum MQTOptToCatalystQuantum ./ -gen-pass-doc)
diff --git a/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h
new file mode 100644
index 0000000000..72d44b123a
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h
@@ -0,0 +1,23 @@
+/*
+ * 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 // from @llvm-project
+
+namespace mqt::ir::conversions {
+
+#define GEN_PASS_DECL
+#include "mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc"
+
+#define GEN_PASS_REGISTRATION
+#include "mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc"
+
+} // namespace mqt::ir::conversions
diff --git a/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td
new file mode 100644
index 0000000000..41b64eb162
--- /dev/null
+++ b/plugins/catalyst/include/mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td
@@ -0,0 +1,24 @@
+// 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/Pass/PassBase.td"
+
+def MQTOptToCatalystQuantum : Pass<"mqtopt-to-catalystquantum"> {
+ let summary = "Convert MQT's `MQTOpt` to Catalyst's `Quantum` dialect.";
+
+ let description = [{
+ This pass converts MQT's `MQTOpt` to Catalyst's `Quantum` dialect.
+ }];
+ let dependentDialects = [
+ "::catalyst::quantum::QuantumDialect",
+ "::mlir::arith::ArithDialect",
+ "::mlir::func::FuncDialect",
+ "::mlir::memref::MemRefDialect",
+ "::mqt::ir::opt::MQTOptDialect"
+ ];
+}
diff --git a/plugins/catalyst/lib/CMakeLists.txt b/plugins/catalyst/lib/CMakeLists.txt
new file mode 100644
index 0000000000..c5614370d5
--- /dev/null
+++ b/plugins/catalyst/lib/CMakeLists.txt
@@ -0,0 +1,56 @@
+# 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
+
+# Q: Why is it add_llvm_library and not add_mlir_library? A: According to Erick from Xanadu, who
+# talked with the MLIR team, the reason is probably, that the MLIR plugin uses the same
+# infrastructure as the LLVM plugins. Probably due to laziness, the appropriate MLIR macros were not
+# adopted yet.
+#
+# Note: On DLL platforms, the tool that will use this plugin must be linked, see
+# https://github.com/llvm/llvm-project/blob/2acecfe65397c162958ab305dc44614ff51e748c/llvm/cmake/modules/AddLLVM.cmake#L770
+# and
+# https://github.com/llvm/llvm-project/blob/2acecfe65397c162958ab305dc44614ff51e748c/llvm/cmake/modules/AddLLVM.cmake#L517
+# This would mean that it is not enough to have the corresponding header at hand, we would need to
+# have the catalyst target available to link against it.
+
+add_subdirectory(Conversion)
+
+set(TARGET_NAME mqt-core-catalyst-plugin)
+
+add_llvm_library(
+ ${TARGET_NAME}
+ MODULE
+ mqt-plugin.cpp
+ LINK_LIBS
+ MLIRMQTOptTransforms
+ CatalystQuantumToMQTOpt
+ MQTOptToCatalystQuantum)
+
+if(DEFINED PLUGIN_OUTPUT_DIRECTORY)
+ cmake_path(ABSOLUTE_PATH PLUGIN_OUTPUT_DIRECTORY BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT_VARIABLE ABS_PLUGIN_OUTPUT_DIR)
+ set_target_properties(${TARGET_NAME} PROPERTIES
+ LIBRARY_OUTPUT_DIRECTORY "${ABS_PLUGIN_OUTPUT_DIR}"
+ RUNTIME_OUTPUT_DIRECTORY "${ABS_PLUGIN_OUTPUT_DIR}"
+ ARCHIVE_OUTPUT_DIRECTORY "${ABS_PLUGIN_OUTPUT_DIR}"
+ )
+endif()
+
+# set required C++ standard
+target_compile_features(${TARGET_NAME} PUBLIC cxx_std_20)
+include(GNUInstallDirs)
+target_compile_options(${TARGET_NAME} PUBLIC $<$:-fexceptions>
+ $<$:/EHsc>)
+
+# install directive for scikit-build-core
+message(STATUS "Configuring mqt-core-catalyst-plugin CMakeLists.txt")
+install(
+ TARGETS mqt-core-catalyst-plugin
+ LIBRARY DESTINATION "mqt/core/plugins/catalyst"
+ RUNTIME DESTINATION "mqt/core/plugins/catalyst"
+ ARCHIVE DESTINATION "mqt/core/plugins/catalyst"
+)
\ No newline at end of file
diff --git a/plugins/catalyst/lib/Conversion/CMakeLists.txt b/plugins/catalyst/lib/Conversion/CMakeLists.txt
new file mode 100644
index 0000000000..6acd44dea6
--- /dev/null
+++ b/plugins/catalyst/lib/Conversion/CMakeLists.txt
@@ -0,0 +1,10 @@
+# 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
+
+add_subdirectory(MQTOptToCatalystQuantum)
+add_subdirectory(CatalystQuantumToMQTOpt)
diff --git a/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt b/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt
new file mode 100644
index 0000000000..6ca50ceb85
--- /dev/null
+++ b/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CMakeLists.txt
@@ -0,0 +1,34 @@
+# 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
+
+add_mlir_library(CatalystQuantumToMQTOpt CatalystQuantumToMQTOpt.cpp LINK_LIBS MLIRMQTOpt DEPENDS
+ CatalystQuantumToMQTOptIncGen)
+
+target_compile_features(CatalystQuantumToMQTOpt PUBLIC cxx_std_20)
+target_compile_options(CatalystQuantumToMQTOpt PUBLIC -fexceptions)
+
+file(GLOB_RECURSE CONVERSION_HEADERS_SOURCE
+ "${MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR}/mlir/Conversion/CatalystQuantumToMQTOpt/*.h")
+file(GLOB_RECURSE CONVERSION_HEADERS_BUILD
+ "${MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR}/mlir/Conversion/CatalystQuantumToMQTOpt/*.inc")
+
+# add public headers using file sets
+target_sources(
+ CatalystQuantumToMQTOpt
+ PUBLIC FILE_SET
+ HEADERS
+ BASE_DIRS
+ ${MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR}
+ FILES
+ ${CONVERSION_HEADERS_SOURCE}
+ FILE_SET
+ HEADERS
+ BASE_DIRS
+ ${MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR}
+ FILES
+ ${CONVERSION_HEADERS_BUILD})
diff --git a/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp b/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp
new file mode 100644
index 0000000000..6b0570157e
--- /dev/null
+++ b/plugins/catalyst/lib/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp
@@ -0,0 +1,674 @@
+/*
+ * 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/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h"
+
+#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace mqt::ir::conversions {
+
+#define GEN_PASS_DEF_CATALYSTQUANTUMTOMQTOPT
+#include "mlir/Conversion/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc"
+
+using namespace mlir;
+using namespace mlir::arith;
+
+class CatalystQuantumToMQTOptTypeConverter final : public TypeConverter {
+public:
+ explicit CatalystQuantumToMQTOptTypeConverter(MLIRContext* ctx) {
+ // Identity conversion: Allow all types to pass through unmodified if needed
+ addConversion([](const Type type) { return type; });
+
+ // Convert Catalyst QubitType to MQTOpt QubitType
+ addConversion([ctx](catalyst::quantum::QubitType /*type*/) -> Type {
+ return opt::QubitType::get(ctx);
+ });
+
+ // Convert Catalyst QuregType to dynamic memref as placeholder
+ // The actual static memref types will flow through from alloc operations
+ addConversion([ctx](catalyst::quantum::QuregType /*type*/) -> Type {
+ auto qubitType = opt::QubitType::get(ctx);
+ return MemRefType::get({ShapedType::kDynamic}, qubitType);
+ });
+
+ // Target materialization: converts values during pattern application
+ // Just returns the input - the actual memref is already created by alloc
+ addTargetMaterialization([](OpBuilder& builder, Type resultType,
+ ValueRange inputs, Location loc) -> Value {
+ if (inputs.size() == 1) {
+ return inputs[0];
+ }
+ return nullptr;
+ });
+ }
+};
+
+struct ConvertQuantumAlloc final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::AllocOp op, OpAdaptor /*adaptor*/,
+ ConversionPatternRewriter& rewriter) const override {
+ // Get the number of qubits from the attribute
+ auto nqubitsAttr = op.getNqubitsAttrAttr();
+ if (!nqubitsAttr) {
+ return op.emitError("AllocOp missing nqubits_attr");
+ }
+
+ const auto nqubits = nqubitsAttr.getValue().getZExtValue();
+
+ // Prepare the result type(s)
+ const auto qubitType = opt::QubitType::get(rewriter.getContext());
+ const auto memrefType =
+ MemRefType::get({static_cast(nqubits)}, qubitType);
+
+ // Create the new operation
+ auto allocOp = rewriter.create(op.getLoc(), memrefType);
+
+ // Replace the original with the new operation
+ rewriter.replaceOp(op, allocOp.getResult());
+ return success();
+ }
+};
+
+struct ConvertQuantumDealloc final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::DeallocOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operand(s)
+ Value memref = adaptor.getQreg();
+
+ // Unwrap unrealized_conversion_cast if present
+ if (auto castOp = memref.getDefiningOp()) {
+ if (!castOp.getInputs().empty()) {
+ memref = castOp.getInputs()[0];
+ }
+ }
+
+ // Create the new operation
+ rewriter.replaceOpWithNewOp(op, memref);
+ return success();
+ }
+};
+
+struct ConvertQuantumMeasure final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::MeasureOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operand(s)
+ const auto inQubit = adaptor.getInQubit();
+
+ // Prepare the result type(s)
+ const auto qubitType = opt::QubitType::get(rewriter.getContext());
+ const auto bitType = rewriter.getI1Type();
+
+ // Create the new operation
+ // Note: quantum.measure returns (i1, !quantum.bit)
+ // mqtopt.measure returns (!mqtopt.Qubit, i1)
+ auto mqtoptOp = rewriter.create(op.getLoc(), qubitType,
+ bitType, inQubit);
+
+ // Replace with results in the correct order
+ rewriter.replaceOp(op, {mqtoptOp.getResult(1), mqtoptOp.getResult(0)});
+ return success();
+ }
+};
+
+struct ConvertQuantumExtract final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::ExtractOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Prepare the result type(s)
+ const auto qubitType = opt::QubitType::get(rewriter.getContext());
+
+ // Get index (either from attribute or operand)
+ Value indexValue;
+ auto idxAttr = op.getIdxAttrAttr();
+
+ if (idxAttr) {
+ // Compile-time constant index from attribute
+ const auto idx = idxAttr.getValue().getZExtValue();
+ indexValue = rewriter.create(op.getLoc(), idx);
+ } else {
+ // Runtime dynamic index from operand
+ auto idxOperand = adaptor.getIdx();
+ if (!idxOperand) {
+ return op.emitError("ExtractOp missing both idx_attr and idx operand");
+ }
+
+ // Convert i64 to index type if needed
+ if (isa(idxOperand.getType())) {
+ indexValue = rewriter.create(
+ op.getLoc(), rewriter.getIndexType(), idxOperand);
+ } else {
+ indexValue = idxOperand;
+ }
+ }
+
+ // Extract operand(s)
+ Value memref = adaptor.getQreg();
+
+ // Unwrap unrealized_conversion_cast if present
+ if (auto castOp = memref.getDefiningOp()) {
+ if (!castOp.getInputs().empty()) {
+ memref = castOp.getInputs()[0];
+ }
+ }
+
+ // Verify we got a static memref type as expected
+ auto memrefType = dyn_cast(memref.getType());
+ if (!memrefType || !memrefType.hasStaticShape()) {
+ return op.emitError("Expected static memref type from alloc, got: ")
+ << memref.getType();
+ }
+
+ // Create the new operation
+ auto loadOp = rewriter.create(
+ op.getLoc(), qubitType, memref, ValueRange{indexValue});
+
+ // Replace the extract operation with the loaded qubit
+ rewriter.replaceOp(op, loadOp.getResult());
+ return success();
+ }
+};
+
+struct ConvertQuantumInsert final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::InsertOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Get index (either from attribute or operand)
+ Value indexValue;
+ auto idxAttr = op.getIdxAttrAttr();
+
+ if (idxAttr) {
+ // Compile-time constant index from attribute
+ const auto idx = idxAttr.getValue().getZExtValue();
+ indexValue = rewriter.create(op.getLoc(), idx);
+ } else {
+ // Runtime dynamic index from operand
+ auto idxOperand = adaptor.getIdx();
+ if (!idxOperand) {
+ return op.emitError("InsertOp missing both idx_attr and idx operand");
+ }
+
+ // Convert i64 to index type if needed
+ if (isa(idxOperand.getType())) {
+ indexValue = rewriter.create(
+ op.getLoc(), rewriter.getIndexType(), idxOperand);
+ } else {
+ indexValue = idxOperand;
+ }
+ }
+
+ // Extract operand(s)
+ Value memref = adaptor.getInQreg();
+
+ // Unwrap unrealized_conversion_cast if present
+ if (auto castOp = memref.getDefiningOp()) {
+ if (!castOp.getInputs().empty()) {
+ memref = castOp.getInputs()[0];
+ }
+ }
+
+ // Create the new operation
+ rewriter.create(op.getLoc(), adaptor.getQubit(), memref,
+ ValueRange{indexValue});
+
+ // In the memref model, the register is modified in-place
+ rewriter.replaceOp(op, memref);
+ return success();
+ }
+};
+
+struct ConvertQuantumGlobalPhase final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::GlobalPhaseOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operand(s) and attribute(s)
+ const auto param = adaptor.getParams();
+ const auto inCtrlQubits = adaptor.getInCtrlQubits();
+ const auto inCtrlValues = adaptor.getInCtrlValues();
+
+ // Separate positive and negative control qubits
+ SmallVector inPosCtrlQubitsVec;
+ SmallVector inNegCtrlQubitsVec;
+
+ for (size_t i = 0; i < inCtrlQubits.size(); ++i) {
+ bool isPosCtrl = false;
+ if (auto constOp = inCtrlValues[i].getDefiningOp()) {
+ if (auto boolAttr = dyn_cast(constOp.getValue())) {
+ isPosCtrl = boolAttr.getValue();
+ } else if (auto intAttr = dyn_cast(constOp.getValue())) {
+ isPosCtrl = (intAttr.getInt() != 0);
+ } else {
+ return op.emitError(
+ "Control value must be a boolean or integer constant");
+ }
+ } else {
+ return op.emitError("Dynamic control values are not supported");
+ }
+
+ if (isPosCtrl) {
+ inPosCtrlQubitsVec.emplace_back(inCtrlQubits[i]);
+ } else {
+ inNegCtrlQubitsVec.emplace_back(inCtrlQubits[i]);
+ }
+ }
+
+ // Create the parameter attributes
+ SmallVector staticParamsVec;
+ SmallVector paramsMaskVec;
+ SmallVector finalParamValues;
+
+ // Check if parameter is a compile-time constant
+ if (auto constOp = param.getDefiningOp()) {
+ if (auto floatAttr = dyn_cast(constOp.getValue())) {
+ staticParamsVec.push_back(floatAttr.getValueAsDouble());
+ paramsMaskVec.push_back(true);
+ } else {
+ finalParamValues.push_back(param);
+ paramsMaskVec.push_back(false);
+ }
+ } else {
+ finalParamValues.push_back(param);
+ paramsMaskVec.push_back(false);
+ }
+
+ const auto staticParams =
+ DenseF64ArrayAttr::get(rewriter.getContext(), staticParamsVec);
+ const auto paramsMask =
+ DenseBoolArrayAttr::get(rewriter.getContext(), paramsMaskVec);
+
+ // Create the new operation
+ auto mqtoptOp = rewriter.create(
+ op.getLoc(), TypeRange{}, // out_qubits
+ ValueRange(inPosCtrlQubitsVec).getTypes(), // pos_ctrl_out_qubits
+ ValueRange(inNegCtrlQubitsVec).getTypes(), // neg_ctrl_out_qubits
+ staticParams, paramsMask, finalParamValues, // params
+ ValueRange{}, // in_qubits
+ ValueRange(inPosCtrlQubitsVec), // pos_ctrl_in_qubits
+ ValueRange(inNegCtrlQubitsVec)); // neg_ctrl_in_qubits
+
+ // Replace the original with the new operation
+ rewriter.replaceOp(op, mqtoptOp);
+ return success();
+ }
+};
+
+struct ConvertQuantumCustomOp final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(catalyst::quantum::CustomOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operand(s) and attribute(s)
+ const auto gateName = op.getGateName();
+ const auto paramsValues = adaptor.getParams();
+ const auto inQubits = adaptor.getInQubits();
+ const auto inCtrlQubits = adaptor.getInCtrlQubits();
+ const auto inCtrlValues = adaptor.getInCtrlValues();
+
+ // Separate positive and negative control qubits
+ SmallVector inPosCtrlQubitsVec;
+ SmallVector inNegCtrlQubitsVec;
+
+ for (size_t i = 0; i < inCtrlQubits.size(); ++i) {
+ bool isPosCtrl = false;
+ if (auto constOp = inCtrlValues[i].getDefiningOp()) {
+ if (auto boolAttr = dyn_cast(constOp.getValue())) {
+ isPosCtrl = boolAttr.getValue();
+ } else if (auto intAttr = dyn_cast(constOp.getValue())) {
+ isPosCtrl = (intAttr.getInt() != 0);
+ } else {
+ return op.emitError(
+ "Control value must be a boolean or integer constant");
+ }
+ } else {
+ return op.emitError("Dynamic control values are not supported");
+ }
+
+ if (isPosCtrl) {
+ inPosCtrlQubitsVec.emplace_back(inCtrlQubits[i]);
+ } else {
+ inNegCtrlQubitsVec.emplace_back(inCtrlQubits[i]);
+ }
+ }
+
+ // Process parameters (static vs dynamic)
+ SmallVector paramsMaskVec;
+ SmallVector staticParamsVec;
+ SmallVector finalParamValues;
+
+ auto maskAttr = op->getAttrOfType("params_mask");
+ auto staticParamsAttr =
+ op->getAttrOfType("static_params");
+
+ size_t totalParams = 0;
+ if (maskAttr) {
+ totalParams = maskAttr.size();
+ } else {
+ totalParams = staticParamsAttr
+ ? staticParamsAttr.size() + paramsValues.size()
+ : paramsValues.size();
+ }
+
+ size_t staticIdx = 0;
+ size_t dynamicIdx = 0;
+
+ for (size_t i = 0; i < totalParams; ++i) {
+ const bool isStatic = (maskAttr ? maskAttr[i] : false);
+
+ paramsMaskVec.emplace_back(isStatic);
+
+ if (isStatic) {
+ if (!staticParamsAttr) {
+ return op.emitError("Missing static_params for static mask");
+ }
+ staticParamsVec.emplace_back(staticParamsAttr[staticIdx++]);
+ } else {
+ if (dynamicIdx >= paramsValues.size()) {
+ return op.emitError("Too few dynamic parameters");
+ }
+ finalParamValues.emplace_back(paramsValues[dynamicIdx++]);
+ }
+ }
+
+ const auto staticParams =
+ DenseF64ArrayAttr::get(rewriter.getContext(), staticParamsVec);
+ const auto paramsMask =
+ DenseBoolArrayAttr::get(rewriter.getContext(), paramsMaskVec);
+
+ // Create the new operation
+ Operation* mqtoptOp = nullptr;
+
+#define CREATE_GATE_OP(GATE_TYPE) \
+ rewriter.create( \
+ op.getLoc(), inQubits.getTypes(), \
+ ValueRange(inPosCtrlQubitsVec).getTypes(), \
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask, \
+ finalParamValues, inQubits, inPosCtrlQubitsVec, inNegCtrlQubitsVec)
+
+ if (gateName == "Hadamard") {
+ mqtoptOp = CREATE_GATE_OP(H);
+ } else if (gateName == "Identity") {
+ mqtoptOp = CREATE_GATE_OP(I);
+ } else if (gateName == "PauliX") {
+ mqtoptOp = CREATE_GATE_OP(X);
+ } else if (gateName == "PauliY") {
+ mqtoptOp = CREATE_GATE_OP(Y);
+ } else if (gateName == "PauliZ") {
+ mqtoptOp = CREATE_GATE_OP(Z);
+ } else if (gateName == "S") {
+ mqtoptOp = CREATE_GATE_OP(S);
+ } else if (gateName == "T") {
+ mqtoptOp = CREATE_GATE_OP(T);
+ } else if (gateName == "SX") {
+ mqtoptOp = CREATE_GATE_OP(SX);
+ } else if (gateName == "ECR") {
+ mqtoptOp = CREATE_GATE_OP(ECR);
+ } else if (gateName == "SWAP") {
+ mqtoptOp = CREATE_GATE_OP(SWAP);
+ } else if (gateName == "ISWAP") {
+ if (op.getAdjoint()) {
+ mqtoptOp = CREATE_GATE_OP(iSWAPdg);
+ } else {
+ mqtoptOp = CREATE_GATE_OP(iSWAP);
+ }
+ } else if (gateName == "RX") {
+ mqtoptOp = CREATE_GATE_OP(RX);
+ } else if (gateName == "RY") {
+ mqtoptOp = CREATE_GATE_OP(RY);
+ } else if (gateName == "RZ") {
+ mqtoptOp = CREATE_GATE_OP(RZ);
+ } else if (gateName == "PhaseShift") {
+ mqtoptOp = CREATE_GATE_OP(P);
+ } else if (gateName == "CRX") {
+ // CRX gate: 1 control qubit + 1 target qubit
+ // inQubits[0] is control, inQubits[1] is target
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "CRY") {
+ // CRY gate: 1 control qubit + 1 target qubit
+ // inQubits[0] is control, inQubits[1] is target
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "CRZ") {
+ // CRZ gate: 1 control qubit + 1 target qubit
+ // inQubits[0] is control, inQubits[1] is target
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "ControlledPhaseShift") {
+ // ControlledPhaseShift gate: 1 control qubit + 1 target qubit
+ // inQubits[0] is control, inQubits[1] is target
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "IsingXY") {
+ // PennyLane IsingXY has 1 parameter (phi), OpenQASM XXPlusYY needs 2
+ // (theta, beta) Relationship: IsingXY(phi) = XXPlusYY(phi, pi) Add pi as
+ // second parameter
+ SmallVector isingxyParams(finalParamValues.begin(),
+ finalParamValues.end());
+ auto piAttr = rewriter.getF64FloatAttr(std::numbers::pi);
+ isingxyParams.push_back(
+ rewriter.create(op.getLoc(), piAttr).getResult());
+
+ SmallVector isingxyStaticParams(staticParamsVec.begin(),
+ staticParamsVec.end());
+ SmallVector isingxyParamsMask(paramsMaskVec.begin(),
+ paramsMaskVec.end());
+ isingxyParamsMask.push_back(false); // pi is a dynamic constant
+
+ auto isingxyStaticParamsAttr =
+ DenseF64ArrayAttr::get(rewriter.getContext(), isingxyStaticParams);
+ auto isingxyParamsMaskAttr =
+ DenseBoolArrayAttr::get(rewriter.getContext(), isingxyParamsMask);
+
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits.getTypes(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), isingxyStaticParamsAttr,
+ isingxyParamsMaskAttr, isingxyParams, inQubits, inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "IsingXX") {
+ mqtoptOp = CREATE_GATE_OP(RXX);
+ } else if (gateName == "IsingYY") {
+ mqtoptOp = CREATE_GATE_OP(RYY);
+ } else if (gateName == "IsingZZ") {
+ mqtoptOp = CREATE_GATE_OP(RZZ);
+ } else if (gateName == "CNOT") {
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "CY") {
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "CZ") {
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[1].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[1], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "Toffoli") {
+ // Toffoli gate: 2 control qubits + 1 target qubit
+ // inQubits[0] and inQubits[1] are controls, inQubits[2] is target
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ inPosCtrlQubitsVec.emplace_back(inQubits[1]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), inQubits[2].getType(),
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, inQubits[2], inPosCtrlQubitsVec,
+ inNegCtrlQubitsVec);
+ } else if (gateName == "CSWAP") {
+ // CSWAP gate: 1 control qubit + 2 target qubits
+ // inQubits[0] is control, inQubits[1] and inQubits[2] are targets
+ inPosCtrlQubitsVec.emplace_back(inQubits[0]);
+ mqtoptOp = rewriter.create(
+ op.getLoc(), ValueRange{inQubits[1], inQubits[2]},
+ ValueRange(inPosCtrlQubitsVec).getTypes(),
+ ValueRange(inNegCtrlQubitsVec).getTypes(), staticParams, paramsMask,
+ finalParamValues, ValueRange{inQubits[1], inQubits[2]},
+ inPosCtrlQubitsVec, inNegCtrlQubitsVec);
+ } else {
+ return op.emitError("Unsupported gate: ") << gateName;
+ }
+
+#undef CREATE_GATE_OP
+
+ // Replace the original with the new operation
+ rewriter.replaceOp(op, mqtoptOp);
+ return success();
+ }
+};
+
+struct CatalystQuantumToMQTOpt final
+ : impl::CatalystQuantumToMQTOptBase {
+ using CatalystQuantumToMQTOptBase::CatalystQuantumToMQTOptBase;
+
+ void runOnOperation() override {
+ MLIRContext* context = &getContext();
+ auto* module = getOperation();
+
+ ConversionTarget target(*context);
+ target.addLegalDialect();
+ target.addLegalDialect();
+ target.addLegalDialect();
+ target.addIllegalDialect();
+
+ // Mark operations legal that have no equivalent in the target dialect
+ target.addLegalOp<
+ catalyst::quantum::DeviceInitOp, catalyst::quantum::DeviceReleaseOp,
+ catalyst::quantum::NamedObsOp, catalyst::quantum::ExpvalOp,
+ catalyst::quantum::FinalizeOp, catalyst::quantum::ComputationalBasisOp,
+ catalyst::quantum::StateOp, catalyst::quantum::InitializeOp>();
+
+ RewritePatternSet patterns(context);
+ const CatalystQuantumToMQTOptTypeConverter typeConverter(context);
+
+ patterns
+ .add(typeConverter,
+ context);
+
+ // Type conversion boilerplate to handle function signatures and control
+ // flow See: https://www.jeremykun.com/2023/10/23/mlir-dialect-conversion
+
+ // Convert func.func signatures to use the converted types
+ populateFunctionOpInterfaceTypeConversionPattern(
+ patterns, typeConverter);
+
+ // Mark func.func as legal only if signature and body types are converted
+ target.addDynamicallyLegalOp([&](func::FuncOp op) {
+ return typeConverter.isSignatureLegal(op.getFunctionType()) &&
+ typeConverter.isLegal(&op.getBody());
+ });
+
+ // Convert return ops to match the new function result types
+ populateReturnOpTypeConversionPattern(patterns, typeConverter);
+
+ // Mark func.return as legal only if operand types match converted types
+ target.addDynamicallyLegalOp(
+ [&](const func::ReturnOp op) { return typeConverter.isLegal(op); });
+
+ // Convert call sites to use the converted argument and result types
+ populateCallOpTypeConversionPattern(patterns, typeConverter);
+
+ // Mark func.call as legal only if operand and result types are converted
+ target.addDynamicallyLegalOp(
+ [&](const func::CallOp op) { return typeConverter.isLegal(op); });
+
+ // Convert control-flow ops (cf.br, cf.cond_br, etc.)
+ populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter);
+
+ // Mark unknown ops as legal if they don't require type conversion
+ target.markUnknownOpDynamicallyLegal([&](Operation* op) {
+ return isNotBranchOpInterfaceOrReturnLikeOp(op) ||
+ isLegalForBranchOpInterfaceTypeConversionPattern(op,
+ typeConverter) ||
+ isLegalForReturnOpTypeConversionPattern(op, typeConverter);
+ });
+
+ if (failed(applyPartialConversion(module, target, std::move(patterns)))) {
+ signalPassFailure();
+ }
+ }
+};
+
+} // namespace mqt::ir::conversions
diff --git a/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt b/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt
new file mode 100644
index 0000000000..2c21fb770d
--- /dev/null
+++ b/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/CMakeLists.txt
@@ -0,0 +1,34 @@
+# 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
+
+add_mlir_library(MQTOptToCatalystQuantum MQTOptToCatalystQuantum.cpp LINK_LIBS MLIRMQTOpt DEPENDS
+ MQTOptToCatalystQuantumIncGen)
+
+target_compile_features(MQTOptToCatalystQuantum PUBLIC cxx_std_20)
+target_compile_options(MQTOptToCatalystQuantum PUBLIC -fexceptions)
+
+file(GLOB_RECURSE CONVERSION_HEADERS_SOURCE
+ "${MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR}/mlir/Conversion/MQTOptToCatalystQuantum/*.h")
+file(GLOB_RECURSE CONVERSION_HEADERS_BUILD
+ "${MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR}/mlir/Conversion/MQTOptToCatalystQuantum/*.inc")
+
+# add public headers using file sets
+target_sources(
+ MQTOptToCatalystQuantum
+ PUBLIC FILE_SET
+ HEADERS
+ BASE_DIRS
+ ${MQT_MLIR_PLUGIN_SOURCE_INCLUDE_DIR}
+ FILES
+ ${CONVERSION_HEADERS_SOURCE}
+ FILE_SET
+ HEADERS
+ BASE_DIRS
+ ${MQT_MLIR_PLUGIN_BUILD_INCLUDE_DIR}
+ FILES
+ ${CONVERSION_HEADERS_BUILD})
diff --git a/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp b/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp
new file mode 100644
index 0000000000..e0289d731e
--- /dev/null
+++ b/plugins/catalyst/lib/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp
@@ -0,0 +1,1510 @@
+/*
+ * 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/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h"
+
+#include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace mqt::ir::conversions {
+
+#define GEN_PASS_DEF_MQTOPTTOCATALYSTQUANTUM
+#include "mlir/Conversion/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc"
+
+using namespace mlir;
+using namespace mlir::arith;
+
+// Helper functions to reduce code duplication
+namespace {
+
+/// Helper struct to hold control qubit information
+struct ControlInfo {
+ SmallVector ctrlQubits;
+ SmallVector ctrlValues;
+};
+
+/// Extract and concatenate control qubits and create corresponding control
+/// values
+ControlInfo extractControlInfo(ValueRange posCtrlQubits,
+ ValueRange negCtrlQubits,
+ ConversionPatternRewriter& rewriter,
+ Location loc) {
+ ControlInfo info;
+
+ // Concatenate controls: [pos..., neg...] (preserve this order consistently)
+ info.ctrlQubits.reserve(posCtrlQubits.size() + negCtrlQubits.size());
+ info.ctrlQubits.append(posCtrlQubits.begin(), posCtrlQubits.end());
+ info.ctrlQubits.append(negCtrlQubits.begin(), negCtrlQubits.end());
+
+ if (info.ctrlQubits.empty()) {
+ return info;
+ }
+
+ // Create control values: 1 for positive controls, 0 for negative controls
+ const Value one =
+ rewriter.create(loc, /*value=*/1,
+ /*width=*/1);
+ const Value zero =
+ rewriter.create(loc, /*value=*/0,
+ /*width=*/1);
+
+ info.ctrlValues.reserve(info.ctrlQubits.size());
+ info.ctrlValues.append(posCtrlQubits.size(), one); // +controls => 1
+ info.ctrlValues.append(negCtrlQubits.size(), zero); // -controls => 0
+
+ return info;
+}
+
+/// Helper function to extract operands and control info - for more complex
+/// cases
+template struct ExtractedOperands {
+ ValueRange inQubits;
+ ControlInfo ctrlInfo;
+};
+
+template
+ExtractedOperands
+extractOperands(OpAdaptor adaptor, ConversionPatternRewriter& rewriter,
+ Location loc) {
+ const ValueRange inQubits = adaptor.getInQubits();
+ const ValueRange posCtrlQubits = adaptor.getPosCtrlInQubits();
+ const ValueRange negCtrlQubits = adaptor.getNegCtrlInQubits();
+
+ const ControlInfo ctrlInfo =
+ extractControlInfo(posCtrlQubits, negCtrlQubits, rewriter, loc);
+
+ return {inQubits, ctrlInfo};
+}
+
+} // anonymous namespace
+
+class MQTOptToCatalystQuantumTypeConverter final : public TypeConverter {
+public:
+ explicit MQTOptToCatalystQuantumTypeConverter(MLIRContext* ctx) {
+ // Identity conversion for types that don't need transformation
+ addConversion([](const Type type) { return type; });
+
+ // Convert MemRef of MQTOpt QubitType to Catalyst QuregType
+ addConversion([ctx](MemRefType memrefType) -> Type {
+ if (auto qubitType =
+ dyn_cast(memrefType.getElementType())) {
+ return catalyst::quantum::QuregType::get(ctx);
+ }
+ return memrefType;
+ });
+
+ // Convert MQTOpt QubitType to Catalyst QubitType
+ addConversion([ctx](opt::QubitType /*type*/) -> Type {
+ return catalyst::quantum::QubitType::get(ctx);
+ });
+ }
+};
+
+struct ConvertMQTOptAlloc final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(memref::AllocOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Only convert memrefs of qubit type
+ auto memrefType = cast(op.getType());
+ if (!isa(memrefType.getElementType())) {
+ return failure();
+ }
+
+ // Prepare the result type(s)
+ const auto resultType =
+ catalyst::quantum::QuregType::get(rewriter.getContext());
+
+ // Get the size from memref type or dynamic operands
+ Value size = nullptr;
+ mlir::IntegerAttr nqubitsAttr = nullptr;
+
+ // Check if this is a statically shaped memref
+ if (memrefType.hasStaticShape() && memrefType.getNumElements() >= 0) {
+ // For static memref: use attribute (no operand)
+ nqubitsAttr = rewriter.getI64IntegerAttr(memrefType.getNumElements());
+ } else {
+ // For dynamic memref: use operand (no attribute)
+ auto dynamicOperands = adaptor.getDynamicSizes();
+ size = dynamicOperands.empty() ? nullptr : dynamicOperands[0];
+ }
+
+ // Replace with quantum alloc operation
+ rewriter.replaceOpWithNewOp(op, resultType,
+ size, nqubitsAttr);
+
+ return success();
+ }
+};
+
+struct ConvertMQTOptDealloc final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(memref::DeallocOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Only convert memrefs of qubit type
+ auto memrefType = cast(op.getMemref().getType());
+ if (!isa(memrefType.getElementType())) {
+ return failure();
+ }
+
+ // Create the new operation
+ const auto catalystOp = rewriter.create(
+ op.getLoc(), TypeRange({}), adaptor.getMemref());
+
+ // Replace the original with the new operation
+ rewriter.replaceOp(op, catalystOp);
+ return success();
+ }
+};
+
+struct ConvertMQTOptMeasure final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::MeasureOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+
+ // Extract operand(s)
+ auto inQubit = adaptor.getInQubit();
+
+ // Prepare the result type(s)
+ auto qubitType = catalyst::quantum::QubitType::get(rewriter.getContext());
+ auto bitType = rewriter.getI1Type();
+
+ // Create the new operation
+ const auto catalystOp = rewriter.create(
+ op.getLoc(), bitType, qubitType, inQubit,
+ /*optional::mlir::IntegerAttr postselect=*/nullptr);
+
+ // Replace all uses of both results and then erase the operation
+ const auto catalystMeasure = catalystOp->getResult(0);
+ const auto catalystQubit = catalystOp->getResult(1);
+ rewriter.replaceOp(op, ValueRange{catalystQubit, catalystMeasure});
+ return success();
+ }
+};
+
+struct ConvertMQTOptLoad final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(memref::LoadOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Only convert loads of qubit type
+ if (!isa(op.getType())) {
+ return failure();
+ }
+
+ // Prepare the result type(s)
+ auto resultType = catalyst::quantum::QubitType::get(rewriter.getContext());
+
+ // Get index (assuming single index for 1D memref)
+ auto indices = adaptor.getIndices();
+ Value index = indices.empty() ? nullptr : indices[0];
+
+ // Convert index type to i64 if needed
+ if (index && mlir::isa(index.getType())) {
+ index = rewriter.create(op.getLoc(),
+ rewriter.getI64Type(), index);
+ }
+
+ // Create the new operation
+ auto catalystOp = rewriter.create(
+ op.getLoc(), resultType, adaptor.getMemref(), index, nullptr);
+
+ // Replace the load operation with the extracted qubit
+ rewriter.replaceOp(op, catalystOp.getResult());
+ return success();
+ }
+};
+
+struct ConvertMQTOptStore final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(memref::StoreOp op, OpAdaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Only convert stores to memrefs with qubit element type
+ auto memrefType = cast(op.getMemRef().getType());
+ if (!isa(memrefType.getElementType())) {
+ return failure();
+ }
+
+ // Get indices (assuming single index for 1D memref)
+ auto indices = adaptor.getIndices();
+ Value index = indices.empty() ? nullptr : indices[0];
+
+ // Convert index type to i64 if needed
+ if (index && mlir::isa(index.getType())) {
+ index = rewriter.create(op.getLoc(),
+ rewriter.getI64Type(), index);
+ }
+
+ // Prepare the result type(s)
+ auto resultType = catalyst::quantum::QuregType::get(rewriter.getContext());
+
+ // Create the new operation
+ rewriter.create(op.getLoc(), resultType,
+ adaptor.getMemref(), index,
+ nullptr, adaptor.getValue());
+
+ // Erase the original store operation (store has no results to replace)
+ rewriter.eraseOp(op);
+ return success();
+ }
+};
+
+template
+struct ConvertMQTOptSimpleGate final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(MQTGateOp op, typename MQTGateOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // BarrierOp has no semantic effect.
+ if (std::is_same_v) {
+ rewriter.eraseOp(op);
+ return success();
+ }
+
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // Gate name may depend on number of controls
+ const StringRef gateName =
+ getGateName(extracted.ctrlInfo.ctrlQubits.size());
+ if (gateName.empty()) {
+ op->emitError() << "Unsupported controlled gate for op: "
+ << op->getName();
+ return failure();
+ }
+
+ // Sanity: lengths must match, or the op verifier will complain.
+ if (extracted.ctrlInfo.ctrlQubits.size() !=
+ extracted.ctrlInfo.ctrlValues.size()) {
+ op->emitError() << "control qubits and control values size mismatch";
+ return failure();
+ }
+
+ // Create CustomOp
+ auto custom = rewriter.create(
+ op.getLoc(),
+ /*gate=*/gateName,
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/adaptor.getParams(),
+ /*adjoint=*/false);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(custom.getOutQubits().begin(),
+ custom.getOutQubits().end());
+ replacements.append(custom.getOutCtrlQubits().begin(),
+ custom.getOutCtrlQubits().end());
+
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+
+private:
+ // Is specialized for each gate type
+ static StringRef getGateName(std::size_t numControls);
+};
+
+template
+struct ConvertMQTOptAdjointGate final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(MQTGateOp op, typename MQTGateOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Get the base gate name and whether it is an adjoint version
+ const auto& [gateName, adjoint] = getGateInfo();
+
+ // Extract control information
+ const ControlInfo ctrlInfo =
+ extractControlInfo(adaptor.getPosCtrlInQubits(),
+ adaptor.getNegCtrlInQubits(), rewriter, op.getLoc());
+
+ // Create CustomOp with adjoint flag
+ auto catalystOp = rewriter.create(
+ op.getLoc(),
+ /*gate=*/gateName,
+ /*in_qubits=*/adaptor.getInQubits(),
+ /*in_ctrl_qubits=*/ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/ctrlInfo.ctrlValues,
+ /*params=*/adaptor.getParams(),
+ /*adjoint=*/adjoint);
+
+ rewriter.replaceOp(op, catalystOp);
+ return success();
+ }
+
+private:
+ template static std::pair getGateInfo() {
+ if constexpr (std::is_same_v) {
+ return {"S", true};
+ } else if constexpr (std::is_same_v) {
+ return {"T", true};
+ } else if constexpr (std::is_same_v) {
+ return {"ISWAP", true};
+ } else if constexpr (std::is_same_v) {
+ return {"SX", true};
+ }
+ // Default case
+ return {"", false};
+ }
+};
+
+// Conversions of unsupported gates, which need decomposition
+template <>
+struct ConvertMQTOptSimpleGate final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::VOp op, opt::VOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // V = RZ(π/2) RY(π/2) RZ(-π/2)
+ auto pi2 = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(std::numbers::pi / 2.0));
+
+ // Create the decomposed operations
+ auto rz1 = rewriter.create(
+ op.getLoc(),
+ /*gate_name=*/"RZ",
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ auto ry = rewriter.create(
+ op.getLoc(),
+ /*gate_name=*/"RY",
+ /*in_qubits=*/rz1.getOutQubits(),
+ /*in_ctrl_qubits=*/rz1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ auto rz2 = rewriter.create(
+ op.getLoc(),
+ /*gate_name=*/"RZ",
+ /*in_qubits=*/ry.getOutQubits(),
+ /*in_ctrl_qubits=*/ry.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/true);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(rz2.getOutQubits().begin(), rz2.getOutQubits().end());
+ replacements.append(rz2.getOutCtrlQubits().begin(),
+ rz2.getOutCtrlQubits().end());
+
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::VdgOp op, opt::VdgOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // V† = RZ(π/2) RY(-π/2) RZ(-π/2)
+ auto negPi2 = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(-std::numbers::pi / 2.0));
+
+ // Create the decomposed operations
+ auto rz1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{negPi2},
+ /*adjoint=*/true);
+
+ auto ry = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RY",
+ /*in_qubits=*/rz1.getOutQubits(),
+ /*in_ctrl_qubits=*/rz1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{negPi2},
+ /*adjoint=*/false);
+
+ auto rz2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/ry.getOutQubits(),
+ /*in_ctrl_qubits=*/ry.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{negPi2},
+ /*adjoint=*/false);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(rz2.getOutQubits().begin(), rz2.getOutQubits().end());
+ replacements.append(rz2.getOutCtrlQubits().begin(),
+ rz2.getOutCtrlQubits().end());
+
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::DCXOp op, opt::DCXOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // DCX = CNOT(q2,q1) CNOT(q1,q2)
+ auto cnot1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"CNOT",
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ auto cnot2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"CNOT",
+ /*in_qubits=*/
+ ValueRange{cnot1.getOutQubits()[1], cnot1.getOutQubits()[0]},
+ /*in_ctrl_qubits=*/cnot1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(cnot2.getOutQubits().begin(),
+ cnot2.getOutQubits().end());
+ replacements.append(cnot2.getOutCtrlQubits().begin(),
+ cnot2.getOutCtrlQubits().end());
+
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::RZXOp op, opt::RZXOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // RZX(q0, q1; θ) = H(q1) · RZZ(q0, q1; θ) · H(q1)
+ // H gates stay uncontrolled; they cancel if control on RZZ is not active
+
+ // H on q1
+ auto h1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"Hadamard",
+ /*in_qubits=*/ValueRange{extracted.inQubits[1]},
+ /*in_ctrl_qubits=*/ValueRange{},
+ /*in_ctrl_values=*/ValueRange{},
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ // RZZ on (q0, q1')
+ if (adaptor.getParams().empty()) {
+ return op.emitError("RZX expects one parameter");
+ }
+ auto rzz = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"IsingZZ",
+ /*in_qubits=*/ValueRange{extracted.inQubits[0], h1.getOutQubits()[0]},
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/adaptor.getParams(),
+ /*adjoint=*/false);
+
+ // H on q1''
+ auto h2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"Hadamard",
+ /*in_qubits=*/ValueRange{rzz.getOutQubits()[1]},
+ /*in_ctrl_qubits=*/ValueRange{},
+ /*in_ctrl_values=*/ValueRange{},
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ // Final results in mqt.opt ordering (targets..., controls...)
+ SmallVector finalResults;
+ finalResults.push_back(rzz.getOutQubits()[0]); // target0 final
+ finalResults.push_back(h2.getOutQubits()[0]); // target1 final
+ finalResults.append(rzz.getOutCtrlQubits().begin(),
+ rzz.getOutCtrlQubits().end()); // controls
+
+ rewriter.replaceOp(op, finalResults);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::GPhaseOp op, opt::GPhaseOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract control information using helper function (no input qubits for
+ // GPhase)
+ auto ctrlInfo =
+ extractControlInfo(adaptor.getPosCtrlInQubits(),
+ adaptor.getNegCtrlInQubits(), rewriter, op.getLoc());
+ const auto params = adaptor.getParams();
+ if (params.empty()) {
+ return op.emitError("GlobalPhaseOp requires exactly one parameter");
+ }
+
+ // Create output types for GlobalPhaseOp (control qubits only)
+ const Type qubitType =
+ catalyst::quantum::QubitType::get(rewriter.getContext());
+ const SmallVector outCtrlTypes(ctrlInfo.ctrlQubits.size(), qubitType);
+
+ auto gphase = rewriter.create(
+ op.getLoc(), TypeRange(outCtrlTypes), params[0], false,
+ ctrlInfo.ctrlQubits, ctrlInfo.ctrlValues);
+
+ // Replace the original operation with the decomposition
+ rewriter.replaceOp(op, gphase.getResults());
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::UOp op, opt::UOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // Extract parameters
+ SmallVector paramValues;
+ auto dynamicParams = adaptor.getParams();
+ auto staticParams = op.getStaticParams();
+ auto paramMask = op.getParamsMask();
+
+ // There must be exactly 3 parameters
+ constexpr size_t numParams = 3;
+ for (size_t i = 0, dynIdx = 0, statIdx = 0; i < numParams; ++i) {
+ if (paramMask.has_value()) {
+ if ((*paramMask)[i]) {
+ // Static parameter
+ auto attr = (*staticParams)[statIdx++];
+ auto floatAttr = rewriter.getF64FloatAttr(attr);
+ auto constOp = rewriter.create(op.getLoc(), floatAttr);
+ paramValues.push_back(constOp);
+ } else {
+ // Dynamic parameter
+ paramValues.push_back(dynamicParams[dynIdx++]);
+ }
+ } else if (staticParams.has_value()) {
+ // All static
+ auto attr = (*staticParams)[i];
+ auto floatAttr = rewriter.getF64FloatAttr(attr);
+ auto constOp = rewriter.create(op.getLoc(), floatAttr);
+ paramValues.push_back(constOp);
+ } else {
+ // All dynamic
+ paramValues.push_back(dynamicParams[i]);
+ }
+ }
+ // Now paramValues[0] = θ, [1] = φ, [2] = λ
+ auto theta = paramValues[0];
+ auto phi = paramValues[1];
+ auto lambda = paramValues[2];
+
+ // Based on
+ // https://docs.quantum.ibm.com/api/qiskit/0.24/qiskit.circuit.library.UGate
+ // U(θ, φ, λ) = RZ(φ − π⁄2) ⋅ RX(π⁄2) ⋅ RZ(π − θ) ⋅ RX(π⁄2) ⋅ RZ(λ − π⁄2)
+ // Note: The MQT UOp uses U(θ/2, φ, λ)
+ auto pi = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(std::numbers::pi));
+ auto pi2 = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(std::numbers::pi / 2.0));
+
+ // Compute φ - π/2
+ auto phiMinusPi2 = rewriter.create(op.getLoc(), phi, pi2);
+ // Compute π - θ/2
+ auto two =
+ rewriter.create(op.getLoc(), rewriter.getF64FloatAttr(2.0));
+ auto theta2 = rewriter.create(op.getLoc(), theta, two);
+ auto piMinusTheta2 = rewriter.create(op.getLoc(), pi, theta2);
+ // Compute λ - π/2
+ auto lambdaMinusPi2 = rewriter.create(op.getLoc(), lambda, pi2);
+
+ // RZ(λ − π/2)
+ auto rz1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{lambdaMinusPi2},
+ /*adjoint=*/false);
+
+ // RX(π/2)
+ auto rx1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RX",
+ /*in_qubits=*/rz1.getOutQubits(),
+ /*in_ctrl_qubits=*/rz1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ // RZ(π − θ)
+ auto rz2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/rx1.getOutQubits(),
+ /*in_ctrl_qubits=*/rx1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{piMinusTheta2},
+ /*adjoint=*/false);
+
+ // RX(π/2)
+ auto rx2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RX",
+ /*in_qubits=*/rz2.getOutQubits(),
+ /*in_ctrl_qubits=*/rz2.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ // RZ(φ − π/2)
+ auto rz3 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/rx2.getOutQubits(),
+ /*in_ctrl_qubits=*/rx2.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{phiMinusPi2},
+ /*adjoint=*/false);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(rz3.getOutQubits().begin(), rz3.getOutQubits().end());
+ replacements.append(rz3.getOutCtrlQubits().begin(),
+ rz3.getOutCtrlQubits().end());
+
+ // Replace the original U gate with the decomposed sequence
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::U2Op op, opt::U2Op::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // Extract parameters
+ SmallVector paramValues;
+ auto dynamicParams = adaptor.getParams();
+ auto staticParams = op.getStaticParams();
+ auto paramMask = op.getParamsMask();
+
+ // There must be exactly 2 parameters
+ constexpr size_t numParams = 2;
+ for (size_t i = 0, dynIdx = 0, statIdx = 0; i < numParams; ++i) {
+ if (paramMask.has_value()) {
+ if ((*paramMask)[i]) {
+ // Static parameter
+ auto attr = (*staticParams)[statIdx++];
+ auto floatAttr = rewriter.getF64FloatAttr(attr);
+ auto constOp = rewriter.create(op.getLoc(), floatAttr);
+ paramValues.push_back(constOp);
+ } else {
+ // Dynamic parameter
+ paramValues.push_back(dynamicParams[dynIdx++]);
+ }
+ } else if (staticParams.has_value()) {
+ // All static
+ auto attr = (*staticParams)[i];
+ auto floatAttr = rewriter.getF64FloatAttr(attr);
+ auto constOp = rewriter.create(op.getLoc(), floatAttr);
+ paramValues.push_back(constOp);
+ } else {
+ // All dynamic
+ paramValues.push_back(dynamicParams[i]);
+ }
+ }
+ // Now paramValues [0] = φ, [1] = λ
+ auto phi = paramValues[0];
+ auto lambda = paramValues[1];
+
+ // U2(φ, λ) = U(π/2, φ, λ) = RZ(φ − π⁄2) ⋅ RX(π⁄2) ⋅ RZ(3/4 π) ⋅ RX(π⁄2) ⋅
+ // RZ(λ − π⁄2)
+ auto pi2 = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(std::numbers::pi / 2.0));
+ auto pi4 = rewriter.create(
+ op.getLoc(), rewriter.getF64FloatAttr(std::numbers::pi / 4.0));
+ auto three =
+ rewriter.create(op.getLoc(), rewriter.getF64FloatAttr(3.0));
+ auto pi34 = rewriter.create(op.getLoc(), pi4, three);
+
+ // Compute φ - π/2
+ auto phiMinusPi2 = rewriter.create(op.getLoc(), phi, pi2);
+ // Compute λ - π/2
+ auto lambdaMinusPi2 = rewriter.create(op.getLoc(), lambda, pi2);
+
+ // RZ(λ − π/2)
+ auto rz1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/extracted.inQubits,
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{lambdaMinusPi2},
+ /*adjoint=*/false);
+
+ // RX(π/2)
+ auto rx1 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RX",
+ /*in_qubits=*/rz1.getOutQubits(),
+ /*in_ctrl_qubits=*/rz1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ // RZ(3/4 π)
+ auto rz2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/rx1.getOutQubits(),
+ /*in_ctrl_qubits=*/rx1.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi34},
+ /*adjoint=*/false);
+
+ // RX(π/2)
+ auto rx2 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RX",
+ /*in_qubits=*/rz2.getOutQubits(),
+ /*in_ctrl_qubits=*/rz2.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{pi2},
+ /*adjoint=*/false);
+
+ // RZ(φ − π/2)
+ auto rz3 = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"RZ",
+ /*in_qubits=*/rx2.getOutQubits(),
+ /*in_ctrl_qubits=*/rx2.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{phiMinusPi2},
+ /*adjoint=*/false);
+
+ // ---- Replace: CustomOp results are (out_qubits, out_ctrl_qubits) ----
+ SmallVector replacements;
+ replacements.append(rz3.getOutQubits().begin(), rz3.getOutQubits().end());
+ replacements.append(rz3.getOutCtrlQubits().begin(),
+ rz3.getOutCtrlQubits().end());
+
+ // Replace the original U gate with the decomposed sequence
+ rewriter.replaceOp(op, replacements);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::PeresOp op, opt::PeresOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // Peres = CNOT(q0, q1) ; X(q0)
+
+ // CNOT(q0, q1)
+ auto cnot = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"CNOT",
+ /*in_qubits=*/ValueRange{extracted.inQubits[0], extracted.inQubits[1]},
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ const Value q0AfterCnot = cnot.getOutQubits()[0];
+ const Value q1AfterCnot = cnot.getOutQubits()[1];
+
+ // X(q0')
+ auto x = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"PauliX",
+ /*in_qubits=*/ValueRange{q0AfterCnot},
+ /*in_ctrl_qubits=*/cnot.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ const Value q0Final = x.getOutQubits()[0]; // target0
+ const Value q1Final = q1AfterCnot; // target1
+
+ // Final: (targets..., controls...)
+ SmallVector finalResults;
+ finalResults.push_back(q0Final);
+ finalResults.push_back(q1Final);
+ finalResults.append(x.getOutCtrlQubits().begin(),
+ x.getOutCtrlQubits().end());
+
+ rewriter.replaceOp(op, finalResults);
+ return success();
+ }
+};
+
+template <>
+struct ConvertMQTOptSimpleGate final
+ : OpConversionPattern {
+ using OpConversionPattern::OpConversionPattern;
+
+ LogicalResult
+ matchAndRewrite(opt::PeresdgOp op, opt::PeresdgOp::Adaptor adaptor,
+ ConversionPatternRewriter& rewriter) const override {
+ // Extract operands and control information using helper function
+ auto extracted = extractOperands(adaptor, rewriter, op.getLoc());
+
+ // Peres† = X(q0) ; CNOT(q0, q1)
+
+ // X(q0)
+ auto x = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"PauliX",
+ /*in_qubits=*/ValueRange{extracted.inQubits[0]},
+ /*in_ctrl_qubits=*/extracted.ctrlInfo.ctrlQubits,
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ const Value q0AfterX = x.getOutQubits()[0];
+
+ // CNOT(q0', q1)
+ auto cnot = rewriter.create(
+ op.getLoc(),
+ /*gate=*/"CNOT",
+ /*in_qubits=*/ValueRange{q0AfterX, extracted.inQubits[1]},
+ /*in_ctrl_qubits=*/x.getOutCtrlQubits(),
+ /*in_ctrl_values=*/extracted.ctrlInfo.ctrlValues,
+ /*params=*/ValueRange{},
+ /*adjoint=*/false);
+
+ // Final: (targets..., controls...)
+ SmallVector finalResults;
+ finalResults.push_back(cnot.getOutQubits()[0]); // q0_final
+ finalResults.push_back(cnot.getOutQubits()[1]); // q1_final
+ finalResults.append(cnot.getOutCtrlQubits().begin(),
+ cnot.getOutCtrlQubits().end()); // controls
+
+ rewriter.replaceOp(op, finalResults);
+ return success();
+ }
+};
+
+// -- IOp (Identity)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "Identity";
+}
+
+// -- XOp (PauliX, CNOT, Toffoli)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ if (numControls == 1) {
+ return "CNOT";
+ }
+ if (numControls == 2) {
+ return "Toffoli";
+ }
+ // 0 or 3+ controls: use PauliX with explicit control qubits
+ return "PauliX";
+}
+
+// -- YOp (PauliY, CY for 1 control, PauliY for 2+ controls)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ // CY is the special name for exactly 1 control
+ if (numControls == 1) {
+ return "CY";
+ }
+ // 0 or 2+ controls: use PauliY with explicit control qubits
+ return "PauliY";
+}
+
+// -- ZOp (PauliZ, CZ for 1 control, PauliZ for 2+ controls)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ // CZ is the special name for exactly 1 control
+ if (numControls == 1) {
+ return "CZ";
+ }
+ // 0 or 2+ controls: use PauliZ with explicit control qubits
+ return "PauliZ";
+}
+
+// -- HOp (Hadamard)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "Hadamard";
+}
+
+// -- SOP (S)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "S";
+}
+
+// -- SXOp (Sqrt X)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "SX";
+}
+
+// -- TOP (T)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "T";
+}
+
+// -- ECROp (ECR)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "ECR";
+}
+
+// -- SWAPOp (SWAP)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ const std::size_t numControls) {
+ if (numControls == 0) {
+ return "SWAP";
+ }
+ if (numControls == 1) {
+ return "CSWAP";
+ }
+ // Unsupported: will be caught in matchAndRewrite
+ return "";
+}
+
+// -- iSWAPOp (iSWAP)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "ISWAP";
+}
+
+// -- RXOp (RX, CRX)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ if (numControls == 0) {
+ return "RX";
+ }
+ if (numControls == 1) {
+ return "CRX";
+ }
+ // Unsupported: will be caught in matchAndRewrite
+ return "";
+}
+
+// -- RYOp (RY, CRY)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ if (numControls == 0) {
+ return "RY";
+ }
+ if (numControls == 1) {
+ return "CRY";
+ }
+ // Unsupported: will be caught in matchAndRewrite
+ return "";
+}
+
+// -- RZOp (RZ, CRZ)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ if (numControls == 0) {
+ return "RZ";
+ }
+ if (numControls == 1) {
+ return "CRZ";
+ }
+ // Unsupported: will be caught in matchAndRewrite
+ return "";
+}
+
+// -- POp (PhaseShift, ControlledPhaseShift)
+template <>
+StringRef
+ConvertMQTOptSimpleGate::getGateName(const std::size_t numControls) {
+ if (numControls == 0) {
+ return "PhaseShift";
+ }
+ if (numControls == 1) {
+ return "ControlledPhaseShift";
+ }
+ // Unsupported: will be caught in matchAndRewrite
+ return "";
+}
+
+// -- RXXOp (IsingXX)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "IsingXX";
+}
+
+// -- RYYOp (IsingYY)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "IsingYY";
+}
+
+// -- RZZ (IsingZZ)
+template <>
+StringRef ConvertMQTOptSimpleGate::getGateName(
+ [[maybe_unused]] std::size_t numControls) {
+ return "IsingZZ";
+}
+
+template <>
+struct ConvertMQTOptSimpleGate