diff --git a/.github/workflows/ci_catalyst.yml b/.github/workflows/ci_catalyst.yml new file mode 100644 index 000000000..418da4e8e --- /dev/null +++ b/.github/workflows/ci_catalyst.yml @@ -0,0 +1,122 @@ +name: Catalyst CI +on: + push: + branches: + - main + paths: + - "mlir/**" + pull_request: + paths: + - "mlir/**" + - ".github/workflows/ci_catalyst.yml" + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + cpp-test-catalyst: + name: 🇨‌ Test MLIR with LLVM@${{ matrix.llvm-version }} + runs-on: macos-14 + strategy: + matrix: + llvm-version: [19] + env: + CMAKE_BUILD_PARALLEL_LEVEL: 4 + CTEST_PARALLEL_LEVEL: 4 + FORCE_COLOR: 3 + steps: + # check out the repository + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # set up ccache for faster C++ builds + - name: Setup ccache + uses: Chocobo1/setup-ccache-action@v1 + with: + prepend_symlinks_to_path: false + override_cache_key: c++-tests-mlir + + # set up mold as linker for faster C++ builds + - name: Set up mold as linker + uses: rui314/setup-mold@v1 + + # set up uv for faster Python package management + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v6 + with: + python-version: 3.13 + activate-environment: true + + # make sure ninja is installed + - name: Install Ninja + run: uv tool install ninja + + # make sure the lit test runner is installed + - name: Install lit + run: uv pip install lit + + # install LLVM 19 and MLIR via Homebrew and set up environment variables + - name: Install LLVM and MLIR via Homebrew + run: | + brew install llvm@19 + brew link --force --overwrite llvm@19 + echo "CC=$(brew --prefix llvm@19)/bin/clang" >> $GITHUB_ENV + echo "CXX=$(brew --prefix llvm@19)/bin/clang++" >> $GITHUB_ENV + echo "MLIR_DIR=$(brew --prefix llvm@19)/lib/cmake/mlir" >> $GITHUB_ENV + echo "LLVM_DIR=$(brew --prefix llvm@19)/lib/cmake/llvm" >> $GITHUB_ENV + echo "$(brew --prefix llvm@19)/bin" >> $GITHUB_PATH + + # clone PennyLane Catalyst (from patched branch) - excl. submodules + - name: Clone PennyLane Catalyst + run: | + git clone --branch mqt_integration --depth=1 https://github.com/flowerthrower/catalyst.git catalyst + + - name: Set macOS deployment target for linker + run: echo "MACOSX_DEPLOYMENT_TARGET=14.0" >> $GITHUB_ENV + + # configure only MLIRQuantum dialect (skip building tools) + - name: Configure MLIRQuantum dialect + run: | + cmake -G Ninja -S catalyst/mlir -B catalyst/mlir/build \ + -DCMAKE_BUILD_TYPE=Release \ + -DMLIR_DIR=$MLIR_DIR \ + -DLLVM_DIR=$LLVM_DIR \ + -DCATALYST_BUILD_TOOLS=OFF \ + -DEnzyme_DIR="none" + + # build MLIRQuantum + - name: Build MLIRQuantum dialect + run: | + ninja -C catalyst/mlir/build MLIRQuantum + + - name: Check Catalyst CMake config + run: test -f catalyst/mlir/build/lib/cmake/catalyst/CatalystConfig.cmake + + # configure the project with CMake + - name: Configure CMake for MLIR + run: | + cmake -G Ninja -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_MQT_CORE_MLIR=ON \ + -DENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN=ON \ + -DCatalyst_DIR="$PWD/catalyst/mlir/build/lib/cmake/catalyst" + + # build the project + - name: Build MLIR quantum-opt + run: cmake --build build --config Release --target quantum-opt + + # run conversion pipeline + - name: Run conversion -> catalystquantum-to-mqtopt + run: | + ./build/mlir/tools/quantum-opt/quantum-opt \ + --pass-pipeline="builtin.module(catalystquantum-to-mqtopt)" \ + mlir/test/Conversion/quantum.mlir + - name: Run conversion -> mqtopt-to-catalystquantum + run: | + ./build/mlir/tools/quantum-opt/quantum-opt \ + --pass-pipeline="builtin.module(mqtopt-to-catalystquantum)" \ + mlir/test/Conversion/mqtopt.mlir diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 5f524f93e..1bd235a84 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -73,3 +73,23 @@ endif() # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) + +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + # Manually detect the installed Catalyst Python and get its cmake directory. + execute_process( + COMMAND "${Python_EXECUTABLE}" -c + "import catalyst, os; print(os.path.dirname(catalyst.__file__))" + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE Python_Catalyst_DIR + ERROR_QUIET) + message(STATUS "Found Catalyst package: ${Python_Catalyst_DIR}") + + # TODO: once the Catalyst Python package provides the necessary files set(Catalyst_DIR + # "${Python_Catalyst_DIR}/mlir/build/lib/cmake/catalyst") + if(Catalyst_DIR) + list(APPEND CMAKE_PREFIX_PATH "${Catalyst_DIR}") + find_package(Catalyst REQUIRED) + else() + message(STATUS "Could not detect Catalyst CMake directory") + endif() +endif() diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index 91e4de8d0..433132289 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -12,9 +12,8 @@ set(MQT_MLIR_MIN_VERSION 19.0) # MLIR must be installed on the system find_package(MLIR REQUIRED CONFIG) -if(MLIR_VERSION VERSION_LESS MQT_MLIR_MIN_VERSION) - message(FATAL_ERROR "MLIR version must be at least ${MQT_MLIR_MIN_VERSION}") -endif() +# if(MLIR_VERSION VERSION_LESS MQT_MLIR_MIN_VERSION) message(FATAL_ERROR "MLIR version must be at +# least ${MQT_MLIR_MIN_VERSION} but found ${MLIR_VERSION}") endif() message(STATUS "Using MLIRConfig.cmake in: ${MLIR_DIR}") message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") @@ -26,6 +25,13 @@ string(REPLACE "." ";" MLIR_VERSION_COMPONENTS ${MLIR_VERSION}) list(GET MLIR_VERSION_COMPONENTS 0 MLIR_VERSION_MAJOR) add_compile_definitions(MLIR_VERSION_MAJOR=${MLIR_VERSION_MAJOR}) +option(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN "Enable Catalyst conversion passes" OFF) +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + message(STATUS "Found MLIR version ${MLIR_VERSION_MAJOR}.") + list(APPEND CMAKE_MODULE_PATH "${CATALYST_CMAKE_DIR}") + list(APPEND MLIR_INCLUDE_DIRS "${CATALYST_INCLUDE_DIRS}") +endif() + # Include the TableGen, LLVM and MLIR CMake modules. include(TableGen) include(AddLLVM) diff --git a/mlir/include/mlir/CMakeLists.txt b/mlir/include/mlir/CMakeLists.txt index 385d04e35..9add328fe 100644 --- a/mlir/include/mlir/CMakeLists.txt +++ b/mlir/include/mlir/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(Dialect) +add_subdirectory(Conversion) diff --git a/mlir/include/mlir/Conversion/CMakeLists.txt b/mlir/include/mlir/Conversion/CMakeLists.txt new file mode 100644 index 000000000..e47cb0322 --- /dev/null +++ b/mlir/include/mlir/Conversion/CMakeLists.txt @@ -0,0 +1,11 @@ +# 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 + +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + add_subdirectory(Catalyst) +endif() diff --git a/mlir/include/mlir/Conversion/Catalyst/CMakeLists.txt b/mlir/include/mlir/Conversion/Catalyst/CMakeLists.txt new file mode 100644 index 000000000..6acd44dea --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CMakeLists.txt b/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CMakeLists.txt new file mode 100644 index 000000000..dcfbcd032 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h b/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h new file mode 100644 index 000000000..92e003093 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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 mlir::mqt::ir::conversions { + +#define GEN_PASS_DECL +#include "mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc" + +} // namespace mlir::mqt::ir::conversions diff --git a/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.td b/mlir/include/mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.td new file mode 100644 index 000000000..b4cde9a45 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/CMakeLists.txt b/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/CMakeLists.txt new file mode 100644 index 000000000..efe8759c1 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h b/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h new file mode 100644 index 000000000..d77954343 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/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 mlir::mqt::ir::conversions { + +#define GEN_PASS_DECL +#include "mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc" + +#define GEN_PASS_REGISTRATION +#include "mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc" + +} // namespace mlir::mqt::ir::conversions diff --git a/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td b/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td new file mode 100644 index 000000000..f7e02ef87 --- /dev/null +++ b/mlir/include/mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.td @@ -0,0 +1,36 @@ +// 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. + The following operations are currently NOT converted (and instead marked legal): + - IOp + - GPhaseOp + - BarrierOp + - SOp, SdgOp + - TOp, TdgOp + - VOp, VdgOp + - UOp, U2Op + - SXOp, SXdgOp + - iSWAPOp, iSWAPdgOp + - PeresOp, PeresdgOp + - DCXOp + - ECROp + - RXXOp, RYYOp, RZZOp, RZXOp + - XXminusYY, XXplusYY + }]; + let dependentDialects = [ + "catalyst::quantum::QuantumDialect", + "::mqt::ir::opt::MQTOptDialect" + ]; +} diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt index 385d04e35..9add328fe 100644 --- a/mlir/lib/CMakeLists.txt +++ b/mlir/lib/CMakeLists.txt @@ -7,3 +7,4 @@ # Licensed under the MIT License add_subdirectory(Dialect) +add_subdirectory(Conversion) diff --git a/mlir/lib/Conversion/CMakeLists.txt b/mlir/lib/Conversion/CMakeLists.txt new file mode 100644 index 000000000..e47cb0322 --- /dev/null +++ b/mlir/lib/Conversion/CMakeLists.txt @@ -0,0 +1,11 @@ +# 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 + +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + add_subdirectory(Catalyst) +endif() diff --git a/mlir/lib/Conversion/Catalyst/CMakeLists.txt b/mlir/lib/Conversion/Catalyst/CMakeLists.txt new file mode 100644 index 000000000..6acd44dea --- /dev/null +++ b/mlir/lib/Conversion/Catalyst/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/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CMakeLists.txt b/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CMakeLists.txt new file mode 100644 index 000000000..27b01a666 --- /dev/null +++ b/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CMakeLists.txt @@ -0,0 +1,14 @@ +# 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 + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library(CatalystQuantumToMQTOpt ${CONVERSION_SOURCES} LINK_LIBS ${LIBRARIES} DEPENDS + CatalystQuantumToMQTOptIncGen) + +target_link_libraries(CatalystQuantumToMQTOpt PRIVATE MLIRQuantum) diff --git a/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp b/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp new file mode 100644 index 000000000..9278108bf --- /dev/null +++ b/mlir/lib/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.cpp @@ -0,0 +1,473 @@ +/* + * 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/Catalyst/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 + +namespace mlir::mqt::ir::conversions { + +#define GEN_PASS_DEF_CATALYSTQUANTUMTOMQTOPT +#include "mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h.inc" + +using namespace mlir; + +class CatalystQuantumToMQTOptTypeConverter : public TypeConverter { +public: + explicit CatalystQuantumToMQTOptTypeConverter(MLIRContext* ctx) { + // Identity conversion: Allow all types to pass through unmodified if + // needed. + addConversion([](Type type) { return type; }); + + // Convert source QuregType to target QubitRegisterType + addConversion([ctx](catalyst::quantum::QuregType /*type*/) -> Type { + return ::mqt::ir::opt::QubitRegisterType::get(ctx); + }); + + // Convert source QubitType to target QubitType + addConversion([ctx](catalyst::quantum::QubitType /*type*/) -> Type { + return ::mqt::ir::opt::QubitType::get(ctx); + }); + } +}; + +struct ConvertQuantumAlloc + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::AllocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto resultType = + ::mqt::ir::opt::QubitRegisterType::get(rewriter.getContext()); + + // Create the new operation + auto mqtoptOp = rewriter.create<::mqt::ir::opt::AllocOp>( + op.getLoc(), resultType, adaptor.getNqubits(), + adaptor.getNqubitsAttrAttr()); + + // Get the result of the new operation, which represents the qubit register + auto trgtQreg = mqtoptOp->getResult(0); + + // Collect the users of the original operation to update their operands + std::vector users(op->getUsers().begin(), + op->getUsers().end()); + + // Iterate over the users in reverse order + for (auto* user : llvm::reverse(users)) { + // Update the operand of the user operation to the new qubit register + user->replaceUsesOfWith(op.getResult(), trgtQreg); + } + + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertQuantumDealloc + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::DeallocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Create the new operation + auto mqtoptOp = rewriter.create<::mqt::ir::opt::DeallocOp>( + op.getLoc(), ::mlir::TypeRange({}), adaptor.getQreg()); + + // Replace the original with the new operation + rewriter.replaceOp(op, mqtoptOp); + return success(); + } +}; + +struct ConvertQuantumMeasure + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto qubitType = ::mqt::ir::opt::QubitType::get(rewriter.getContext()); + auto bitType = rewriter.getI1Type(); + + // Create the new operation + auto mqtOp = rewriter.create<::mqt::ir::opt::MeasureOp>( + op.getLoc(), mlir::TypeRange{qubitType}, mlir::TypeRange{bitType}, + adaptor.getInQubit()); + + // Because the results (bit and qubit) have changed order, we need to + // manually update their uses + auto catalystMeasure = op->getResult(0); // bit + auto catalystQubit = op->getResult(1); // qubit + + auto mqtQubit = mqtOp->getResult(0); + auto mqtMeasure = mqtOp->getResult(1); + + // Collect the users of the original qubit + std::vector qubitUsers(catalystQubit.getUsers().begin(), + catalystQubit.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(qubitUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(mqtOp) && user != mqtOp && user != op) { + // Update operands in the user operation + user->replaceUsesOfWith(catalystQubit, mqtQubit); + } + } + + // Collect the users of the original measurement bit + std::vector measureUsers( + catalystMeasure.getUsers().begin(), catalystMeasure.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(measureUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(mqtOp) && user != mqtOp && user != op) { + // Update operands in the user operation + user->replaceUsesOfWith(catalystMeasure, mqtMeasure); + } + } + + // Erase the old operation + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertQuantumExtract + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::ExtractOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto resultType0 = + ::mqt::ir::opt::QubitRegisterType::get(rewriter.getContext()); + auto resultType1 = ::mqt::ir::opt::QubitType::get(rewriter.getContext()); + + // Create the new operation + auto mqtoptOp = rewriter.create<::mqt::ir::opt::ExtractOp>( + op.getLoc(), resultType0, resultType1, adaptor.getQreg(), + adaptor.getIdx(), adaptor.getIdxAttrAttr()); + + auto inQreg = op->getOperand(0); + auto outQreg = mqtoptOp->getResult(0); + + // Collect the users of the original input qubit register to update their + // operands + std::vector users(inQreg.getUsers().begin(), + inQreg.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(users)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(mqtoptOp) && user != mqtoptOp && user != op) { + user->replaceUsesOfWith(inQreg, outQreg); + } + } + + // Collect the users of the original output qubit + auto oldQubit = op->getResult(0); + auto newQubit = mqtoptOp->getResult(1); + + std::vector qubitUsers(oldQubit.getUsers().begin(), + oldQubit.getUsers().end()); + + // Iterate over qubit users in reverse order + for (auto* user : llvm::reverse(qubitUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(mqtoptOp) && user != mqtoptOp && user != op) { + user->replaceUsesOfWith(oldQubit, newQubit); + } + } + + // Erase the old operation + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertQuantumInsert + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::InsertOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto resultType = + ::mqt::ir::opt::QubitRegisterType::get(rewriter.getContext()); + + // Create the new operation + auto mqtoptOp = rewriter.create<::mqt::ir::opt::InsertOp>( + op.getLoc(), resultType, adaptor.getInQreg(), adaptor.getQubit(), + adaptor.getIdx(), adaptor.getIdxAttrAttr()); + + auto targetQreg = mqtoptOp->getResult(0); + auto sourceQreg = op->getResult(0); + + // Collect the users of the original out qubit register to update their + // operands + std::vector users(sourceQreg.getUsers().begin(), + sourceQreg.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(users)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(mqtoptOp) && user != mqtoptOp && user != op) { + user->replaceUsesOfWith(sourceQreg, targetQreg); + } + } + // Erase the old operation + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertQuantumCustomOp + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(catalyst::quantum::CustomOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Extract operand(s) and attribute(s) + auto gateName = op.getGateName(); + auto paramsValues = adaptor.getParams(); + auto allQubitsValues = adaptor.getInQubits(); + auto inNegCtrlQubitsValues = mlir::ValueRange(); // TODO: not available yet + + // Can be manipulated later + llvm::SmallVector inQubitsVec(allQubitsValues.begin(), + allQubitsValues.end()); + llvm::SmallVector inCtrlQubitsVec; + + llvm::SmallVector paramsMaskVec; + llvm::SmallVector staticParamsVec; + llvm::SmallVector finalParamValues; + + // Read attributes + auto maskAttr = op->getAttrOfType("params_mask"); + auto staticParamsAttr = + op->getAttrOfType("static_params"); + + // Total length of combined parameter list + size_t totalParams = 0; + if (maskAttr) { + totalParams = maskAttr.size(); + } else { + totalParams = staticParamsAttr + ? staticParamsAttr.size() + paramsValues.size() + : paramsValues.size(); + } + + // Pointers to step through static/dynamic values + size_t staticIdx = 0; + size_t dynamicIdx = 0; + + // Build final mask + values in order + for (size_t i = 0; i < totalParams; ++i) { + bool const isStatic = (maskAttr ? maskAttr[i] : false); + + paramsMaskVec.emplace_back(isStatic); + + if (isStatic) { + assert(staticParamsAttr && "Missing static_params for static mask"); + staticParamsVec.emplace_back(staticParamsAttr[staticIdx++]); + } else { + assert(dynamicIdx < paramsValues.size() && + "Too few dynamic parameters"); + finalParamValues.emplace_back(paramsValues[dynamicIdx++]); + } + } + + auto staticParams = + DenseF64ArrayAttr::get(rewriter.getContext(), staticParamsVec); + auto paramsMask = + DenseBoolArrayAttr::get(rewriter.getContext(), paramsMaskVec); + + if (gateName == "CNOT" || gateName == "CY" || gateName == "CZ" || + gateName == "CRX" || gateName == "CRY" || gateName == "CRZ" || + gateName == "ControlledPhaseShift") { + + assert(inQubitsVec.size() == 2 && "Expected 1 control + 1 target qubit"); + inCtrlQubitsVec.emplace_back(inQubitsVec[0]); + inQubitsVec = {inQubitsVec[1]}; + + } else if (gateName == "Toffoli") { + + assert(inQubitsVec.size() == 3 && "Expected 2 controls + 1 target qubit"); + inCtrlQubitsVec.emplace_back(inQubitsVec[0]); + inCtrlQubitsVec.emplace_back(inQubitsVec[1]); + inQubitsVec = {inQubitsVec[2]}; + } + + // Final ValueRanges to pass into create<> ops + mlir::ValueRange inQubitsValues(inQubitsVec); + mlir::ValueRange inCtrlQubitsValues(inCtrlQubitsVec); + + // Create the new operation + Operation* mqtoptOp = nullptr; + + if (gateName.compare("Hadamard") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::HOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("PauliX") == 0 || + gateName.compare("CNOT") == 0 || + gateName.compare("Toffoli") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::XOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("PauliY") == 0 || gateName.compare("CY") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::YOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("PauliZ") == 0 || gateName.compare("CZ") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::ZOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("SWAP") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::SWAPOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("RX") == 0 || gateName.compare("CRX") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::RXOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("RY") == 0 || gateName.compare("CRY") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::RYOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("RZ") == 0 || gateName.compare("CRZ") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::RZOp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else if (gateName.compare("PhaseShift") == 0 || + gateName.compare("ControlledPhaseShift") == 0) { + mqtoptOp = rewriter.create<::mqt::ir::opt::POp>( + op.getLoc(), inQubitsValues.getType(), inCtrlQubitsValues.getType(), + inNegCtrlQubitsValues.getType(), staticParams, paramsMask, + paramsValues, inQubitsValues, inCtrlQubitsValues, + inNegCtrlQubitsValues); + } else { + llvm::errs() << "Unsupported gate: " << gateName << "\n"; + return failure(); + } + + // Replace the original with the new operation + rewriter.replaceOp(op, mqtoptOp); + return success(); + } +}; + +struct CatalystQuantumToMQTOpt + : impl::CatalystQuantumToMQTOptBase { + using CatalystQuantumToMQTOptBase::CatalystQuantumToMQTOptBase; + + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + ConversionTarget target(*context); + target.addLegalDialect<::mqt::ir::opt::MQTOptDialect>(); + 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); + CatalystQuantumToMQTOptTypeConverter typeConverter(context); + + patterns.add(typeConverter, + context); + + // Boilerplate code to prevent: unresolved materialization + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + + populateReturnOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](func::ReturnOp op) { return typeConverter.isLegal(op); }); + + populateCallOpTypeConversionPattern(patterns, typeConverter); + target.addDynamicallyLegalOp( + [&](func::CallOp op) { return typeConverter.isLegal(op); }); + + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + target.markUnknownOpDynamicallyLegal([&](Operation* op) { + return isNotBranchOpInterfaceOrReturnLikeOp(op) || + isLegalForBranchOpInterfaceTypeConversionPattern(op, + typeConverter) || + isLegalForReturnOpTypeConversionPattern(op, typeConverter); + }); + + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mlir::mqt::ir::conversions diff --git a/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/CMakeLists.txt b/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/CMakeLists.txt new file mode 100644 index 000000000..484c0eb5a --- /dev/null +++ b/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/CMakeLists.txt @@ -0,0 +1,14 @@ +# 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 + +file(GLOB CONVERSION_SOURCES *.cpp) + +add_mlir_library(MQTOptToCatalystQuantum ${CONVERSION_SOURCES} LINK_LIBS ${LIBRARIES} DEPENDS + MQTOptToCatalystQuantumIncGen) + +target_link_libraries(MQTOptToCatalystQuantum PRIVATE MLIRQuantum) diff --git a/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp b/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp new file mode 100644 index 000000000..e8a4ae554 --- /dev/null +++ b/mlir/lib/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.cpp @@ -0,0 +1,536 @@ +/* + * 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/Catalyst/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 + +namespace mlir::mqt::ir::conversions { + +#define GEN_PASS_DEF_MQTOPTTOCATALYSTQUANTUM +#include "mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h.inc" + +using namespace mlir; + +class MQTOptToCatalystQuantumTypeConverter : public TypeConverter { +public: + explicit MQTOptToCatalystQuantumTypeConverter(MLIRContext* ctx) { + // Identity conversion: Allow all types to pass through unmodified if + // needed. + addConversion([](Type type) { return type; }); + + // Convert source QubitRegisterType to target QuregType + addConversion([ctx](::mqt::ir::opt::QubitRegisterType /*type*/) -> Type { + return catalyst::quantum::QuregType::get(ctx); + }); + + // Convert source QubitType to target QubitType + addConversion([ctx](::mqt::ir::opt::QubitType /*type*/) -> Type { + return catalyst::quantum::QubitType::get(ctx); + }); + } +}; + +struct ConvertMQTOptAlloc + : public OpConversionPattern<::mqt::ir::opt::AllocOp> { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(::mqt::ir::opt::AllocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto resultType = catalyst::quantum::QuregType::get(rewriter.getContext()); + + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), resultType, adaptor.getSize(), adaptor.getSizeAttrAttr()); + + // Get the result of the new operation, which represents the qubit register + auto trgtQreg = catalystOp->getResult(0); + + // Collect the users of the original operation to update their operands + std::vector users(op->getUsers().begin(), + op->getUsers().end()); + + // Iterate over the users in reverse order + for (auto* user : llvm::reverse(users)) { + // Registers should only be used in Extract, Insert or Dealloc operations + if (mlir::isa<::mqt::ir::opt::ExtractOp>(user) || + mlir::isa<::mqt::ir::opt::InsertOp>(user) || + mlir::isa<::mqt::ir::opt::DeallocOp>(user)) { + // Update the operand of the user operation to the new qubit register + user->setOperand(0, trgtQreg); + } + } + + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertMQTOptDealloc + : public OpConversionPattern<::mqt::ir::opt::DeallocOp> { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(::mqt::ir::opt::DeallocOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), ::mlir::TypeRange({}), adaptor.getQreg()); + + // Replace the original with the new operation + rewriter.replaceOp(op, catalystOp); + return success(); + } +}; + +struct ConvertMQTOptMeasure + : public OpConversionPattern<::mqt::ir::opt::MeasureOp> { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(::mqt::ir::opt::MeasureOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + + // Extract operand(s) + auto inQubit = adaptor.getInQubits()[0]; + + // Prepare the result type(s) + auto qubitType = catalyst::quantum::QubitType::get(rewriter.getContext()); + auto bitType = rewriter.getI1Type(); + + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), bitType, qubitType, inQubit, + /*optional::mlir::IntegerAttr postselect=*/nullptr); + + // Because the results (bit and qubit) have change order, we need to + // manually update their uses + auto mqtQubit = op->getResult(0); + auto catalystQubit = catalystOp->getResult(1); + + auto mqtMeasure = op->getResult(1); + auto catalystMeasure = catalystOp->getResult(0); + + // Collect the users of the original input qubit register to update their + // operands + std::vector qubitUsers(mqtQubit.getUsers().begin(), + mqtQubit.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(qubitUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(catalystOp) && user != catalystOp && + user != op) { + // Update operands in the user operation { + user->replaceUsesOfWith(mqtQubit, catalystQubit); + } + } + + std::vector measureUsers(mqtMeasure.getUsers().begin(), + mqtMeasure.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(measureUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(catalystOp) && user != catalystOp && + user != op) { + // Update operands in the user operation { + user->replaceUsesOfWith(mqtMeasure, catalystMeasure); + } + } + + // Erase the old operation + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertMQTOptExtract + : public OpConversionPattern<::mqt::ir::opt::ExtractOp> { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(::mqt::ir::opt::ExtractOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Prepare the result type(s) + auto resultType = catalyst::quantum::QubitType::get(rewriter.getContext()); + + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), resultType, adaptor.getInQreg(), adaptor.getIndex(), + adaptor.getIndexAttrAttr()); + + auto mqtQreg = op->getResult(0); + auto catalystQreg = catalystOp.getOperand(0); + + // Collect the users of the original input qubit register to update their + // operands + std::vector users(mqtQreg.getUsers().begin(), + mqtQreg.getUsers().end()); + + // Iterate over users in reverse order to update their operands properly + for (auto* user : llvm::reverse(users)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(catalystOp) && user != catalystOp && + user != op) { + // Update operands in the user operation + if (mlir::isa<::mqt::ir::opt::ExtractOp>(user) || + mlir::isa<::mqt::ir::opt::InsertOp>(user) || + mlir::isa<::mqt::ir::opt::DeallocOp>(user)) { + user->setOperand(0, catalystQreg); + } + } + } + + // Collect the users of the original output qubit + auto oldQubit = op->getResult(1); + auto newQubit = catalystOp->getResult(0); + + std::vector qubitUsers(oldQubit.getUsers().begin(), + oldQubit.getUsers().end()); + + // Iterate over qubit users in reverse order + for (auto* user : llvm::reverse(qubitUsers)) { + + // Only consider operations after the current operation + if (!user->isBeforeInBlock(catalystOp) && user != catalystOp && + user != op) { + + auto operandIdx = 0; + for (auto operand : user->getOperands()) { + if (operand == oldQubit) { + user->setOperand(operandIdx, newQubit); + } + operandIdx++; + } + } + } + + // Erase the old operation + rewriter.eraseOp(op); + return success(); + } +}; + +struct ConvertMQTOptInsert + : public OpConversionPattern<::mqt::ir::opt::InsertOp> { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(::mqt::ir::opt::InsertOp op, OpAdaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + + // Extract operand(s) and attribute(s) + auto inQregValue = adaptor.getInQreg(); + auto qubitValue = adaptor.getInQubit(); + auto idxValue = adaptor.getIndex(); + auto idxIntegerAttr = adaptor.getIndexAttrAttr(); + + // Prepare the result type(s) + auto resultType = catalyst::quantum::QuregType::get(rewriter.getContext()); + + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), resultType, inQregValue, idxValue, idxIntegerAttr, + qubitValue); + + // Replace the original with the new operation + rewriter.replaceOp(op, catalystOp); + return success(); + } +}; + +template +struct ConvertMQTOptSimpleGate : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(MQTGateOp op, typename MQTGateOp::Adaptor adaptor, + ConversionPatternRewriter& rewriter) const override { + // Extract operand(s) and attribute(s) + auto inQubitsValues = adaptor.getInQubits(); // excl. controls + auto posCtrlQubitsValues = adaptor.getPosCtrlInQubits(); + auto negCtrlQubitsValues = adaptor.getNegCtrlInQubits(); + + llvm::SmallVector inCtrlQubits; + inCtrlQubits.append(posCtrlQubitsValues.begin(), posCtrlQubitsValues.end()); + inCtrlQubits.append(negCtrlQubitsValues.begin(), negCtrlQubitsValues.end()); + + // Output type + mlir::Type qubitType = + catalyst::quantum::QubitType::get(rewriter.getContext()); + std::vector qubitTypes( + inQubitsValues.size() + inCtrlQubits.size(), qubitType); + auto outQubitTypes = mlir::TypeRange(qubitTypes); + + // Merge inQubitsValues and inCtrlQubits to form the full qubit list + auto allQubitsValues = llvm::SmallVector(); + allQubitsValues.append(inQubitsValues.begin(), inQubitsValues.end()); + allQubitsValues.append(inCtrlQubits.begin(), inCtrlQubits.end()); + auto inQubits = mlir::ValueRange(allQubitsValues); + + // Determine gate name depending on control count + llvm::StringRef gateName = getGateName(inCtrlQubits.size()); + if (gateName.empty()) { + llvm::errs() << "Unsupported controlled gate for op: " << op->getName() + << "\n"; + return failure(); + } + + // Create the new operation + auto catalystOp = rewriter.create( + op.getLoc(), + /*out_qubits=*/outQubitTypes, + /*out_ctrl_qubits=*/mlir::TypeRange({}), + /*params=*/adaptor.getParams(), + /*in_qubits=*/inQubits, + /*gate_name=*/gateName, + /*adjoint=*/nullptr, + /*in_ctrl_qubits=*/mlir::ValueRange({}), + /*in_ctrl_values=*/mlir::ValueRange()); + + // Replace the original with the new operation + rewriter.replaceOp(op, catalystOp); + return success(); + } + +private: + // Is specialized for each gate type + static llvm::StringRef getGateName(std::size_t numControls); +}; + +// -- XOp (PauliX, CNOT, Toffoli) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::XOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "PauliX"; + if (numControls == 1) + return "CNOT"; + if (numControls == 2) + return "Toffoli"; + return ""; +} + +// -- YOp (PauliY, CY) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::YOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "PauliY"; + if (numControls == 1) + return "CY"; + return ""; +} + +// -- ZOp (PauliZ, CZ) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::ZOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "PauliZ"; + if (numControls == 1) + return "CZ"; + return ""; +} + +// -- HOp (Hadamard) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::HOp>::getGateName( + std::size_t numControls) { + return "Hadamard"; +} + +// -- SWAPOp (SWAP) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::SWAPOp>::getGateName( + std::size_t numControls) { + return "SWAP"; +} + +// -- RXOp (RX, CRX) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::RXOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "RX"; + if (numControls == 1) + return "CRX"; + return ""; +} + +// -- RYOp (RY, CRY) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::RYOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "RY"; + if (numControls == 1) + return "CRY"; + return ""; +} + +// -- RZOp (RZ, CRZ) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::RZOp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "RZ"; + if (numControls == 1) + return "CRZ"; + return ""; +} + +// -- POp (PhaseShift, ControlledPhaseShift) +template <> +llvm::StringRef ConvertMQTOptSimpleGate<::mqt::ir::opt::POp>::getGateName( + std::size_t numControls) { + if (numControls == 0) + return "PhaseShift"; + if (numControls == 1) + return "ControlledPhaseShift"; + return ""; +} + +struct MQTOptToCatalystQuantum + : impl::MQTOptToCatalystQuantumBase { + using MQTOptToCatalystQuantumBase::MQTOptToCatalystQuantumBase; + + void runOnOperation() override { + MLIRContext* context = &getContext(); + auto* module = getOperation(); + + ConversionTarget target(*context); + target.addLegalDialect(); + target.addIllegalDialect<::mqt::ir::opt::MQTOptDialect>(); + + // Mark operations legal, that have no equivalent in the target dialect + target.addLegalOp< + ::mqt::ir::opt::IOp, ::mqt::ir::opt::GPhaseOp, + ::mqt::ir::opt::BarrierOp, ::mqt::ir::opt::SOp, ::mqt::ir::opt::SdgOp, + ::mqt::ir::opt::TOp, ::mqt::ir::opt::TdgOp, ::mqt::ir::opt::VOp, + ::mqt::ir::opt::VdgOp, ::mqt::ir::opt::UOp, ::mqt::ir::opt::U2Op, + ::mqt::ir::opt::SXOp, ::mqt::ir::opt::SXdgOp, ::mqt::ir::opt::iSWAPOp, + ::mqt::ir::opt::iSWAPdgOp, ::mqt::ir::opt::PeresOp, + ::mqt::ir::opt::PeresdgOp, ::mqt::ir::opt::DCXOp, ::mqt::ir::opt::ECROp, + ::mqt::ir::opt::RXXOp, ::mqt::ir::opt::RYYOp, ::mqt::ir::opt::RZZOp, + ::mqt::ir::opt::RZXOp, ::mqt::ir::opt::XXminusYY, + ::mqt::ir::opt::XXplusYY>(); + + RewritePatternSet patterns(context); + MQTOptToCatalystQuantumTypeConverter typeConverter(context); + + patterns.add(typeConverter, + context); + + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + patterns.add>(typeConverter, + context); + + // Boilerplate code to prevent "unresolved materialization" errors when the + // IR contains ops with signature or operand/result types not yet rewritten: + // https://www.jeremykun.com/2023/10/23/mlir-dialect-conversion + + // Rewrites func.func signatures to use the converted types. + // Needed so that the converted argument/result types match expectations + // in callers, bodies, and return ops. + populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); + + // Mark func.func as dynamically legal if: + // - the signature types are legal under the type converter + // - all block arguments in the function body are type-converted + target.addDynamicallyLegalOp([&](func::FuncOp op) { + return typeConverter.isSignatureLegal(op.getFunctionType()) && + typeConverter.isLegal(&op.getBody()); + }); + + // Converts return ops (func.return) to match the new function result types. + // Required when the function result types are changed by the converter. + populateReturnOpTypeConversionPattern(patterns, typeConverter); + + // Legal only if the return operand types match the converted function + // result types. + target.addDynamicallyLegalOp( + [&](func::ReturnOp op) { return typeConverter.isLegal(op); }); + + // Rewrites call sites (func.call) to use the converted argument and result + // types. Needed so that calls into rewritten functions pass/receive correct + // types. + populateCallOpTypeConversionPattern(patterns, typeConverter); + + // Legal only if operand/result types are all type-converted correctly. + target.addDynamicallyLegalOp( + [&](func::CallOp op) { return typeConverter.isLegal(op); }); + + // Rewrites control-flow ops like cf.br, cf.cond_br, etc. + // Ensures block argument types are consistent after conversion. + // Required for any dialects or passes that use CFG-based control flow. + populateBranchOpInterfaceTypeConversionPattern(patterns, typeConverter); + + // Fallback: mark any unhandled op as dynamically legal if: + // - it's not a return or branch-like op (i.e., doesn't require special + // handling), or + // - it passes the legality checks for branch ops or return ops + // This is crucial to avoid blocking conversion for unknown ops that don't + // require specific operand type handling. + target.markUnknownOpDynamicallyLegal([&](Operation* op) { + return isNotBranchOpInterfaceOrReturnLikeOp(op) || + isLegalForBranchOpInterfaceTypeConversionPattern(op, + typeConverter) || + isLegalForReturnOpTypeConversionPattern(op, typeConverter); + }); + + if (failed(applyPartialConversion(module, target, std::move(patterns)))) { + signalPassFailure(); + } + } +}; + +} // namespace mlir::mqt::ir::conversions diff --git a/mlir/test/Conversion/mqtopt.mlir b/mlir/test/Conversion/mqtopt.mlir new file mode 100644 index 000000000..2d324890d --- /dev/null +++ b/mlir/test/Conversion/mqtopt.mlir @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s --mqtopt-to-catalystquantum | FileCheck %s + +// CHECK-LABEL: func @bar() +func.func @bar() { + // CHECK: %cst = arith.constant 3.000000e-01 : f64 + %cst = arith.constant 3.000000e-01 : f64 + + // CHECK: %[[QREG:.*]] = quantum.alloc( 3) : !quantum.reg + %0 = "mqtopt.allocQubitRegister"() <{size_attr = 3 : i64}> : () -> !mqtopt.QubitRegister + + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][ 1] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][ 2] : !quantum.reg -> !quantum.bit + %out_qureg, %out_qubit = "mqtopt.extractQubit"(%0) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + %out_qureg_0, %out_qubit_1 = "mqtopt.extractQubit"(%out_qureg) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + %out_qureg_2, %out_qubit_3 = "mqtopt.extractQubit"(%out_qureg_0) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + + // CHECK: %[[H:.*]] = quantum.custom "Hadamard"() %[[Q0]] : !quantum.bit + // CHECK: %[[X:.*]] = quantum.custom "PauliX"() %[[H]] : !quantum.bit + // CHECK: %[[Y:.*]] = quantum.custom "PauliY"() %[[X]] : !quantum.bit + // CHECK: %[[Z:.*]] = quantum.custom "PauliZ"() %[[Y]] : !quantum.bit + %1 = mqtopt.h() %out_qubit : !mqtopt.Qubit + %2 = mqtopt.x() %1 : !mqtopt.Qubit + %3 = mqtopt.y() %2 : !mqtopt.Qubit + %4 = mqtopt.z() %3 : !mqtopt.Qubit + + // CHECK: %[[CNOT:.*]]:2 = quantum.custom "CNOT"() %[[Z]], %[[Q1]] : !quantum.bit, !quantum.bit + // CHECK: %[[CY:.*]]:2 = quantum.custom "CY"() %[[CNOT]]#0, %[[CNOT]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[CZ:.*]]:2 = quantum.custom "CZ"() %[[CY]]#0, %[[CY]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[SW0:.*]]:2 = quantum.custom "SWAP"() %[[CZ]]#1, %[[CZ]]#0 : !quantum.bit, !quantum.bit + // CHECK: %[[TOF:.*]]:3 = quantum.custom "Toffoli"() %[[SW0]]#0, %[[Q2]], %[[SW0]]#1 : !quantum.bit, !quantum.bit, !quantum.bit + %5, %6 = mqtopt.x() %4 ctrl %out_qubit_1 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %7, %8 = mqtopt.y() %5 ctrl %6 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %9, %10 = mqtopt.z() %7 ctrl %8 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %11, %12 = mqtopt.swap() %10, %9 : !mqtopt.Qubit, !mqtopt.Qubit + %13, %14, %15 = mqtopt.x() %11 ctrl %out_qubit_3, %12 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + + // CHECK: %[[RX:.*]] = quantum.custom "RX"(%cst) %[[TOF]]#0 : !quantum.bit + // CHECK: %[[RY:.*]] = quantum.custom "RY"(%cst) %[[RX]] : !quantum.bit + // CHECK: %[[RZ:.*]] = quantum.custom "RZ"(%cst) %[[RY]] : !quantum.bit + // CHECK: %[[PS:.*]] = quantum.custom "PhaseShift"(%cst) %[[RZ]] : !quantum.bit + %16 = mqtopt.rx(%cst) %13 : !mqtopt.Qubit + %17 = mqtopt.ry(%cst) %16 : !mqtopt.Qubit + %18 = mqtopt.rz(%cst) %17 : !mqtopt.Qubit + %19 = mqtopt.p(%cst) %18 : !mqtopt.Qubit + + // CHECK: %[[CRX:.*]]:2 = quantum.custom "CRX"(%cst) %[[PS]], %[[TOF]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[CRY:.*]]:2 = quantum.custom "CRY"(%cst) %[[CRX]]#0, %[[CRX]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[CRZ:.*]]:2 = quantum.custom "CRZ"(%cst) %[[CRY]]#0, %[[CRY]]#1 : !quantum.bit, !quantum.bit + // CHECK: %[[CPS:.*]]:2 = quantum.custom "ControlledPhaseShift"(%cst) %[[CRZ]]#0, %[[CRZ]]#1 : !quantum.bit, !quantum.bit + %200, %201 = mqtopt.rx(%cst) %19 ctrl %14 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %210, %211 = mqtopt.ry(%cst) %200 ctrl %201 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %220, %221 = mqtopt.rz(%cst) %210 ctrl %211 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %230, %231 = mqtopt.p(%cst) %220 ctrl %221 : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // CHECK: %[[MRES:.*]], %[[QMEAS:.*]] = quantum.measure %[[TOF]]#2 : i1, !quantum.bit + %q_meas, %c0_0 = "mqtopt.measure"(%15) : (!mqtopt.Qubit) -> (!mqtopt.Qubit, i1) + + // CHECK: %[[R1:.*]] = quantum.insert %[[QREG]][ 2], %[[QMEAS]] : !quantum.reg, !quantum.bit + // CHECK: %[[R2:.*]] = quantum.insert %[[R1]][ 1], %[[CPS]]#0 : !quantum.reg, !quantum.bit + // CHECK: %[[R3:.*]] = quantum.insert %[[R2]][ 0], %[[CPS]]#1 : !quantum.reg, !quantum.bit + %240 = "mqtopt.insertQubit"(%out_qureg_2, %q_meas) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + %250 = "mqtopt.insertQubit"(%240, %230) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + %260 = "mqtopt.insertQubit"(%250, %231) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + + // CHECK: quantum.dealloc %[[R3]] : !quantum.reg + "mqtopt.deallocQubitRegister"(%260) : (!mqtopt.QubitRegister) -> () + + return +} diff --git a/mlir/test/Conversion/mqtopt_GHZ.mlir b/mlir/test/Conversion/mqtopt_GHZ.mlir new file mode 100644 index 000000000..09a7cd5c7 --- /dev/null +++ b/mlir/test/Conversion/mqtopt_GHZ.mlir @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s --mqtopt-to-catalystquantum | FileCheck %s + +// CHECK-LABEL: func @bar() +func.func @bar() { + // CHECK: %[[QREG:.*]] = quantum.alloc( 3) : !quantum.reg + %0 = "mqtopt.allocQubitRegister"() <{size_attr = 3 : i64}> : () -> !mqtopt.QubitRegister + + // CHECK: %[[Q0:.*]] = quantum.extract %[[QREG]][ 0] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q1:.*]] = quantum.extract %[[QREG]][ 1] : !quantum.reg -> !quantum.bit + // CHECK: %[[Q2:.*]] = quantum.extract %[[QREG]][ 2] : !quantum.reg -> !quantum.bit + %out_qureg, %out_qubit = "mqtopt.extractQubit"(%0) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + %out_qureg_0, %out_qubit_1 = "mqtopt.extractQubit"(%out_qureg) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + %out_qureg_2, %out_qubit_3 = "mqtopt.extractQubit"(%out_qureg_0) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + + // CHECK: %[[H:.*]] = quantum.custom "Hadamard"() %[[Q0]] : !quantum.bit + %1 = mqtopt.h() %out_qubit : !mqtopt.Qubit + + // CHECK: %[[CX1:.*]]:2 = quantum.custom "CNOT"() %[[H]], %[[Q1]] : !quantum.bit, !quantum.bit + // CHECK: %[[CX2:.*]]:2 = quantum.custom "CNOT"() %[[CX1]]#1, %[[Q2]] : !quantum.bit, !quantum.bit + %20, %21 = mqtopt.x() %1 ctrl %out_qubit_1 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %30, %31 = mqtopt.x() %21 ctrl %out_qubit_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit + + // CHECK: %[[R0:.*]] = quantum.insert %[[QREG]][ 0], %[[CX1]]#0 : !quantum.reg, !quantum.bit + // CHECK: %[[R1:.*]] = quantum.insert %[[R0]][ 1], %[[CX2]]#0 : !quantum.reg, !quantum.bit + // CHECK: %[[R2:.*]] = quantum.insert %[[R1]][ 2], %[[CX2]]#1 : !quantum.reg, !quantum.bit + %4 = "mqtopt.insertQubit"(%out_qureg_2, %20) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + %5 = "mqtopt.insertQubit"(%4, %30) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + %6 = "mqtopt.insertQubit"(%5, %31) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + + "mqtopt.deallocQubitRegister"(%6) : (!mqtopt.QubitRegister) -> () + return +} diff --git a/mlir/test/Conversion/quantum.mlir b/mlir/test/Conversion/quantum.mlir new file mode 100644 index 000000000..9be7ba311 --- /dev/null +++ b/mlir/test/Conversion/quantum.mlir @@ -0,0 +1,85 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s --catalystquantum-to-mqtopt | FileCheck %s + +module { + // CHECK-LABEL: func @bar() + func.func @bar() { + // CHECK: %[[PHI:.*]] = arith.constant 3.000000e-01 : f64 + %phi0 = arith.constant 3.000000e-01 : f64 + + // CHECK: %[[QREG:.*]] = "mqtopt.allocQubitRegister"() <{size_attr = 3 : i64}> : () -> !mqtopt.QubitRegister + %r0 = quantum.alloc( 3) : !quantum.reg + + // CHECK: %[[QR1:.*]], %[[Q0:.*]] = "mqtopt.extractQubit"(%[[QREG]]) <{index_attr = 0 : i64}> + // CHECK: %[[QR2:.*]], %[[Q1:.*]] = "mqtopt.extractQubit"(%[[QR1]]) <{index_attr = 1 : i64}> + // CHECK: %[[QR3:.*]], %[[Q2:.*]] = "mqtopt.extractQubit"(%[[QR2]]) <{index_attr = 2 : i64}> + %q0 = quantum.extract %r0[ 0] : !quantum.reg -> !quantum.bit + %q1 = quantum.extract %r0[ 1] : !quantum.reg -> !quantum.bit + %q2 = quantum.extract %r0[ 2] : !quantum.reg -> !quantum.bit + + // CHECK: %[[H:.*]] = mqtopt.h( static [] mask []) %[[Q0]] : !mqtopt.Qubit + // CHECK: %[[X:.*]] = mqtopt.x( static [] mask []) %[[H]] : !mqtopt.Qubit + // CHECK: %[[Y:.*]] = mqtopt.y( static [] mask []) %[[X]] : !mqtopt.Qubit + // CHECK: %[[Z:.*]] = mqtopt.z( static [] mask []) %[[Y]] : !mqtopt.Qubit + %out_h = quantum.custom "Hadamard"() %q0 : !quantum.bit + %out_x = quantum.custom "PauliX"() %out_h : !quantum.bit + %out_y = quantum.custom "PauliY"() %out_x : !quantum.bit + %out_z = quantum.custom "PauliZ"() %out_y : !quantum.bit + + // CHECK: %[[CNOT0:.*]], %[[CNOT1:.*]] = mqtopt.x( static [] mask []) %[[Q0]] ctrl %[[Q1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CY0:.*]], %[[CY1:.*]] = mqtopt.y( static [] mask []) %[[CNOT0]] ctrl %[[CNOT1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CZ0:.*]], %[[CZ1:.*]] = mqtopt.z( static [] mask []) %[[CY0]] ctrl %[[CY1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[SWAP:.*]]:2 = mqtopt.swap( static [] mask []) %[[CZ1]], %[[CZ0]] : !mqtopt.Qubit, !mqtopt.Qubit + // CHECK: %[[TOF0:.*]], %[[TOFPACKED:.*]]:2 = mqtopt.x( static [] mask []) %[[Q2]] ctrl %[[SWAP]]#0, %[[SWAP]]#1 : !mqtopt.Qubit ctrl !mqtopt.Qubit, !mqtopt.Qubit + %cnot:2 = quantum.custom "CNOT"() %q1, %q0 : !quantum.bit, !quantum.bit + %cy:2 = quantum.custom "CY"() %cnot#1, %cnot#0 : !quantum.bit, !quantum.bit + %cz:2 = quantum.custom "CZ"() %cy#1, %cy#0 : !quantum.bit, !quantum.bit + %swap:2 = quantum.custom "SWAP"() %cz#1, %cz#0 : !quantum.bit, !quantum.bit + %toffoli:3 = quantum.custom "Toffoli"() %swap#0, %swap#1, %q2 : !quantum.bit, !quantum.bit, !quantum.bit + + // CHECK: %[[RX:.*]] = mqtopt.rx(%[[PHI]] static [] mask [false]) %[[TOF0]] : !mqtopt.Qubit + // CHECK: %[[RY:.*]] = mqtopt.ry(%[[PHI]] static [] mask [false]) %[[RX]] : !mqtopt.Qubit + // CHECK: %[[RZ:.*]] = mqtopt.rz(%[[PHI]] static [] mask [false]) %[[RY]] : !mqtopt.Qubit + // CHECK: %[[P:.*]] = mqtopt.p(%[[PHI]] static [] mask [false]) %[[RZ]] : !mqtopt.Qubit + %rx = quantum.custom "RX"(%phi0) %toffoli#0 : !quantum.bit + %ry = quantum.custom "RY"(%phi0) %rx : !quantum.bit + %rz = quantum.custom "RZ"(%phi0) %ry : !quantum.bit + %phaseShift = quantum.custom "PhaseShift"(%phi0) %rz : !quantum.bit + + // CHECK: %[[CRX0:.*]], %[[CRX1:.*]] = mqtopt.rx(%[[PHI]] static [] mask [false]) %[[P]] ctrl %[[TOFPACKED]]#0 : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CRY0:.*]], %[[CRY1:.*]] = mqtopt.ry(%[[PHI]] static [] mask [false]) %[[CRX0]] ctrl %[[CRX1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CRZ0:.*]], %[[CRZ1:.*]] = mqtopt.ry(%[[PHI]] static [] mask [false]) %[[CRY0]] ctrl %[[CRY1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CPS0:.*]], %[[CPS1:.*]] = mqtopt.p(%[[PHI]] static [] mask [false]) %[[CRZ0]] ctrl %[[CRZ1]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + %crx:2 = quantum.custom "CRX"(%phi0) %toffoli#1, %phaseShift : !quantum.bit, !quantum.bit + %cry:2 = quantum.custom "CRY"(%phi0) %crx#1, %crx#0 : !quantum.bit, !quantum.bit + %crz:2 = quantum.custom "CRY"(%phi0) %cry#1, %cry#0 : !quantum.bit, !quantum.bit + %cps:2 = quantum.custom "ControlledPhaseShift"(%phi0) %crz#1, %crz#0 : !quantum.bit, !quantum.bit + + // CHECK: %[[OUT:.*]] = mqtopt.rx(%[[PHI]] static [] mask [false]) %[[CPS0]] : !mqtopt.Qubit + %out = quantum.custom "RX"(%phi0) %cps#0 { + //static_params = array, + //params_mask = array + } : !quantum.bit + + // CHECK: %[[QMEAS:.*]], %[[MEAS:.*]] = "mqtopt.measure"(%[[TOFPACKED]]#1) : (!mqtopt.Qubit) -> (!mqtopt.Qubit, i1) + %meas:2 = quantum.measure %toffoli#2 : i1, !quantum.bit + + // CHECK: %[[R1:.*]] = "mqtopt.insertQubit"(%[[QR3]], %[[QMEAS]]) <{index_attr = 2 : i64}> + // CHECK: %[[R2:.*]] = "mqtopt.insertQubit"(%[[R1]], %[[CPS1]]) <{index_attr = 1 : i64}> + // CHECK: %[[R3:.*]] = "mqtopt.insertQubit"(%[[R2]], %[[OUT]]) <{index_attr = 0 : i64}> + %r1_2 = quantum.insert %r0[2], %meas#1 : !quantum.reg, !quantum.bit + %r1_1 = quantum.insert %r1_2[1], %cps#1 : !quantum.reg, !quantum.bit + %r1_0 = quantum.insert %r1_1[0], %out : !quantum.reg, !quantum.bit + + // CHECK: "mqtopt.deallocQubitRegister"(%[[R3]]) : (!mqtopt.QubitRegister) -> () + quantum.dealloc %r1_0 : !quantum.reg + return + } +} diff --git a/mlir/test/Conversion/quantum_GHZ.mlir b/mlir/test/Conversion/quantum_GHZ.mlir new file mode 100644 index 000000000..3aa2bedb3 --- /dev/null +++ b/mlir/test/Conversion/quantum_GHZ.mlir @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 Chair for Design Automation, TUM +// Copyright (c) 2025 Munich Quantum Software Company GmbH +// All rights reserved. +// +// SPDX-License-Identifier: MIT +// +// Licensed under the MIT License + +// RUN: quantum-opt %s --catalystquantum-to-mqtopt | FileCheck %s + +module { + // CHECK-LABEL: func @bar() + func.func @bar() { + // CHECK: %[[QREG:.*]] = "mqtopt.allocQubitRegister"() <{size_attr = 3 : i64}> : () -> !mqtopt.QubitRegister + %0 = quantum.alloc( 3) : !quantum.reg + + // CHECK: %[[QR1:.*]], %[[Q0:.*]] = "mqtopt.extractQubit"(%[[QREG]]) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + // CHECK: %[[QR2:.*]], %[[Q1:.*]] = "mqtopt.extractQubit"(%[[QR1]]) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + // CHECK: %[[QR3:.*]], %[[Q2:.*]] = "mqtopt.extractQubit"(%[[QR2]]) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister) -> (!mqtopt.QubitRegister, !mqtopt.Qubit) + %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit + %2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit + %3 = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit + + // CHECK: %[[H:.*]] = mqtopt.h( static [] mask []) %[[Q0]] : !mqtopt.Qubit + // CHECK: %[[CX1:.*]], %[[CX1_0:.*]] = mqtopt.x( static [] mask []) %[[Q1]] ctrl %[[H]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[CX2:.*]], %[[CX2_0:.*]] = mqtopt.x( static [] mask []) %[[Q2]] ctrl %[[CX1_0]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + %out_h = quantum.custom "Hadamard"() %1 : !quantum.bit + %out_qubits:2 = quantum.custom "CNOT"() %out_h, %2 : !quantum.bit, !quantum.bit + %out_qubits_0:2 = quantum.custom "CNOT"() %out_qubits#1, %3 : !quantum.bit, !quantum.bit + + // CHECK: %[[R0:.*]] = "mqtopt.insertQubit"(%[[QR3]], %[[CX1]]) <{index_attr = 0 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + // CHECK: %[[R1:.*]] = "mqtopt.insertQubit"(%[[R0]], %[[CX2]]) <{index_attr = 1 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + // CHECK: %[[R2:.*]] = "mqtopt.insertQubit"(%[[R1]], %[[CX2_0]]) <{index_attr = 2 : i64}> : (!mqtopt.QubitRegister, !mqtopt.Qubit) -> !mqtopt.QubitRegister + %4 = quantum.insert %0[ 0], %out_qubits#0 : !quantum.reg, !quantum.bit + %5 = quantum.insert %4[ 1], %out_qubits_0#0 : !quantum.reg, !quantum.bit + %6 = quantum.insert %5[ 2], %out_qubits_0#1 : !quantum.reg, !quantum.bit + + // CHECK: "mqtopt.deallocQubitRegister"(%[[R2]]) : (!mqtopt.QubitRegister) -> () + quantum.dealloc %6 : !quantum.reg + + return + } +} diff --git a/mlir/tools/quantum-opt/CMakeLists.txt b/mlir/tools/quantum-opt/CMakeLists.txt index 8eadfce13..9d94bcc5f 100644 --- a/mlir/tools/quantum-opt/CMakeLists.txt +++ b/mlir/tools/quantum-opt/CMakeLists.txt @@ -19,7 +19,17 @@ set(LIBS MLIRMQTOptTransforms MLIRMQTDynTransforms) +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + list(APPEND LIBS CatalystQuantumToMQTOpt MQTOptToCatalystQuantum MLIRQuantum) +endif() + add_mlir_tool(quantum-opt quantum-opt.cpp DEPENDS ${LIBS} SUPPORT_PLUGINS) + +if(ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) + # This makes ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN available as a macro in the C++ file. + target_compile_definitions(quantum-opt PRIVATE ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN) +endif() + target_compile_options(quantum-opt PRIVATE -fexceptions) target_link_libraries(quantum-opt PUBLIC ${LIBS}) llvm_update_compile_flags(quantum-opt) diff --git a/mlir/tools/quantum-opt/quantum-opt.cpp b/mlir/tools/quantum-opt/quantum-opt.cpp index 46bd60cdb..cbbc83f2f 100644 --- a/mlir/tools/quantum-opt/quantum-opt.cpp +++ b/mlir/tools/quantum-opt/quantum-opt.cpp @@ -8,6 +8,13 @@ * Licensed under the MIT License */ +#ifdef ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN +#include "mlir/Conversion/Catalyst/CatalystQuantumToMQTOpt/CatalystQuantumToMQTOpt.h" // IWYU pragma: keep +#include "mlir/Conversion/Catalyst/MQTOptToCatalystQuantum/MQTOptToCatalystQuantum.h" // IWYU pragma: keep + +#include +#endif + #include "mlir/Dialect/MQTDyn/IR/MQTDynDialect.h" // IWYU pragma: keep #include "mlir/Dialect/MQTDyn/Transforms/Passes.h" // IWYU pragma: keep #include "mlir/Dialect/MQTOpt/IR/MQTOptDialect.h" // IWYU pragma: keep @@ -19,6 +26,14 @@ #include #include +void registerCatalystIfEnabled(mlir::DialectRegistry& registry) { +#ifdef ENABLE_MQT_CORE_MLIR_CATALYST_PLUGIN + mlir::mqt::ir::conversions::registerCatalystQuantumToMQTOptPasses(); + mlir::mqt::ir::conversions::registerMQTOptToCatalystQuantumPasses(); + registry.insert(); +#endif +} + int main(const int argc, char** argv) { mlir::registerAllPasses(); mqt::ir::opt::registerMQTOptPasses(); @@ -30,6 +45,8 @@ int main(const int argc, char** argv) { registry.insert(); registry.insert(); + registerCatalystIfEnabled(registry); + return mlir::asMainReturnCode( MlirOptMain(argc, argv, "Quantum optimizer driver\n", registry)); }