From 7cca7dbe3af84ccf04076addbb46be897d1974bb Mon Sep 17 00:00:00 2001 From: burgholzer Date: Wed, 10 Jan 2024 22:07:58 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20switch=20to=20mqt-core=20Python?= =?UTF-8?q?=20package?= 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 ++- extern/mqt-core | 2 +- noxfile.py | 15 +- pyproject.toml | 10 +- src/mqt/qcec/__init__.py | 11 ++ src/mqt/qcec/parameterized.py | 192 ++++++++++++------------ src/mqt/qcec/pyqcec.pyi | 6 +- src/mqt/qcec/verify.py | 26 +++- src/mqt/qcec/verify_compilation_flow.py | 5 +- src/python/CMakeLists.txt | 26 +++- src/python/bindings.cpp | 51 +------ test/python/constraints.txt | 1 + test/python/test_construction.py | 20 +-- 14 files changed, 196 insertions(+), 189 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 290ae105..0c9937f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -100,6 +100,7 @@ repos: - importlib_resources - numpy - pytest + - mqt-core~=2.2.2 # Check for spelling - repo: https://github.com/codespell-project/codespell diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 724b287a..7be303d1 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -4,6 +4,19 @@ include(FetchContent) set(FETCH_PACKAGES "") if(BUILD_MQT_QCEC_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( @@ -19,12 +32,6 @@ if(BUILD_MQT_QCEC_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/extern/mqt-core b/extern/mqt-core index fc7d8660..f2533950 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit fc7d8660e6d2007befbcdf70abdad0e845e67ae8 +Subproject commit f2533950fccdab892dd3b193d3b10aebaf952dd6 diff --git a/noxfile.py b/noxfile.py index f6ef02ce..484b17be 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", @@ -54,7 +55,12 @@ def _run_tests( _extras.append("coverage") posargs.append("--cov-config=pyproject.toml") - session.install(*BUILD_REQUIREMENTS, *install_args, env=env) + # On Linux, `mqt-core` needs to be installed with `--no-binary` to avoid ABI + # incompatibility issues caused by compiling with different GCC versions. + if sys.platform == "linux": + install_args = ["--no-binary", "mqt.core", *install_args] + + session.install("-v", *BUILD_REQUIREMENTS, *install_args, env=env) install_arg = f"-ve.[{','.join(_extras)}]" session.install("--no-build-isolation", install_arg, *install_args, env=env) session.run("pytest", *run_args, *posargs, env=env) @@ -89,8 +95,11 @@ def docs(session: nox.Session) -> None: session.error("Must not specify non-HTML builder with --serve") extra_installs = ["sphinx-autobuild"] if args.serve else [] - session.install(*BUILD_REQUIREMENTS, *extra_installs) - session.install("--no-build-isolation", "-ve.[docs]") + # On Linux, `mqt-core` needs to be installed with `--no-binary` to avoid ABI + # incompatibility issues caused by compiling with different GCC versions. + install_args = ["--no-binary", "mqt.core"] if sys.platform == "linux" else [] + session.install(*BUILD_REQUIREMENTS, *extra_installs, *install_args) + session.install("--no-build-isolation", "-ve.[docs]", *install_args) session.chdir("docs") if args.builder == "linkcheck": diff --git a/pyproject.toml b/pyproject.toml index a4bcb2c1..92be1db4 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] @@ -35,9 +40,9 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ + "mqt.core[qiskit]~=2.2.2", "importlib_resources>=5.0; python_version < '3.10'", "typing_extensions>=4.2", - "qiskit[qasm3-import]>=0.45.0", ] dynamic = ["version"] @@ -100,7 +105,6 @@ sdist.exclude = [ [tool.scikit-build.cmake.define] BUILD_MQT_QCEC_TESTS = "OFF" BUILD_MQT_QCEC_BINDINGS = "ON" -ENABLE_IPO = "ON" [tool.check-sdist] diff --git a/src/mqt/qcec/__init__.py b/src/mqt/qcec/__init__.py index fd879cdb..de56b0dc 100644 --- a/src/mqt/qcec/__init__.py +++ b/src/mqt/qcec/__init__.py @@ -6,6 +6,17 @@ from __future__ import annotations +import sys + +# under Windows, make sure to add the appropriate DLL directory to the PATH +if sys.platform == "win32": # pragma: no cover + import os + import sysconfig + from pathlib import Path + + bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin" + os.add_dll_directory(str(bin_dir)) + from ._version import version as __version__ from .compilation_flow_profiles import AncillaMode, generate_profile from .pyqcec import ( diff --git a/src/mqt/qcec/parameterized.py b/src/mqt/qcec/parameterized.py index d331a864..c44e5df5 100644 --- a/src/mqt/qcec/parameterized.py +++ b/src/mqt/qcec/parameterized.py @@ -4,20 +4,20 @@ import time from itertools import chain -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from numpy.typing import NDArray - from qiskit import QuantumCircuit +from typing import TYPE_CHECKING, cast import numpy as np -from qiskit.circuit import Parameter, ParameterExpression + +from mqt.core.operations import SymbolicOperation +from mqt.core.symbolic import Expression from . import Configuration, EquivalenceCheckingManager, EquivalenceCriterion +if TYPE_CHECKING: + from numpy.typing import NDArray -def __is_parameterized(qc: QuantumCircuit | str) -> bool: - return not isinstance(qc, str) and qc.parameters + from mqt.core import QuantumComputation + from mqt.core.symbolic import Variable def __adjust_timeout(curr_timeout: float, res: EquivalenceCheckingManager.Results | float) -> float: @@ -30,12 +30,12 @@ def __adjust_timeout(curr_timeout: float, res: EquivalenceCheckingManager.Result def check_parameterized_zx( - circ1: QuantumCircuit | str, circ2: QuantumCircuit | str, configuration: Configuration + circ1: QuantumComputation, circ2: QuantumComputation, configuration: Configuration ) -> EquivalenceCheckingManager.Results: """Check circuits for equivalence with the ZX-calculus.""" ecm = EquivalenceCheckingManager(circ1, circ2, configuration) ecm.disable_all_checkers() - ecm.set_zx_checker(True) + ecm.set_zx_checker() ecm.run() @@ -43,44 +43,39 @@ def check_parameterized_zx( def extract_params( - circ1: QuantumCircuit, circ2: QuantumCircuit -) -> tuple[list[Parameter], NDArray[np.float64], NDArray[np.float64]]: + circ1: QuantumComputation, circ2: QuantumComputation +) -> tuple[list[Variable], NDArray[np.float64], NDArray[np.float64]]: """Extract parameters and equations of parameterized circuits.""" - p1 = set(circ1.parameters) - p2 = set(circ2.parameters) - - p = p1.union(p2) - - n_params = len(p) - exprs = list(chain(*[instr[0].params for instr in circ1.data + circ2.data if instr[0].params != []])) - - def is_expr(x: float | Parameter | ParameterExpression) -> bool: - return isinstance(x, (Parameter, ParameterExpression)) - - symb_params: list[Parameter | ParameterExpression] = [param for param in p if is_expr(param)] - symb_params.sort(key=lambda param: param.name) - symb_exprs = list(filter(is_expr, exprs)) - - offsets = np.zeros(len(symb_exprs)) - for row, expr in enumerate(symb_exprs): - zero_map = dict.fromkeys(expr.parameters, 0) - offsets[row] = -float(expr.bind(zero_map)) - - equs = np.zeros((len(symb_exprs), n_params)) - - for col, param in enumerate(symb_params): - for row, expr in enumerate(symb_exprs): - one_map = dict.fromkeys(expr.parameters, 0) - if param in expr.parameters: - one_map[param] = 1 - val = float((expr + offsets[row]).bind(one_map)) - equs[row, col] = val - - return symb_params, equs, offsets + symbolic_params = list(circ1.variables.union(circ2.variables)) + num_symbolic_params = len(symbolic_params) + + symbolic_expressions = [ + param + for op in chain(circ1, circ2) + if op.is_symbolic_operation() + for param in cast(SymbolicOperation, op).get_parameters() + if isinstance(param, Expression) and not param.is_constant() + ] + num_symbolic_expressions = len(symbolic_expressions) + + offsets = np.zeros(num_symbolic_expressions) + for row, expr in enumerate(symbolic_expressions): + zero_map = dict.fromkeys(expr.variables, 0.0) + offsets[row] = -float(expr.evaluate(zero_map)) + + equations = np.zeros((num_symbolic_expressions, num_symbolic_params)) + for col, param in enumerate(symbolic_params): + for row, expr in enumerate(symbolic_expressions): + one_map = dict.fromkeys(expr.variables, 0.0) + if param in expr.variables: + one_map[param] = 1.0 + equations[row, col] = (expr + offsets[row]).evaluate(one_map) + + return symbolic_params, equations, offsets def check_instantiated( - circ1: QuantumCircuit, circ2: QuantumCircuit, configuration: Configuration + circ1: QuantumComputation, circ2: QuantumComputation, configuration: Configuration ) -> EquivalenceCheckingManager.Results: """Check circuits for equivalence with DD equivalence checker.""" ecm = EquivalenceCheckingManager(circ1, circ2, configuration) @@ -92,7 +87,7 @@ def check_instantiated( def check_instantiated_random( - circ1: QuantumCircuit, circ2: QuantumCircuit, params: list[Parameter], configuration: Configuration + circ1: QuantumComputation, circ2: QuantumComputation, params: list[Variable], configuration: Configuration ) -> EquivalenceCheckingManager.Results: """Check whether circuits are equivalent for random instantiation of symbolic parameters.""" param_map = {} @@ -100,14 +95,14 @@ def check_instantiated_random( for p in params: param_map[p] = rng.random() * 2 * np.pi - circ1_inst = circ1.assign_parameters(param_map) - circ2_inst = circ2.assign_parameters(param_map) + circ1_inst = circ1.instantiate(param_map) + circ2_inst = circ2.instantiate(param_map) return check_instantiated(circ1_inst, circ2_inst, configuration) def check_parameterized( - circ1: QuantumCircuit | str, circ2: QuantumCircuit | str, configuration: Configuration + circ1: QuantumComputation, circ2: QuantumComputation, configuration: Configuration ) -> EquivalenceCheckingManager.Results: """Equivalence checking flow for parameterized circuit.""" total_preprocessing_time = 0.0 @@ -115,33 +110,33 @@ def check_parameterized( total_simulations_started = 0 total_simulations_finished = 0 - def update_stats(res: EquivalenceCheckingManager.Results) -> None: + def __update_stats(result: EquivalenceCheckingManager.Results) -> None: nonlocal total_preprocessing_time nonlocal total_runtime nonlocal total_simulations_started nonlocal total_simulations_finished - total_preprocessing_time += res.preprocessing_time - total_runtime += res.check_time - total_simulations_started += res.started_simulations - total_simulations_finished += res.performed_simulations + total_preprocessing_time += result.preprocessing_time + total_runtime += result.check_time + total_simulations_started += result.started_simulations + total_simulations_finished += result.performed_simulations - def write_stats(i: int, res: EquivalenceCheckingManager.Results) -> None: + def __write_stats(instantiation: int, result: EquivalenceCheckingManager.Results) -> None: nonlocal total_preprocessing_time nonlocal total_runtime nonlocal total_simulations_started nonlocal total_simulations_finished - res.check_time = total_runtime - res.preprocessing_time = total_preprocessing_time - res.started_simulations = total_simulations_started - res.performed_simulations = total_simulations_finished - res.performed_instantiations = i + result.check_time = total_runtime + result.preprocessing_time = total_preprocessing_time + result.started_simulations = total_simulations_started + result.performed_simulations = total_simulations_finished + result.performed_instantiations = instantiation res = check_parameterized_zx(circ1, circ2, configuration) if res.considered_equivalent(): return res - update_stats(res) + __update_stats(res) timeout = __adjust_timeout(configuration.execution.timeout, res) n_checks = configuration.parameterized.additional_instantiations @@ -149,70 +144,71 @@ def write_stats(i: int, res: EquivalenceCheckingManager.Results) -> None: parameters, mat, offsets = extract_params(circ1, circ2) - def instantiate_params( - qc1: QuantumCircuit, qc2: QuantumCircuit, b: NDArray[np.float64] - ) -> tuple[QuantumCircuit, QuantumCircuit, float]: + def __instantiate_params( + qc1: QuantumComputation, qc2: QuantumComputation, b: NDArray[np.float64] + ) -> tuple[QuantumComputation, QuantumComputation, float]: start_time = time.time() mat_pinv = np.linalg.pinv(mat) x = np.dot(mat_pinv, b) param_map = {param: x[i] for i, param in enumerate(parameters)} - qc1_bound = qc1.assign_parameters(param_map) - qc2_bound = qc2.assign_parameters(param_map) - - def round_zero_params(qc: QuantumCircuit) -> QuantumCircuit: - for instr in qc.data: - if not hasattr(instr[0], "mutable") or instr[0].mutable: - params = instr[0].params - instr[0].params = [float(x) for x in params] - instr[0].params = [0 if np.abs(x) < tol else x for x in instr[0].params] - return qc - - qc1_bound = round_zero_params(qc1_bound) - qc2_bound = round_zero_params(qc2_bound) - return qc1_bound, qc2_bound, time.time() - start_time + qc1_bound = qc1.instantiate(param_map) + qc2_bound = qc2.instantiate(param_map) - def instantiate_params_zero( - qc1: QuantumCircuit, qc2: QuantumCircuit - ) -> tuple[QuantumCircuit, QuantumCircuit, float]: - return instantiate_params(qc1, qc2, offsets) + def __round_zero_params(qc: QuantumComputation) -> None: + for op in qc: + if len(op.parameter) == 0: + continue - def instantiate_params_phases( - qc1: QuantumCircuit, qc2: QuantumCircuit - ) -> tuple[QuantumCircuit, QuantumCircuit, float]: - phases = [0, np.pi, np.pi / 2, -np.pi / 2, np.pi / 4, -np.pi / 4] - rng = np.random.default_rng() - b = rng.choice(phases, size=len(offsets)) + offsets - return instantiate_params(qc1, qc2, b) + for i, param in enumerate(op.parameter): + if np.abs(param) < tol: + op.parameter[i] = 0 - circ1_inst, circ2_inst, runtime = instantiate_params_zero(circ1, circ2) + __round_zero_params(qc1_bound) + __round_zero_params(qc2_bound) + return qc1_bound, qc2_bound, time.time() - start_time + + def __instantiate_params_zero( + qc1: QuantumComputation, qc2: QuantumComputation + ) -> tuple[QuantumComputation, QuantumComputation, float]: + return __instantiate_params(qc1, qc2, offsets) + + circ1_inst, circ2_inst, runtime = __instantiate_params_zero(circ1, circ2) timeout = __adjust_timeout(timeout, runtime) if timeout < 0: - write_stats(1, res) + __write_stats(1, res) res.equivalence = EquivalenceCriterion.no_information return res res = check_instantiated(circ1_inst, circ2_inst, configuration) - update_stats(res) + __update_stats(res) if res.equivalence == EquivalenceCriterion.not_equivalent: - write_stats(1, res) + __write_stats(1, res) return res + def __instantiate_params_phases( + qc1: QuantumComputation, qc2: QuantumComputation + ) -> tuple[QuantumComputation, QuantumComputation, float]: + phases = [0.0, np.pi, np.pi / 2, -np.pi / 2, np.pi / 4, -np.pi / 4] + rng = np.random.default_rng() + b = rng.choice(phases, size=len(offsets)) + offsets + return __instantiate_params(qc1, qc2, b) + for i in range(n_checks): - circ1_inst, circ2_inst, runtime = instantiate_params_phases(circ1, circ2) + circ1_inst, circ2_inst, runtime = __instantiate_params_phases(circ1, circ2) timeout = __adjust_timeout(timeout, runtime) res = check_instantiated(circ1_inst, circ2_inst, configuration) timeout = __adjust_timeout(timeout, res) if timeout < 0: - write_stats(i + 2, res) + __write_stats(i + 2, res) res.equivalence = EquivalenceCriterion.no_information return res - update_stats(res) + __update_stats(res) if res.equivalence == EquivalenceCriterion.not_equivalent: - write_stats(i + 2, res) + __write_stats(i + 2, res) return res res = check_instantiated_random(circ1, circ2, parameters, configuration) @@ -220,6 +216,6 @@ def instantiate_params_phases( if timeout < 0: res.equivalence = EquivalenceCriterion.no_information - update_stats(res) - write_stats(n_checks + 2, res) + __update_stats(res) + __write_stats(n_checks + 2, res) return res diff --git a/src/mqt/qcec/pyqcec.pyi b/src/mqt/qcec/pyqcec.pyi index d99848f7..4faa5942 100644 --- a/src/mqt/qcec/pyqcec.pyi +++ b/src/mqt/qcec/pyqcec.pyi @@ -1,6 +1,6 @@ from typing import Any, ClassVar, overload -from qiskit import QuantumCircuit +from mqt.core import QuantumComputation from .types import ApplicationSchemeName, EquivalenceCriterionName, StateTypeName @@ -98,9 +98,7 @@ class EquivalenceCheckingManager: def considered_equivalent(self) -> bool: ... def json(self) -> dict[str, Any]: ... - def __init__( - self, circ1: QuantumCircuit | str, circ2: QuantumCircuit | str, config: Configuration = ... - ) -> None: ... + def __init__(self, circ1: QuantumComputation, circ2: QuantumComputation, config: Configuration = ...) -> None: ... def backpropagate_output_permutation(self) -> None: ... def disable_all_checkers(self) -> None: ... def equivalence(self) -> EquivalenceCriterion: ... diff --git a/src/mqt/qcec/verify.py b/src/mqt/qcec/verify.py index 85af329a..9ab03749 100644 --- a/src/mqt/qcec/verify.py +++ b/src/mqt/qcec/verify.py @@ -2,22 +2,30 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union if TYPE_CHECKING: + from os import PathLike + from qiskit import QuantumCircuit from typing_extensions import Unpack + from mqt.core import QuantumComputation + from .configuration import ConfigurationOptions + CircuitInputType = Union[QuantumComputation, str, PathLike[str], QuantumCircuit] + +from mqt.core.io import load + from . import Configuration, EquivalenceCheckingManager from .configuration import augment_config_from_kwargs -from .parameterized import __is_parameterized, check_parameterized +from .parameterized import check_parameterized def verify( - circ1: QuantumCircuit | str, - circ2: QuantumCircuit | str, + circ1: CircuitInputType, + circ2: CircuitInputType, configuration: Configuration | None = None, **kwargs: Unpack[ConfigurationOptions], ) -> EquivalenceCheckingManager.Results: @@ -49,11 +57,15 @@ def verify( # prepare the configuration augment_config_from_kwargs(configuration, kwargs) - if __is_parameterized(circ1) or __is_parameterized(circ2): - return check_parameterized(circ1, circ2, configuration) + # load the circuits + qc1 = load(circ1) + qc2 = load(circ2) + + if not qc1.is_variable_free() or not qc2.is_variable_free(): + return check_parameterized(qc1, qc2, configuration) # create the equivalence checker from configuration - ecm = EquivalenceCheckingManager(circ1, circ2, configuration) + ecm = EquivalenceCheckingManager(qc1, qc2, configuration) # execute the check ecm.run() diff --git a/src/mqt/qcec/verify_compilation_flow.py b/src/mqt/qcec/verify_compilation_flow.py index 58f8854b..03e94d29 100644 --- a/src/mqt/qcec/verify_compilation_flow.py +++ b/src/mqt/qcec/verify_compilation_flow.py @@ -10,6 +10,7 @@ from typing_extensions import Unpack from .configuration import ConfigurationOptions + from .verify import CircuitInputType if TYPE_CHECKING or sys.version_info < (3, 10, 0): import importlib_resources as resources @@ -46,8 +47,8 @@ def __check_if_circuit_contains_measurements(circuit: QuantumCircuit) -> None: def verify_compilation( - original_circuit: QuantumCircuit | str, - compiled_circuit: QuantumCircuit | str, + original_circuit: CircuitInputType, + compiled_circuit: CircuitInputType, optimization_level: int = 1, ancilla_mode: AncillaMode = AncillaMode.NO_ANCILLA, configuration: Configuration | None = None, diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 3b616099..3f24c611 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -1,3 +1,22 @@ +if(APPLE) + set(BASEPOINT @loader_path) +else() + set(BASEPOINT $ORIGIN) +endif() +list(APPEND CMAKE_INSTALL_RPATH ${BASEPOINT} + ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR}) +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +list( + APPEND + CMAKE_INSTALL_RPATH + ${BASEPOINT}/../core/${CMAKE_INSTALL_LIBDIR} + ${BASEPOINT}/../core/lib + ${BASEPOINT}/../core/lib64 + ${BASEPOINT}/../../core/${CMAKE_INSTALL_LIBDIR} + ${BASEPOINT}/../../core/lib + ${BASEPOINT}/../../core/lib64) + pybind11_add_module( pyqcec # Prefer thin LTO if available @@ -6,8 +25,11 @@ pybind11_add_module( OPT_SIZE # Source code goes here bindings.cpp) -target_link_libraries(pyqcec PRIVATE MQT::QCEC MQT::CorePython pybind11_json +target_link_libraries(pyqcec PRIVATE MQT::QCEC pybind11_json MQT::ProjectOptions MQT::ProjectWarnings) # Install directive for scikit-build-core -install(TARGETS pyqcec LIBRARY DESTINATION .) +install( + TARGETS pyqcec + DESTINATION . + COMPONENT mqt-qcec_PythonModule) diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 453ece68..238614e3 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -6,8 +6,6 @@ #include "EquivalenceCheckingManager.hpp" #include "pybind11/pybind11.h" #include "pybind11/stl.h" -#include "pybind11_json/pybind11_json.hpp" -#include "python/qiskit/QuantumCircuit.hpp" #include #include @@ -16,50 +14,6 @@ namespace py = pybind11; using namespace pybind11::literals; namespace ec { -namespace { -qc::QuantumComputation importCircuit(const py::object& circ) { - const py::object quantumCircuit = - py::module::import("qiskit").attr("QuantumCircuit"); - const py::object pyQasmQobjExperiment = - py::module::import("qiskit.qobj").attr("QasmQobjExperiment"); - - auto qc = qc::QuantumComputation(); - - if (py::isinstance(circ)) { - const auto file = circ.cast(); - qc.import(file); - } else if (py::isinstance(circ, quantumCircuit)) { - qc::qiskit::QuantumCircuit::import(qc, circ); - } else { - throw std::runtime_error( - "PyObject is neither py::str, QuantumCircuit, nor QasmQobjExperiment"); - } - - return qc; -} - -std::unique_ptr -createManagerFromConfiguration(const py::object& circ1, const py::object& circ2, - const Configuration& configuration = {}) { - qc::QuantumComputation qc1; - try { - qc1 = importCircuit(circ1); - } catch (const std::exception& ex) { - throw std::runtime_error("Could not import first circuit: " + - std::string(ex.what())); - } - - qc::QuantumComputation qc2; - try { - qc2 = importCircuit(circ2); - } catch (const std::exception& ex) { - throw std::runtime_error("Could not import second circuit: " + - std::string(ex.what())); - } - - return std::make_unique(qc1, qc2, configuration); -} -} // namespace PYBIND11_MODULE(pyqcec, m) { m.doc() = "Python interface for the MQT QCEC quantum circuit equivalence " @@ -179,8 +133,9 @@ PYBIND11_MODULE(pyqcec, m) { "tool"); // Constructors - ecm.def(py::init(&createManagerFromConfiguration), "circ1"_a, "circ2"_a, - "config"_a = Configuration(), + ecm.def(py::init(), + "circ1"_a, "circ2"_a, "config"_a = Configuration(), "Create an equivalence checking manager for two circuits and " "configure it with a :class:`Configuration` object.") .def("get_configuration", &EquivalenceCheckingManager::getConfiguration) diff --git a/test/python/constraints.txt b/test/python/constraints.txt index 1e633f9e..2ebbd967 100644 --- a/test/python/constraints.txt +++ b/test/python/constraints.txt @@ -2,6 +2,7 @@ scikit-build-core==0.6.1 setuptools-scm==7.0.0 pybind11==2.11.0 pytest==7.0.0 +mqt.core==2.2.2 importlib_resources==5.0.0 typing_extensions==4.2.0 qiskit==0.45.0 diff --git a/test/python/test_construction.py b/test/python/test_construction.py index a3293b61..c5ace332 100644 --- a/test/python/test_construction.py +++ b/test/python/test_construction.py @@ -2,36 +2,26 @@ from __future__ import annotations -from pathlib import Path - import pytest -from qiskit import QuantumCircuit, qasm2 from mqt import qcec +from mqt.core import QuantumComputation @pytest.fixture() -def example_circuit() -> QuantumCircuit: +def example_circuit() -> QuantumComputation: """Fixture for a simple circuit.""" - qc = QuantumCircuit(1) + qc = QuantumComputation(1) qc.h(0) return qc -def test_default_constructor_with_qiskit(example_circuit: QuantumCircuit) -> None: +def test_default_constructor_with_quantum_computation(example_circuit: QuantumComputation) -> None: """Test constructing an instance from two qiskit circuits with all default arguments.""" qcec.EquivalenceCheckingManager(circ1=example_circuit, circ2=example_circuit) -def test_constructor_with_configuration(example_circuit: QuantumCircuit) -> None: +def test_constructor_with_configuration(example_circuit: QuantumComputation) -> None: """Test constructing an instance from circuits and a configuration object.""" config = qcec.Configuration() qcec.EquivalenceCheckingManager(circ1=example_circuit, circ2=example_circuit, config=config) - - -def test_default_constructor_with_file(example_circuit: QuantumCircuit) -> None: - """Test constructing an instance from two circuit files with all default arguments.""" - filename = "test.qasm" - qasm2.dump(example_circuit, Path(filename)) - qcec.EquivalenceCheckingManager(circ1=filename, circ2=filename) - Path(filename).unlink() From b555c9f675cd755d942c08d0d43fdf41b7f7f644 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Thu, 18 Jan 2024 12:45:19 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=97=EF=B8=8F=20temporarily=20switch?= =?UTF-8?q?=20to=20the=20development=20version=20of=20`mqt-core`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .pre-commit-config.yaml | 2 +- cmake/ExternalDependencies.cmake | 4 ++-- extern/mqt-core | 2 +- noxfile.py | 2 +- pyproject.toml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c9937f2..e5a7a906 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -100,7 +100,7 @@ repos: - importlib_resources - numpy - pytest - - mqt-core~=2.2.2 + - mqt-core @ git+https://github.com/cda-tum/mqt-core.git@installation-improvements # Check for spelling - repo: https://github.com/codespell-project/codespell diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 7be303d1..98b892a1 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -39,7 +39,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) FetchContent_Declare( mqt-core GIT_REPOSITORY https://github.com/cda-tum/mqt-core.git - GIT_TAG v${MQT_CORE_VERSION} + GIT_TAG installation-improvements FIND_PACKAGE_ARGS ${MQT_CORE_VERSION}) list(APPEND FETCH_PACKAGES mqt-core) else() @@ -48,7 +48,7 @@ else() FetchContent_Declare( mqt-core GIT_REPOSITORY https://github.com/cda-tum/mqt-core.git - GIT_TAG v${MQT_CORE_VERSION}) + GIT_TAG installation-improvements) list(APPEND FETCH_PACKAGES mqt-core) endif() endif() diff --git a/extern/mqt-core b/extern/mqt-core index f2533950..483124eb 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit f2533950fccdab892dd3b193d3b10aebaf952dd6 +Subproject commit 483124ebd40babcccdd7d7b19bfb763b91ce66ec diff --git a/noxfile.py b/noxfile.py index 484b17be..58e61cde 100644 --- a/noxfile.py +++ b/noxfile.py @@ -17,7 +17,7 @@ PYTHON_ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] BUILD_REQUIREMENTS = [ - "mqt.core~=2.2.2", + "mqt.core @ git+https://github.com/cda-tum/mqt-core.git@installation-improvements", "scikit-build-core[pyproject]>=0.6.1", "setuptools_scm>=7", "pybind11>=2.11", diff --git a/pyproject.toml b/pyproject.toml index 92be1db4..0201bda1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "scikit-build-core>=0.6.1", "setuptools-scm>=7", "pybind11>=2.11", - "mqt.core~=2.2.2", + "mqt.core @ git+https://github.com/cda-tum/mqt-core.git@installation-improvements", ] build-backend = "scikit_build_core.build" @@ -40,7 +40,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "mqt.core[qiskit]~=2.2.2", + "mqt.core[qiskit] @ git+https://github.com/cda-tum/mqt-core.git@installation-improvements", "importlib_resources>=5.0; python_version < '3.10'", "typing_extensions>=4.2", ]