From 2643b7b70335c55b447a7f82e825a9c85e88e701 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 26 Jan 2024 16:44:28 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20switch=20to=20mqt-core=20Python=20p?= =?UTF-8?q?ackage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .pre-commit-config.yaml | 1 + cmake/ExternalDependencies.cmake | 19 ++++++--- noxfile.py | 1 + pyproject.toml | 9 +++- src/mqt/qmap/__init__.py | 2 - src/mqt/qmap/clifford_synthesis.py | 25 ++++-------- src/mqt/qmap/compile.py | 15 +++++-- src/mqt/qmap/pyqmap.pyi | 13 +----- src/python/bindings.cpp | 59 +++------------------------ test/python/constraints.txt | 1 + test/python/test_cliffordsynthesis.py | 7 ---- 11 files changed, 50 insertions(+), 102 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a908d48b..26651dda8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,6 +77,7 @@ repos: - importlib_resources - numpy - pytest + - mqt.core~=2.2.2 - mqt.qcec # Check for spelling diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index b8b9b5bab..0e7a8f05b 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -10,6 +10,19 @@ if(NOT Z3_FOUND) endif() if(BUILD_MQT_QMAP_BINDINGS) + # Manually detect the installed mqt-core package. + execute_process( + COMMAND "${Python_EXECUTABLE}" -m mqt.core --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE mqt-core_DIR + ERROR_QUIET) + + # Add the detected directory to the CMake prefix path. + if(mqt-core_DIR) + list(APPEND CMAKE_PREFIX_PATH "${mqt-core_DIR}") + message(STATUS "Found mqt-core package: ${mqt-core_DIR}") + endif() + if(NOT SKBUILD) # Manually detect the installed pybind11 package. execute_process( @@ -25,12 +38,6 @@ if(BUILD_MQT_QMAP_BINDINGS) find_package(pybind11 CONFIG REQUIRED) endif() -set(FETCHCONTENT_SOURCE_DIR_MQT-CORE - ${PROJECT_SOURCE_DIR}/extern/mqt-core - CACHE - PATH - "Path to the source directory of the mqt-core library. This variable is used by FetchContent to download the library if it is not already available." -) set(MQT_CORE_VERSION 2.2.2 CACHE STRING "MQT Core version") diff --git a/noxfile.py b/noxfile.py index 96c950a34..e4f10feb3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -17,6 +17,7 @@ PYTHON_ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] BUILD_REQUIREMENTS = [ + "mqt.core~=2.2.2", "scikit-build-core[pyproject]>=0.6.1", "setuptools_scm>=7", "pybind11>=2.11", diff --git a/pyproject.toml b/pyproject.toml index 077d196a9..6cc88fea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,10 @@ [build-system] -requires = ["scikit-build-core>=0.6.1", "setuptools-scm>=7", "pybind11>=2.11"] +requires = [ + "scikit-build-core>=0.6.1", + "setuptools-scm>=7", + "pybind11>=2.11", + "mqt.core~=2.2.2", +] build-backend = "scikit_build_core.build" [project] @@ -36,7 +41,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "qiskit[qasm3-import]>=0.45.0", + "mqt.core[qiskit]~=2.2.2", "rustworkx[all]>=0.13.0", "importlib_resources>=5.0; python_version < '3.10'", "typing_extensions>=4.0" diff --git a/src/mqt/qmap/__init__.py b/src/mqt/qmap/__init__.py index 92996363d..dfb3ccfac 100644 --- a/src/mqt/qmap/__init__.py +++ b/src/mqt/qmap/__init__.py @@ -35,7 +35,6 @@ LookaheadHeuristic, MappingResults, Method, - QuantumComputation, SwapReduction, SynthesisConfiguration, SynthesisResults, @@ -64,7 +63,6 @@ "SynthesisConfiguration", "SynthesisResults", "TargetMetric", - "QuantumComputation", "Tableau", "CliffordSynthesizer", "synthesize_clifford", diff --git a/src/mqt/qmap/clifford_synthesis.py b/src/mqt/qmap/clifford_synthesis.py index 444d32afb..6ed63c43b 100644 --- a/src/mqt/qmap/clifford_synthesis.py +++ b/src/mqt/qmap/clifford_synthesis.py @@ -2,33 +2,26 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from .compile import CircuitInputType from qiskit import QuantumCircuit, qasm3 from qiskit.quantum_info import Clifford, PauliList from qiskit.transpiler.layout import TranspileLayout +from mqt.core.io import load + from .compile import extract_initial_layout_from_qasm from .pyqmap import ( CliffordSynthesizer, - QuantumComputation, SynthesisConfiguration, SynthesisResults, Tableau, ) -def _import_circuit(circuit: str | QuantumCircuit | QuantumComputation) -> QuantumComputation: - """Import a circuit from a string, a QuantumCircuit, or a QuantumComputation.""" - if isinstance(circuit, QuantumCircuit): - return QuantumComputation.from_qiskit(circuit) - if isinstance(circuit, str): - if circuit.endswith(".qasm"): - return QuantumComputation.from_file(circuit) - return QuantumComputation.from_qasm_str(circuit) - return circuit - - def _reverse_paulis(paulis: list[str]) -> list[str]: return [s[0] + s[:0:-1] if s[0] in "+-" else s[::-1] for s in paulis] @@ -84,11 +77,9 @@ def _circuit_from_qasm(qasm: str) -> QuantumCircuit: """Create a proper :class:`qiskit.QuantumCircuit` from a QASM string (including layout information).""" circ = qasm3.loads(qasm) layout = extract_initial_layout_from_qasm(qasm, circ.qregs) - circ._layout = TranspileLayout( # noqa: SLF001 initial_layout=layout, input_qubit_mapping=layout.get_virtual_bits() ) - return circ @@ -138,7 +129,7 @@ def synthesize_clifford( def optimize_clifford( - circuit: str | QuantumCircuit | QuantumComputation, + circuit: CircuitInputType, initial_tableau: str | Clifford | PauliList | Tableau | None = None, include_destabilizers: bool = False, **kwargs: Any, # noqa: ANN401 @@ -168,7 +159,7 @@ def optimize_clifford( """ config = _config_from_kwargs(kwargs) - qc = _import_circuit(circuit) + qc = load(circuit) if initial_tableau is not None: synthesizer = CliffordSynthesizer(_import_tableau(initial_tableau, include_destabilizers), qc) else: diff --git a/src/mqt/qmap/compile.py b/src/mqt/qmap/compile.py index 4f9d99dc8..b66b71704 100644 --- a/src/mqt/qmap/compile.py +++ b/src/mqt/qmap/compile.py @@ -2,18 +2,26 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from qiskit import QuantumCircuit, QuantumRegister, qasm3 from qiskit.transpiler import Layout, TranspileLayout if TYPE_CHECKING: + from os import PathLike + from qiskit.providers import Backend from qiskit.providers.models import BackendProperties from qiskit.transpiler.target import Target + from mqt.core import QuantumComputation + from .visualization import SearchVisualizer + CircuitInputType = Union[QuantumComputation, str, PathLike[str], QuantumCircuit] + +from mqt.core.io import load + from .load_architecture import load_architecture from .load_calibration import load_calibration from .pyqmap import ( @@ -59,7 +67,7 @@ def extract_initial_layout_from_qasm(qasm: str, qregs: list[QuantumRegister]) -> def compile( # noqa: A001 - circ: QuantumCircuit | str, + circ: CircuitInputType, arch: str | Arch | Architecture | Backend | None, calibration: str | BackendProperties | Target | None = None, method: str | Method = "heuristic", @@ -185,7 +193,8 @@ def compile( # noqa: A001 config.lookaheads = lookaheads config.lookahead_factor = lookahead_factor - results = map(circ, architecture, config) + qc = load(circ) + results = map(qc, architecture, config) circ = qasm3.loads(results.mapped_circuit) layout = extract_initial_layout_from_qasm(results.mapped_circuit, circ.qregs) diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index 5805bf7bd..efa5a6e89 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -1,6 +1,6 @@ from typing import Any, ClassVar, overload -from qiskit import QuantumCircuit +from mqt.core import QuantumComputation class Arch: __members__: ClassVar[dict[Arch, int]] = ... # read-only @@ -376,7 +376,7 @@ class SwapReduction: @property def value(self) -> int: ... -def map(circ: str | QuantumCircuit, arch: Architecture, config: Configuration) -> MappingResults: ... # noqa: A001 +def map(circ: QuantumComputation, arch: Architecture, config: Configuration) -> MappingResults: ... # noqa: A001 class TargetMetric: __members__: ClassVar[dict[TargetMetric, int]] = ... # read-only @@ -468,15 +468,6 @@ class SynthesisResults: @property def two_qubit_gates(self) -> int: ... -class QuantumComputation: - def __init__(self) -> None: ... - @staticmethod - def from_file(file: str) -> QuantumComputation: ... - @staticmethod - def from_qasm_str(qasm: str) -> QuantumComputation: ... - @staticmethod - def from_qiskit(circuit: QuantumCircuit) -> QuantumComputation: ... - class Tableau: @overload def __init__(self, n: int, include_stabilizers: bool = False) -> None: ... diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 766c3d4a6..900c8d74e 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -3,52 +3,32 @@ // See README.md or go to https://github.com/cda-tum/qmap for more information. // +#include "QuantumComputation.hpp" #include "cliffordsynthesis/CliffordSynthesizer.hpp" #include "exact/ExactMapper.hpp" #include "heuristic/HeuristicMapper.hpp" #include "nlohmann/json.hpp" #include "pybind11/pybind11.h" #include "pybind11/stl.h" -#include "pybind11_json/pybind11_json.hpp" -#include "python/qiskit/QuantumCircuit.hpp" namespace py = pybind11; using namespace pybind11::literals; -void loadQC(qc::QuantumComputation& qc, const py::object& circ) { - try { - if (py::isinstance(circ)) { - auto&& file = circ.cast(); - qc.import(file); - } else { - qc::qiskit::QuantumCircuit::import(qc, circ); - } - } catch (std::exception const& e) { - std::stringstream ss{}; - ss << "Could not import circuit: " << e.what(); - throw std::invalid_argument(ss.str()); - } -} - // c++ binding function -MappingResults map(const py::object& circ, Architecture& arch, +MappingResults map(const qc::QuantumComputation& circ, Architecture& arch, Configuration& config) { - qc::QuantumComputation qc{}; - - loadQC(qc, circ); - if (config.useTeleportation) { config.teleportationQubits = - std::min((arch.getNqubits() - qc.getNqubits()) & ~1U, + std::min((arch.getNqubits() - circ.getNqubits()) & ~1U, static_cast(8)); } std::unique_ptr mapper; try { if (config.method == Method::Heuristic) { - mapper = std::make_unique(qc, arch); + mapper = std::make_unique(circ, arch); } else if (config.method == Method::Exact) { - mapper = std::make_unique(qc, arch); + mapper = std::make_unique(circ, arch); } } catch (std::exception const& e) { std::stringstream ss{}; @@ -657,35 +637,6 @@ PYBIND11_MODULE(pyqmap, m) { "Constructs a tableau from two lists of Pauli strings, the Stabilizers" "and Destabilizers."); - auto quantumComputation = py::class_( - m, "QuantumComputation", - "A class for the intermediate representation of quantum circuits in the " - "Munich Quantum Toolkit."); - quantumComputation.def_static( - "from_file", - [](const std::string& filename) { - return qc::QuantumComputation(filename); - }, - "filename"_a, "Reads a quantum circuit from a file."); - quantumComputation.def_static( - "from_qasm_str", - [](const std::string& qasm) { - std::stringstream ss(qasm); - qc::QuantumComputation qc{}; - qc.import(ss, qc::Format::OpenQASM3); - return qc; - }, - "qasm"_a, "Reads a quantum circuit from a qasm string."); - quantumComputation.def_static( - "from_qiskit", - [](const py::object& circuit) { - qc::QuantumComputation qc{}; - qc::qiskit::QuantumCircuit::import(qc, circuit); - return qc; - }, - "circuit"_a, - "Reads a quantum circuit from a Qiskit :class:`QuantumCircuit`."); - auto synthesizer = py::class_( m, "CliffordSynthesizer", "A class for synthesizing Clifford circuits."); diff --git a/test/python/constraints.txt b/test/python/constraints.txt index a6b63750d..576b4ce12 100644 --- a/test/python/constraints.txt +++ b/test/python/constraints.txt @@ -7,6 +7,7 @@ typing_extensions==4.0.0 qiskit==0.45.0 rustworkx==0.13.0 mqt.qcec==2.0.0 +mqt.core==2.2.2 distinctipy==1.2.2 plotly==5.15.0 networkx==2.5 diff --git a/test/python/test_cliffordsynthesis.py b/test/python/test_cliffordsynthesis.py index a1c3e969f..7e03be861 100644 --- a/test/python/test_cliffordsynthesis.py +++ b/test/python/test_cliffordsynthesis.py @@ -227,13 +227,6 @@ def bell_circuit() -> QuantumCircuit: return circ -def test_optimize_quantum_computation(bell_circuit: QuantumCircuit) -> None: - """Test that we can optimize an MQT QuantumComputation.""" - qc = qmap.QuantumComputation.from_qiskit(bell_circuit) - circ, _ = qmap.optimize_clifford(circuit=qc) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - def test_optimize_from_qasm_file(bell_circuit: QuantumCircuit) -> None: """Test that we can optimize from a QASM file.""" with Path("bell.qasm").open("w") as f: