From 31748995036f250f9fa71fa8a6133ebbff82f29c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 15:38:02 +0200 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=94=A7=20do=20not=20explicitly=20hide?= =?UTF-8?q?=20visibility=20for=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- cmake/CompilerOptions.cmake | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 468d925fb..9f98ee0f2 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -60,10 +60,6 @@ function(enable_project_options target_name) option(BINDINGS "Configure for building Python bindings") if(BINDINGS) - check_cxx_compiler_flag(-fvisibility=hidden HAS_VISIBILITY_HIDDEN) - if(HAS_VISIBILITY_HIDDEN) - target_compile_options(${target_name} INTERFACE -fvisibility=hidden) - endif() include(CheckPIESupported) check_pie_supported() set_target_properties(${target_name} PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON) From f6c60a8ba6844e1dace07547191d732f85f91605 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 15:40:09 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=94=A7=20export=20all=20symbols=20by?= =?UTF-8?q?=20default=20on=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- cmake/StandardProjectSettings.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake index 088d19f96..af372ac93 100644 --- a/cmake/StandardProjectSettings.cmake +++ b/cmake/StandardProjectSettings.cmake @@ -70,3 +70,8 @@ if(DEPLOY) "10.15" CACHE STRING "" FORCE) endif() + +# export all symbols by default on Windows +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS + ON + CACHE BOOL "Export all symbols on Windows") From 8c635fd29ed5a146dcabb5f5406b0088a139097b Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 15:40:56 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=94=A7=20build=20shared=20libs=20by?= =?UTF-8?q?=20default=20when=20building=20Python=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚨 ignore check-wheel-contents warnings Signed-off-by: burgholzer --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c29b3eb41..78597154c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,6 +96,7 @@ git-only = [ [tool.scikit-build.cmake.define] BUILD_MQT_CORE_BINDINGS = "ON" BUILD_MQT_CORE_TESTS = "OFF" +BUILD_SHARED_LIBS = "ON" [[tool.scikit-build.overrides]] if.python-version = ">=3.13" @@ -109,6 +110,13 @@ cmake.define.DISABLE_GIL = "1" write_to = "src/mqt/core/_version.py" +[tool.check-wheel-contents] +ignore = [ + "W002", # Wheel contains duplicate files (.so symlinks) + "W004", # Module is not located at importable path (/lib/mqt-core-*.so) +] + + [tool.pytest.ini_options] minversion = "7.2" addopts = ["-ra", "--strict-markers", "--strict-config"] From 0fcd9abff406f1a6d0208668f46e17a533827c1c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 15:41:31 +0200 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=94=A7=20build=20all=20relevant=20tar?= =?UTF-8?q?gets=20when=20building=20Python=20package?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 78597154c..aba37445d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,11 +68,18 @@ ninja.version = ">=1.10" # Setuptools-style build caching in a local directory build-dir = "build/{wheel_tag}/{build_type}" -# Only build the Python bindings target -build.targets = ["ir"] - -# Only install the Python package component -install.components = ["mqt-core_Python"] +# All the targets to build +build.targets = [ + "mqt-core-ir", + "mqt-core-algorithms", + "mqt-core-circuit-optimizer", + "mqt-core-dd", + "mqt-core-zx", + "mqt-core-ds", + "mqt-core-na", + "mqt-core-python", + "ir" +] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" sdist.include = ["src/mqt/core/_version.py"] From 67e666aa2cc21419fa90ebb9e34abdaacada3b4d Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 15:42:22 +0200 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=A8=20mqt-core-cli=20for=20finding=20?= =?UTF-8?q?CMake=20and=20include=20paths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 1 + src/mqt/core/__main__.py | 54 +++++++++++++++++++ src/mqt/core/_commands.py | 51 ++++++++++++++++++ test/python/test_cli.py | 107 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 213 insertions(+) create mode 100644 src/mqt/core/__main__.py create mode 100644 src/mqt/core/_commands.py create mode 100644 test/python/test_cli.py diff --git a/pyproject.toml b/pyproject.toml index aba37445d..54044f90a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ qiskit = [ [project.scripts] mqt-core-dd-compare = "mqt.core.dd.evaluation:main" +mqt-core-cli = "mqt.core.__main__:main" [project.urls] Homepage = "https://github.com/cda-tum/mqt-core" diff --git a/src/mqt/core/__main__.py b/src/mqt/core/__main__.py new file mode 100644 index 000000000..eb37eb882 --- /dev/null +++ b/src/mqt/core/__main__.py @@ -0,0 +1,54 @@ +# Copyright (c) 2025 Chair for Design Automation, TUM +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Command line interface for mqt-core.""" + +from __future__ import annotations + +import argparse +import sys + +from ._commands import cmake_dir, include_dir +from ._version import version as __version__ + + +def main() -> None: + """Entry point for the mqt-core command line interface. + + This function is called when running the `mqt-core-cli` script. + + .. code-block:: bash + + mqt-core-cli [--version] [--include_dir] [--cmake_dir] + + It provides the following command line options: + + - :code:`--version`: Print the version and exit. + - :code:`--include_dir`: Print the path to the mqt-core C++ include directory. + - :code:`--cmake_dir`: Print the path to the mqt-core CMake module directory. + + """ + parser = argparse.ArgumentParser() + parser.add_argument("--version", action="version", version=__version__, help="Print version and exit.") + + parser.add_argument( + "--include_dir", action="store_true", help="Print the path to the mqt-core C++ include directory." + ) + parser.add_argument( + "--cmake_dir", action="store_true", help="Print the path to the mqt-core CMake module directory." + ) + args = parser.parse_args() + if not sys.argv[1:]: + parser.print_help() + if args.include_dir: + print(include_dir().resolve()) + if args.cmake_dir: + print(cmake_dir().resolve()) + + +if __name__ == "__main__": + main() diff --git a/src/mqt/core/_commands.py b/src/mqt/core/_commands.py new file mode 100644 index 000000000..887e58728 --- /dev/null +++ b/src/mqt/core/_commands.py @@ -0,0 +1,51 @@ +# Copyright (c) 2025 Chair for Design Automation, TUM +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Useful commands for obtaining information about mqt-core.""" + +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError, distribution +from pathlib import Path + + +def include_dir() -> Path: + """Return the path to the mqt-core include directory. + + Raises: + FileNotFoundError: If the include directory is not found. + ImportError: If mqt-core is not installed. + """ + try: + dist = distribution("mqt-core") + located_include_dir = Path(dist.locate_file("mqt/core/include/mqt-core")) + if located_include_dir.exists() and located_include_dir.is_dir(): + return located_include_dir + msg = "mqt-core include files not found." + raise FileNotFoundError(msg) + except PackageNotFoundError: + msg = "mqt-core not installed, installation required to access the include files." + raise ImportError(msg) from None + + +def cmake_dir() -> Path: + """Return the path to the mqt-core CMake module directory. + + Raises: + FileNotFoundError: If the CMake module directory is not found. + ImportError: If mqt-core is not installed. + """ + try: + dist = distribution("mqt-core") + located_cmake_dir = Path(dist.locate_file("mqt/core/share/cmake")) + if located_cmake_dir.exists() and located_cmake_dir.is_dir(): + return located_cmake_dir + msg = "mqt-core CMake files not found." + raise FileNotFoundError(msg) + except PackageNotFoundError: + msg = "mqt-core not installed, installation required to access the CMake files." + raise ImportError(msg) from None diff --git a/test/python/test_cli.py b/test/python/test_cli.py new file mode 100644 index 000000000..a1096cfd1 --- /dev/null +++ b/test/python/test_cli.py @@ -0,0 +1,107 @@ +# Copyright (c) 2025 Chair for Design Automation, TUM +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Test the mqt-core CLI.""" + +from __future__ import annotations + +import sys +from importlib.metadata import PackageNotFoundError +from pathlib import Path +from typing import TYPE_CHECKING +from unittest.mock import patch + +import pytest + +from mqt.core import __version__ as mqt_core_version + +if TYPE_CHECKING: + from pytest_console_scripts import ScriptRunner + + +def test_cli_no_arguments(script_runner: ScriptRunner) -> None: + """Test running the CLI with no arguments.""" + ret = script_runner.run(["mqt-core-cli"]) + assert ret.success + assert "usage: mqt-core-cli [-h] [--version] [--include_dir] [--cmake_dir]" in ret.stdout + + +def test_cli_help(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --help argument.""" + ret = script_runner.run(["mqt-core-cli", "--help"]) + assert ret.success + assert "usage: mqt-core-cli [-h] [--version] [--include_dir] [--cmake_dir]" in ret.stdout + + +def test_cli_version(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --version argument.""" + ret = script_runner.run(["mqt-core-cli", "--version"]) + assert ret.success + assert mqt_core_version in ret.stdout + + +def test_cli_include_dir(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --include_dir argument.""" + ret = script_runner.run(["mqt-core-cli", "--include_dir"]) + assert ret.success + include_dir = Path(ret.stdout.strip()) + assert include_dir.exists() + assert include_dir.is_dir() + + +def test_cli_cmake_dir(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --cmake_dir argument.""" + ret = script_runner.run(["mqt-core-cli", "--cmake_dir"]) + assert ret.success + cmake_dir = Path(ret.stdout.strip()) + assert cmake_dir.exists() + assert cmake_dir.is_dir() + + +def test_cli_include_dir_not_installed(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --include_dir argument, but mqt-core is not installed.""" + with patch("importlib.metadata.Distribution.from_name") as mock: + mock.side_effect = PackageNotFoundError() + ret = script_runner.run(["mqt-core-cli", "--include_dir"]) + assert not ret.success + assert "mqt-core not installed, installation required to access the include files." in ret.stderr + + +def test_cli_cmake_dir_not_installed(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --cmake_dir argument, but mqt-core is not installed.""" + with patch("importlib.metadata.Distribution.from_name") as mock: + mock.side_effect = PackageNotFoundError() + ret = script_runner.run(["mqt-core-cli", "--cmake_dir"]) + assert not ret.success + assert "mqt-core not installed, installation required to access the CMake files." in ret.stderr + + +def test_cli_include_dir_not_found(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --include_dir argument, but the include directory is not found.""" + with patch("importlib.metadata.Distribution.from_name") as mock: + mock.return_value.locate_file.return_value = "dir-not-found" + ret = script_runner.run(["mqt-core-cli", "--include_dir"]) + assert not ret.success + assert "mqt-core include files not found." in ret.stderr + + +def test_cli_cmake_dir_not_found(script_runner: ScriptRunner) -> None: + """Test running the CLI with the --cmake_dir argument, but the CMake directory is not found.""" + with patch("importlib.metadata.Distribution.from_name") as mock: + mock.return_value.locate_file.return_value = "dir-not-found" + ret = script_runner.run(["mqt-core-cli", "--cmake_dir"]) + assert not ret.success + assert "mqt-core CMake files not found." in ret.stderr + + +@pytest.mark.skipif(sys.platform.startswith("win"), reason="The subprocess calls do not work properly on Windows.") +def test_cli_execute_module() -> None: + """Test running the CLI by executing the mqt-core module.""" + from subprocess import check_output + + output = check_output(["python", "-m", "mqt.core", "--version"]) # noqa: S603, S607 + assert mqt_core_version in output.decode() From ac1f56f375b8f234d4f04cc417f4aebcda6b4029 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 6 Aug 2024 16:25:30 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=94=A5=20remove=20the=20`mqt-core-pyt?= =?UTF-8?q?hon`=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../mqt-core/python/qiskit/QuantumCircuit.hpp | 56 -- pyproject.toml | 3 +- src/CMakeLists.txt | 24 - src/python/qiskit/QuantumCircuit.cpp | 502 ------------------ 4 files changed, 1 insertion(+), 584 deletions(-) delete mode 100644 include/mqt-core/python/qiskit/QuantumCircuit.hpp delete mode 100644 src/python/qiskit/QuantumCircuit.cpp diff --git a/include/mqt-core/python/qiskit/QuantumCircuit.hpp b/include/mqt-core/python/qiskit/QuantumCircuit.hpp deleted file mode 100644 index d2df1b294..000000000 --- a/include/mqt-core/python/qiskit/QuantumCircuit.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2025 Chair for Design Automation, TUM - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#pragma once - -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/OpType.hpp" -#include "python/pybind11.hpp" // IWYU pragma: keep - -#include - -namespace py = pybind11; - -namespace qc::qiskit { -using namespace pybind11::literals; - -class QuantumCircuit { -public: - static void import(QuantumComputation& qc, const py::object& circ); - -protected: - static void emplaceOperation(QuantumComputation& qc, - const py::object& instruction, - const py::list& qargs, const py::list& cargs, - const py::list& params, const py::dict& qubitMap, - const py::dict& clbitMap); - - static SymbolOrNumber parseSymbolicExpr(const py::object& pyExpr); - - static SymbolOrNumber parseParam(const py::object& param); - - static void addOperation(QuantumComputation& qc, OpType type, - const py::list& qargs, const py::list& params, - const py::dict& qubitMap); - - static void addTwoTargetOperation(QuantumComputation& qc, OpType type, - const py::list& qargs, - const py::list& params, - const py::dict& qubitMap); - - static void importDefinition(QuantumComputation& qc, const py::object& circ, - const py::list& qargs, const py::list& cargs, - const py::dict& qubitMap, - const py::dict& clbitMap); - - static void importInitialLayout(QuantumComputation& qc, - const py::object& circ); -}; -} // namespace qc::qiskit diff --git a/pyproject.toml b/pyproject.toml index 54044f90a..3f18dbde6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,8 +78,7 @@ build.targets = [ "mqt-core-zx", "mqt-core-ds", "mqt-core-na", - "mqt-core-python", - "ir" + "ir", ] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 924935aa9..5259d77a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,30 +87,6 @@ add_subdirectory(zx) # add NA library add_subdirectory(na) -# ** Note ** The following target will soon be removed from the project. All top-level projects -# should switch to using the mqt-core Python package. -if(BINDINGS AND NOT TARGET mqt-core-python) - # add Python interface library - add_library( - ${MQT_CORE_TARGET_NAME}-python ${MQT_CORE_INCLUDE_BUILD_DIR}/python/qiskit/QuantumCircuit.hpp - python/qiskit/QuantumCircuit.cpp) - - # link with main project library and pybind11 libraries - target_link_libraries( - ${MQT_CORE_TARGET_NAME}-python - PUBLIC MQT::CoreIR pybind11::pybind11 - PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) - - # add MQT alias - add_library(MQT::CorePython ALIAS ${MQT_CORE_TARGET_NAME}-python) - set_target_properties( - ${MQT_CORE_TARGET_NAME}-python - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CorePython) - list(APPEND MQT_CORE_TARGETS ${MQT_CORE_TARGET_NAME}-python) -endif() - if(BUILD_MQT_CORE_BINDINGS) add_subdirectory(python) endif() diff --git a/src/python/qiskit/QuantumCircuit.cpp b/src/python/qiskit/QuantumCircuit.cpp deleted file mode 100644 index 69ef1f044..000000000 --- a/src/python/qiskit/QuantumCircuit.cpp +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright (c) 2025 Chair for Design Automation, TUM - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -#include "python/qiskit/QuantumCircuit.hpp" - -#include "Definitions.hpp" -#include "ir/QuantumComputation.hpp" -#include "ir/operations/Control.hpp" -#include "ir/operations/Expression.hpp" -#include "ir/operations/NonUnitaryOperation.hpp" -#include "ir/operations/OpType.hpp" -#include "ir/operations/StandardOperation.hpp" -#include "ir/operations/SymbolicOperation.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -void qc::qiskit::QuantumCircuit::import(qc::QuantumComputation& qc, - const py::object& circ) { - qc.reset(); - - const py::object quantumCircuit = - py::module::import("qiskit").attr("QuantumCircuit"); - - if (!py::isinstance(circ, quantumCircuit)) { - throw QFRException( - "[import] Python object needs to be a Qiskit QuantumCircuit"); - } - - if (!circ.attr("name").is_none()) { - qc.setName(circ.attr("name").cast()); - } - - // handle qubit registers - const py::object qubit = py::module::import("qiskit.circuit").attr("Qubit"); - const py::object ancillaQubit = - py::module::import("qiskit.circuit").attr("AncillaQubit"); - const py::object ancillaRegister = - py::module::import("qiskit.circuit").attr("AncillaRegister"); - int qubitIndex = 0; - const py::dict qubitMap{}; - auto&& circQregs = circ.attr("qregs"); - for (const auto qreg : circQregs) { - // create corresponding register in quantum computation - auto size = qreg.attr("size").cast(); - auto name = qreg.attr("name").cast(); - if (py::isinstance(qreg, ancillaRegister)) { - qc.addAncillaryRegister(size, name); - // add ancillas to qubit map - for (std::size_t i = 0; i < size; ++i) { - qubitMap[ancillaQubit(qreg, i)] = qubitIndex; - qubitIndex++; - } - } else { - qc.addQubitRegister(size, name); - // add qubits to qubit map - for (std::size_t i = 0; i < size; ++i) { - qubitMap[qubit(qreg, i)] = qubitIndex; - qubitIndex++; - } - } - } - - // handle classical registers - const py::object clbit = py::module::import("qiskit.circuit").attr("Clbit"); - int clbitIndex = 0; - const py::dict clbitMap{}; - auto&& circCregs = circ.attr("cregs"); - for (const auto creg : circCregs) { - // create corresponding register in quantum computation - auto size = creg.attr("size").cast(); - auto name = creg.attr("name").cast(); - qc.addClassicalRegister(size, name); - - // add clbits to clbit map - for (std::size_t i = 0; i < size; ++i) { - clbitMap[clbit(creg, i)] = clbitIndex; - clbitIndex++; - } - } - - try { - qc.gphase(circ.attr("global_phase").cast()); - } catch (const py::cast_error& e) { - std::clog << e.what() << "\n"; - std::clog << "[import] Warning: Symbolic global phase values are not " - "supported yet. Ignoring global phase.\n"; - } - - // iterate over instructions - auto&& data = circ.attr("data"); - for (const auto pyinst : data) { - auto&& instruction = pyinst.attr("operation"); - auto&& qargs = pyinst.attr("qubits"); - auto&& cargs = pyinst.attr("clbits"); - auto&& params = instruction.attr("params"); - - emplaceOperation(qc, instruction, qargs, cargs, params, qubitMap, clbitMap); - } - - // import initial layout in case it is available - if (!circ.attr("_layout").is_none()) { - importInitialLayout(qc, circ); - } - qc.initializeIOMapping(); -} - -void qc::qiskit::QuantumCircuit::emplaceOperation( - qc::QuantumComputation& qc, const py::object& instruction, - const py::list& qargs, const py::list& cargs, const py::list& params, - const py::dict& qubitMap, const py::dict& clbitMap) { - static const auto NATIVELY_SUPPORTED_GATES = - std::set{"i", "id", "iden", - "x", "y", "z", - "h", "s", "sdg", - "t", "tdg", "p", - "u1", "rx", "ry", - "rz", "u2", "u", - "u3", "cx", "cy", - "cz", "cp", "cu1", - "ch", "crx", "cry", - "crz", "cu3", "ccx", - "swap", "cswap", "iswap", - "sx", "sxdg", "csx", - "mcx", "mcx_gray", "mcx_recursive", - "mcx_vchain", "mcphase", "mcrx", - "mcry", "mcrz", "dcx", - "ecr", "rxx", "ryy", - "rzx", "rzz", "xx_minus_yy", - "xx_plus_yy"}; - - auto instructionName = instruction.attr("name").cast(); - if (instructionName == "measure") { - auto control = qubitMap[qargs[0]].cast(); - auto target = clbitMap[cargs[0]].cast(); - qc.emplace_back(control, target); - } else if (instructionName == "barrier") { - Targets targets{}; - for (const auto qubit : qargs) { - auto target = qubitMap[qubit].cast(); - targets.emplace_back(target); - } - qc.emplace_back(targets, Barrier); - } else if (instructionName == "reset") { - Targets targets{}; - for (const auto qubit : qargs) { - auto target = qubitMap[qubit].cast(); - targets.emplace_back(target); - } - qc.reset(targets); - } else if (NATIVELY_SUPPORTED_GATES.count(instructionName) != 0) { - // natively supported operations - if (instructionName == "i" || instructionName == "id" || - instructionName == "iden") { - addOperation(qc, I, qargs, params, qubitMap); - } else if (instructionName == "x" || instructionName == "cx" || - instructionName == "ccx" || instructionName == "mcx_gray" || - instructionName == "mcx") { - addOperation(qc, X, qargs, params, qubitMap); - } else if (instructionName == "y" || instructionName == "cy") { - addOperation(qc, Y, qargs, params, qubitMap); - } else if (instructionName == "z" || instructionName == "cz") { - addOperation(qc, Z, qargs, params, qubitMap); - } else if (instructionName == "h" || instructionName == "ch") { - addOperation(qc, H, qargs, params, qubitMap); - } else if (instructionName == "s") { - addOperation(qc, S, qargs, params, qubitMap); - } else if (instructionName == "sdg") { - addOperation(qc, Sdg, qargs, params, qubitMap); - } else if (instructionName == "t") { - addOperation(qc, T, qargs, params, qubitMap); - } else if (instructionName == "tdg") { - addOperation(qc, Tdg, qargs, params, qubitMap); - } else if (instructionName == "rx" || instructionName == "crx" || - instructionName == "mcrx") { - addOperation(qc, RX, qargs, params, qubitMap); - } else if (instructionName == "ry" || instructionName == "cry" || - instructionName == "mcry") { - addOperation(qc, RY, qargs, params, qubitMap); - } else if (instructionName == "rz" || instructionName == "crz" || - instructionName == "mcrz") { - addOperation(qc, RZ, qargs, params, qubitMap); - } else if (instructionName == "p" || instructionName == "u1" || - instructionName == "cp" || instructionName == "cu1" || - instructionName == "mcphase") { - addOperation(qc, P, qargs, params, qubitMap); - } else if (instructionName == "sx" || instructionName == "csx") { - addOperation(qc, SX, qargs, params, qubitMap); - } else if (instructionName == "sxdg") { - addOperation(qc, SXdg, qargs, params, qubitMap); - } else if (instructionName == "u2") { - addOperation(qc, U2, qargs, params, qubitMap); - } else if (instructionName == "u" || instructionName == "u3" || - instructionName == "cu3") { - addOperation(qc, U, qargs, params, qubitMap); - } else if (instructionName == "swap" || instructionName == "cswap") { - addTwoTargetOperation(qc, SWAP, qargs, params, qubitMap); - } else if (instructionName == "iswap") { - addTwoTargetOperation(qc, iSWAP, qargs, params, qubitMap); - } else if (instructionName == "dcx") { - addTwoTargetOperation(qc, DCX, qargs, params, qubitMap); - } else if (instructionName == "ecr") { - addTwoTargetOperation(qc, ECR, qargs, params, qubitMap); - } else if (instructionName == "rxx") { - addTwoTargetOperation(qc, RXX, qargs, params, qubitMap); - } else if (instructionName == "ryy") { - addTwoTargetOperation(qc, RYY, qargs, params, qubitMap); - } else if (instructionName == "rzx") { - addTwoTargetOperation(qc, RZX, qargs, params, qubitMap); - } else if (instructionName == "rzz") { - addTwoTargetOperation(qc, RZZ, qargs, params, qubitMap); - } else if (instructionName == "xx_minus_yy") { - addTwoTargetOperation(qc, XXminusYY, qargs, params, qubitMap); - } else if (instructionName == "xx_plus_yy") { - addTwoTargetOperation(qc, XXplusYY, qargs, params, qubitMap); - } else if (instructionName == "mcx_recursive") { - if (qargs.size() <= 5) { - addOperation(qc, X, qargs, params, qubitMap); - } else { - auto qargsCopy = qargs.attr("copy")(); - qargsCopy.attr("pop")(); // discard ancillaries - addOperation(qc, X, qargsCopy, params, qubitMap); - } - } else if (instructionName == "mcx_vchain") { - auto size = qargs.size(); - const std::size_t ncontrols = (size + 1) / 2; - auto qargsCopy = qargs.attr("copy")(); - // discard ancillaries - for (std::size_t i = 0; i < ncontrols - 2; ++i) { - qargsCopy.attr("pop")(); - } - addOperation(qc, X, qargsCopy, params, qubitMap); - } - } else { - try { - importDefinition(qc, instruction.attr("definition"), qargs, cargs, - qubitMap, clbitMap); - } catch (py::error_already_set& e) { - std::cerr << "Failed to import instruction " << instructionName - << " from Qiskit QuantumCircuit\n"; - std::cerr << e.what() << "\n"; - } - } -} - -qc::SymbolOrNumber -qc::qiskit::QuantumCircuit::parseSymbolicExpr(const py::object& pyExpr) { - static const std::regex SUMMANDS("[+|-]?[^+-]+"); - static const std::regex PRODUCTS("[\\*/]?[^\\*/]+"); - - auto exprStr = pyExpr.attr("__str__")().cast(); - exprStr.erase(std::remove(exprStr.begin(), exprStr.end(), ' '), - exprStr.end()); // strip whitespace - - auto sumIt = std::sregex_iterator(exprStr.begin(), exprStr.end(), SUMMANDS); - const auto sumEnd = std::sregex_iterator(); - - qc::Symbolic sym; - bool isConst = true; - - while (sumIt != sumEnd) { - auto match = *sumIt; - auto matchStr = match.str(); - const int sign = matchStr[0] == '-' ? -1 : 1; - if (matchStr[0] == '+' || matchStr[0] == '-') { - matchStr.erase(0, 1); - } - - auto prodIt = - std::sregex_iterator(matchStr.begin(), matchStr.end(), PRODUCTS); - auto prodEnd = std::sregex_iterator(); - - fp coeff = 1.0; - std::string var; - while (prodIt != prodEnd) { - auto prodMatch = *prodIt; - auto prodStr = prodMatch.str(); - - const bool isDiv = prodStr[0] == '/'; - if (prodStr[0] == '*' || prodStr[0] == '/') { - prodStr.erase(0, 1); - } - - std::istringstream iss(prodStr); - fp f{}; - iss >> f; - - if (iss.eof() && !iss.fail()) { - coeff *= isDiv ? 1.0 / f : f; - } else { - var = prodStr; - } - - ++prodIt; - } - if (var.empty()) { - sym += coeff; - } else { - isConst = false; - sym += sym::Term(sym::Variable{var}, sign * coeff); - } - ++sumIt; - } - - if (isConst) { - return {sym.getConst()}; - } - return {sym}; -} - -qc::SymbolOrNumber -qc::qiskit::QuantumCircuit::parseParam(const py::object& param) { - try { - return param.cast(); - } catch ([[maybe_unused]] py::cast_error& e) { - return parseSymbolicExpr(param); - } -} - -void qc::qiskit::QuantumCircuit::addOperation(qc::QuantumComputation& qc, - qc::OpType type, - const py::list& qargs, - const py::list& params, - const py::dict& qubitMap) { - std::vector qubits{}; - for (const auto qubit : qargs) { - auto target = qubitMap[qubit].cast(); - qubits.emplace_back(target); - } - auto target = qubits.back().qubit; - qubits.pop_back(); - std::vector parameters{}; - for (const auto& param : params) { - parameters.emplace_back( - parseParam(py::reinterpret_borrow(param))); - } - const Controls controls(qubits.cbegin(), qubits.cend()); - if (std::all_of(parameters.cbegin(), parameters.cend(), [](const auto& p) { - return std::holds_alternative(p); - })) { - std::vector fpParams{}; - std::transform(parameters.cbegin(), parameters.cend(), - std::back_inserter(fpParams), - [](const auto& p) { return std::get(p); }); - qc.emplace_back(controls, target, type, fpParams); - } else { - qc.emplace_back(controls, target, type, parameters); - for (const auto& p : parameters) { - qc.addVariables(p); - } - } -} - -void qc::qiskit::QuantumCircuit::addTwoTargetOperation( - qc::QuantumComputation& qc, qc::OpType type, const py::list& qargs, - const py::list& params, const py::dict& qubitMap) { - std::vector qubits{}; - for (const auto qubit : qargs) { - auto target = qubitMap[qubit].cast(); - qubits.emplace_back(target); - } - auto target1 = qubits.back().qubit; - qubits.pop_back(); - auto target0 = qubits.back().qubit; - qubits.pop_back(); - std::vector parameters{}; - for (const auto& param : params) { - parameters.emplace_back( - parseParam(py::reinterpret_borrow(param))); - } - const Controls controls(qubits.cbegin(), qubits.cend()); - if (std::all_of(parameters.cbegin(), parameters.cend(), [](const auto& p) { - return std::holds_alternative(p); - })) { - std::vector fpParams{}; - std::transform(parameters.cbegin(), parameters.cend(), - std::back_inserter(fpParams), - [](const auto& p) { return std::get(p); }); - qc.emplace_back(controls, target0, target1, type, - fpParams); - } else { - qc.emplace_back(controls, target0, target1, type, - parameters); - for (const auto& p : parameters) { - qc.addVariables(p); - } - } -} - -void qc::qiskit::QuantumCircuit::importDefinition( - qc::QuantumComputation& qc, const py::object& circ, const py::list& qargs, - const py::list& cargs, const py::dict& qubitMap, const py::dict& clbitMap) { - const py::dict qargMap{}; - py::list&& defQubits = circ.attr("qubits"); - for (size_t i = 0; i < qargs.size(); ++i) { - qargMap[defQubits[i]] = qargs[i]; - } - - const py::dict cargMap{}; - py::list&& defClbits = circ.attr("clbits"); - for (size_t i = 0; i < cargs.size(); ++i) { - cargMap[defClbits[i]] = cargs[i]; - } - - auto&& data = circ.attr("data"); - for (const auto pyinst : data) { - auto&& instruction = pyinst.attr("operation"); - - const py::list& instQargs = pyinst.attr("qubits"); - py::list mappedQargs{}; - for (auto&& instQarg : instQargs) { - mappedQargs.append(qargMap[instQarg]); - } - - const py::list& instCargs = pyinst.attr("clbits"); - py::list mappedCargs{}; - for (auto&& instCarg : instCargs) { - mappedCargs.append(cargMap[instCarg]); - } - - auto&& instParams = instruction.attr("params"); - - emplaceOperation(qc, instruction, mappedQargs, mappedCargs, instParams, - qubitMap, clbitMap); - } -} - -void qc::qiskit::QuantumCircuit::importInitialLayout(qc::QuantumComputation& qc, - const py::object& circ) { - const py::object qubit = py::module::import("qiskit.circuit").attr("Qubit"); - - // get layout - auto layout = circ.attr("layout"); - - if (layout.is_none()) { - return; - } - - auto initial_layout = layout.attr("initial_layout"); - - // create map between registers used in the layout and logical qubit indices - // NOTE: this only works correctly if the registers were originally declared - // in alphabetical order! - const auto registers = initial_layout.attr("get_registers")().cast(); - std::size_t logicalQubitIndex = 0U; - const py::dict logicalQubitIndices{}; - - // the ancilla register - decltype(registers.get_type()) ancillaRegister = py::none(); - - for (const auto qreg : registers) { - // skip ancillary register since it is handled as the very last qubit - // register - if (const auto qregName = qreg.attr("name").cast(); - qregName == "ancilla") { - ancillaRegister = qreg; - continue; - } - - const auto size = qreg.attr("size").cast(); - for (std::size_t i = 0U; i < size; ++i) { - logicalQubitIndices[qubit(qreg, i)] = logicalQubitIndex; - ++logicalQubitIndex; - } - } - - // handle ancillary register, if there is one - if (!ancillaRegister.is_none()) { - const auto size = ancillaRegister.attr("size").cast(); - for (std::size_t i = 0U; i < size; ++i) { - logicalQubitIndices[qubit(ancillaRegister, i)] = logicalQubitIndex; - qc.setLogicalQubitAncillary(static_cast(logicalQubitIndex)); - ++logicalQubitIndex; - } - } - - // get a map of physical to logical qubits - const auto physicalQubits = - initial_layout.attr("get_physical_bits")().cast(); - - // create initial layout (and assume identical output permutation) - for (const auto& [physicalQubit, logicalQubit] : physicalQubits) { - if (logicalQubitIndices.contains(logicalQubit)) { - qc.initialLayout[physicalQubit.cast()] = - logicalQubitIndices[logicalQubit].cast(); - qc.outputPermutation[physicalQubit.cast()] = - logicalQubitIndices[logicalQubit].cast(); - } - } -} From d84d02fe167f5edbb15c0fb4e2762e1d3a656da4 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Tue, 6 Aug 2024 22:19:24 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=8F=81=20patch=20dll=20directory=20on?= =?UTF-8?q?=20Windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit additionally ignore DLLs in wheels for delvewheel repair. Delvewheel cannot find these by default and would error if this was not set. Signed-off-by: Lukas Burgholzer --- pyproject.toml | 4 ++-- src/mqt/core/__init__.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3f18dbde6..525e85d95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -253,8 +253,8 @@ environment = { DEPLOY = "ON" } environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } [tool.cibuildwheel.windows] -before-build = "uv pip install delvewheel>=1.7.3" -repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt" +before-build = "uv pip install delvewheel>=1.9.0" +repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing" environment = { CMAKE_GENERATOR = "Ninja" } [[tool.cibuildwheel.overrides]] diff --git a/src/mqt/core/__init__.py b/src/mqt/core/__init__.py index 8c1510442..94b477e53 100644 --- a/src/mqt/core/__init__.py +++ b/src/mqt/core/__init__.py @@ -10,7 +10,22 @@ from __future__ import annotations import os +import sys from pathlib import Path + +# under Windows, make sure to add the appropriate DLL directory to the PATH +if sys.platform == "win32": + + def _dll_patch() -> None: + """Add the DLL directory to the PATH.""" + import sysconfig + + bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin" + os.add_dll_directory(str(bin_dir)) + + _dll_patch() + del _dll_patch + from typing import TYPE_CHECKING from ._version import version as __version__ From c4767199239ea355b723c4d5f7a2e1c2d7242710 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 9 Aug 2024 16:02:09 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=A9=B9=20explicitly=20export=20the=20?= =?UTF-8?q?`RealNumber`=20constants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer Signed-off-by: Lukas Burgholzer Signed-off-by: burgholzer --- include/mqt-core/dd/RealNumber.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/include/mqt-core/dd/RealNumber.hpp b/include/mqt-core/dd/RealNumber.hpp index 1d4866445..627ead174 100644 --- a/include/mqt-core/dd/RealNumber.hpp +++ b/include/mqt-core/dd/RealNumber.hpp @@ -10,6 +10,7 @@ #pragma once #include "dd/DDDefinitions.hpp" +#include "mqt_core_dd_export.h" #include #include @@ -239,11 +240,11 @@ struct RealNumber { namespace constants { // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) /// The constant zero. -extern RealNumber zero; +MQT_CORE_DD_EXPORT extern RealNumber zero; /// The constant one. -extern RealNumber one; +MQT_CORE_DD_EXPORT extern RealNumber one; /// The constant sqrt(2)/2 = 1/sqrt(2). -extern RealNumber sqrt2over2; +MQT_CORE_DD_EXPORT extern RealNumber sqrt2over2; // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) /** From f5e67935bb05055a272019a055219aa4ba2ea469 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Mon, 6 Jan 2025 17:33:47 +0100 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=91=B7=F0=9F=94=A7=20do=20not=20build?= =?UTF-8?q?=20emulated=20wheels=20for=20mqt-core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is mostly to avoid PyPI space limitations as we typically support all non-EOL Python versions (5) and a total of three emulated platforms. This adds 15 wheels to each release on PyPI, which amounts to over 42% of all wheels (15/35). It also considerably adds to the overall build time. Signed-off-by: burgholzer --- .github/workflows/cd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6a452fb38..54b4e6153 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,4 @@ -name: CD +name: CD 🚀 on: push: branches: [main] @@ -21,8 +21,8 @@ jobs: with: # Do not include local version information on pushes to main to facilitate TestPyPI uploads. no-local-version: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} - # Do not build emulated wheels on pushes to main to reduce runner load for CD. - build-emulated-wheels: ${{ github.ref != 'refs/heads/main' || github.event_name != 'push' }} + # Do not build emulated wheels for mqt-core to avoid issues with PyPI space limitations. + build-emulated-wheels: false # Downloads the previously generated artifacts and deploys to TestPyPI on pushes to main deploy-test-pypi: