From e88d4db89bccb5933f7bc53d2b542c3e64131ae3 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 27 Jan 2024 12:59:45 +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 | 15 ++++- docs/source/index.rst | 1 - docs/source/simulators/CircuitSimulator.ipynb | 11 +++- docs/source/simulators/UnitarySimulator.ipynb | 28 ++++++--- noxfile.py | 22 +++++++ pyproject.toml | 25 +++++--- src/mqt/ddsim/__init__.py | 17 ++++++ src/mqt/ddsim/deterministicnoisesimulator.py | 4 +- src/mqt/ddsim/hybridqasmsimulator.py | 5 +- src/mqt/ddsim/pathqasmsimulator.py | 25 ++++---- src/mqt/ddsim/primitives/estimator.py | 11 ++-- src/mqt/ddsim/qasmsimulator.py | 5 +- src/mqt/ddsim/stochasticnoisesimulator.py | 4 +- src/mqt/ddsim/unitarysimulator.py | 5 +- src/python/CMakeLists.txt | 21 ++++++- src/python/bindings.cpp | 57 ++++--------------- .../test_hybrid_standalone_simulator.py | 11 ++-- .../simulator/test_standalone_simulator.py | 41 +++++++------ .../test_path_sim_standalone_simulator.py | 21 +++---- .../test_standalone_unitary_simulator.py | 4 +- 21 files changed, 203 insertions(+), 131 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6756b8a2..f9d5de73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,6 +101,7 @@ repos: additional_dependencies: - numpy - pytest + - mqt.core @ git+https://github.com/cda-tum/mqt-core@shared-libs # Check for spelling - repo: https://github.com/crate-ci/typos diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 4a594bc4..5364312a 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -4,6 +4,19 @@ include(FetchContent) set(FETCH_PACKAGES "") if(BUILD_MQT_DDSIM_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( @@ -22,7 +35,7 @@ endif() # cmake-format: off set(MQT_CORE_VERSION 2.6.1 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "9be91674dddcf1f73143d9509e8e4ad806f47592" +set(MQT_CORE_REV "shared-libs" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "cda-tum" CACHE STRING "MQT Core repository owner (change when using a fork)") diff --git a/docs/source/index.rst b/docs/source/index.rst index 646f2546..5b58b804 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -24,7 +24,6 @@ We appreciate any feedback and contributions to the project. If you want to cont Installation Quickstart Simulators - Usage Primitives FAQ Publications diff --git a/docs/source/simulators/CircuitSimulator.ipynb b/docs/source/simulators/CircuitSimulator.ipynb index 2c11f064..a84636af 100644 --- a/docs/source/simulators/CircuitSimulator.ipynb +++ b/docs/source/simulators/CircuitSimulator.ipynb @@ -79,10 +79,12 @@ "metadata": {}, "outputs": [], "source": [ + "from mqt.core import load\n", "from mqt.ddsim import CircuitSimulator\n", "\n", "# create a CircuitSimulator object\n", - "sim = CircuitSimulator(filename)\n", + "qc = load(filename)\n", + "sim = CircuitSimulator(qc)\n", "\n", "# run the simulation\n", "result = sim.simulate(shots=1024)\n", @@ -178,7 +180,8 @@ "qc.measure([0, 1], [0, 1])\n", "\n", "# create a CircuitSimulator object\n", - "sim = CircuitSimulator(qc)\n", + "circ = load(qc)\n", + "sim = CircuitSimulator(circ)\n", "\n", "# run the simulation\n", "result = sim.simulate(shots=1024)\n", @@ -274,10 +277,12 @@ "metadata": {}, "outputs": [], "source": [ + "from mqt.core import load\n", "from mqt.ddsim import CircuitSimulator\n", "\n", "# create a CircuitSimulator object\n", - "sim = CircuitSimulator(filename)\n", + "circ = load(filename)\n", + "sim = CircuitSimulator(circ)\n", "\n", "# run the simulation\n", "result = sim.simulate(shots=1024)\n", diff --git a/docs/source/simulators/UnitarySimulator.ipynb b/docs/source/simulators/UnitarySimulator.ipynb index ac9f9e0b..1a7d0f57 100644 --- a/docs/source/simulators/UnitarySimulator.ipynb +++ b/docs/source/simulators/UnitarySimulator.ipynb @@ -49,10 +49,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -117,10 +119,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -181,10 +185,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -248,10 +254,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -317,10 +325,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -390,10 +400,12 @@ "source": [ "import graphviz\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import UnitarySimulator\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", @@ -502,6 +514,7 @@ "import graphviz\n", "from qiskit import QuantumCircuit\n", "\n", + "from mqt.core import load\n", "from mqt.ddsim import ConstructionMode, UnitarySimulator\n", "\n", "qc = QuantumCircuit(3)\n", @@ -511,7 +524,8 @@ "qc.cx(2, 1)\n", "\n", "# Create the simulator\n", - "sim = UnitarySimulator(qc, mode=ConstructionMode.recursive)\n", + "circ = load(qc)\n", + "sim = UnitarySimulator(circ, mode=ConstructionMode.recursive)\n", "\n", "# Construct the decision diagram representation of the unitary\n", "sim.construct()\n", diff --git a/noxfile.py b/noxfile.py index c83057e0..2b600203 100644 --- a/noxfile.py +++ b/noxfile.py @@ -70,6 +70,19 @@ def _run_tests( posargs.append("--cov-config=pyproject.toml") session.install(*BUILD_REQUIREMENTS, *install_args, env=env) + + # install mqt.core from source to avoid ABI incompatibilities + session.install( + "--no-build-isolation", + "mqt.core @ git+https://github.com/cda-tum/mqt-core@shared-libs", + "--no-binary", + "mqt.core", + *install_args, + env={ + "CMAKE_GENERATOR": "Ninja", + }, + ) + install_arg = f"-ve.[{','.join(_extras)}]" session.install("--no-build-isolation", "--reinstall-package", "mqt.ddsim", install_arg, *install_args, env=env) session.run("pytest", *run_args, *posargs, env=env) @@ -103,6 +116,15 @@ def docs(session: nox.Session) -> None: serve = args.builder == "html" and session.interactive extra_installs = ["sphinx-autobuild"] if serve else [] session.install(*BUILD_REQUIREMENTS, *extra_installs) + + # install mqt.core from source to avoid ABI incompatibilities + session.install( + "--no-build-isolation", + "mqt.core @ git+https://github.com/cda-tum/mqt-core@shared-libs", + "--no-binary", + "mqt.core", + ) + session.install("--no-build-isolation", "-ve.[docs]", "--reinstall-package", "mqt.ddsim") session.chdir("docs") diff --git a/pyproject.toml b/pyproject.toml index 3bcc0837..2600b991 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,9 @@ [build-system] requires = [ - "scikit-build-core>=0.10.1", - "setuptools-scm>=7", - "pybind11>=2.13.5", + "scikit-build-core>=0.10.1", + "setuptools-scm>=7", + "pybind11>=2.13.5", + "mqt.core @ git+https://github.com/cda-tum/mqt-core@shared-libs", ] build-backend = "scikit_build_core.build" @@ -39,7 +40,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "qiskit[qasm3-import]>=1.0.0" + "mqt.core[qiskit] @ git+https://github.com/cda-tum/mqt-core@shared-libs", ] dynamic = ["version"] @@ -51,7 +52,7 @@ tnflow = [ "quimb", "pandas", "numpy", - "kahypar", + "kahypar; python_version < '3.12'", # getting kahypar installed on Python 3.12+ is hard "networkx", ] test = ["pytest>=7.0"] @@ -294,13 +295,23 @@ manylinux-aarch64-image = "manylinux_2_28" manylinux-ppc64le-image = "manylinux_2_28" manylinux-s390x-image = "manylinux_2_28" +# The mqt-core shared libraries are provided by the mqt-core Python package. +# They should not be vendorized into the mqt-qcec wheel. This requires +# excluding the shared libraries from the repair process. + [tool.cibuildwheel.linux] environment = { DEPLOY = "ON" } +# The SOVERSION needs to be updated when the shared libraries are updated. +repair-wheel-command = """auditwheel repair -w {dest_dir} {wheel} \ +--exclude libmqt-core-ir.so.2.6 \ +--exclude libmqt-core-circuit-optimizer.so.2.6 \ +--exclude libmqt-core-dd.so.2.6""" [tool.cibuildwheel.macos] environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } +repair-wheel-command = "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} --ignore-missing-dependencies" [tool.cibuildwheel.windows] -before-build = "pip install delvewheel>=1.7.3" -repair-wheel-command = "delvewheel repair -v -w {dest_dir} {wheel} --namespace-pkg mqt" +before-build = "uv pip install delvewheel>=1.7.3" +repair-wheel-command = """delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --no-dll \"mqt-core-ir.dll;mqt-core-circuit-optimizer.dll;mqt-core-dd.dll\"""" environment = { CMAKE_ARGS = "-T ClangCL", SKBUILD_CMAKE_ARGS="--fresh" } diff --git a/src/mqt/ddsim/__init__.py b/src/mqt/ddsim/__init__.py index cb31ddc8..fda51120 100644 --- a/src/mqt/ddsim/__init__.py +++ b/src/mqt/ddsim/__init__.py @@ -2,6 +2,23 @@ from __future__ import annotations +import sys + +# 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 os + import sysconfig + from pathlib import Path + + bin_dir = Path(sysconfig.get_paths()["purelib"]) / "mqt" / "core" / "bin" + os.add_dll_directory(str(bin_dir)) + + _dll_patch() + del _dll_patch + from ._version import version as __version__ from .provider import DDSIMProvider from .pyddsim import ( diff --git a/src/mqt/ddsim/deterministicnoisesimulator.py b/src/mqt/ddsim/deterministicnoisesimulator.py index 7ef21173..83e075d3 100644 --- a/src/mqt/ddsim/deterministicnoisesimulator.py +++ b/src/mqt/ddsim/deterministicnoisesimulator.py @@ -9,6 +9,7 @@ from qiskit.result.models import ExperimentResult, ExperimentResultData from mqt import ddsim +from mqt.core import load from .header import DDSIMHeader from .qasmsimulator import QasmSimulatorBackend @@ -55,8 +56,9 @@ def _run_experiment(qc: QuantumCircuit, **options: dict[str, Any]) -> Experiment seed = cast(int, options.get("simulator_seed", -1)) shots = cast(int, options.get("shots", 1024)) + circ = load(qc) sim = ddsim.DeterministicNoiseSimulator( - circ=qc, + circ=circ, seed=seed, noise_effects=noise_effects, noise_probability=noise_probability, diff --git a/src/mqt/ddsim/hybridqasmsimulator.py b/src/mqt/ddsim/hybridqasmsimulator.py index feb60644..cd9a8f7e 100644 --- a/src/mqt/ddsim/hybridqasmsimulator.py +++ b/src/mqt/ddsim/hybridqasmsimulator.py @@ -14,6 +14,8 @@ from qiskit.transpiler import Target from qiskit.utils.multiprocessing import local_hardware_info +from mqt.core import load + from .header import DDSIMHeader from .pyddsim import HybridCircuitSimulator, HybridMode from .qasmsimulator import QasmSimulatorBackend @@ -81,7 +83,8 @@ def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResul msg = f"Simulation mode{mode} not supported by hybrid simulator. Available modes are 'amplitude' and 'dd'." raise QiskitError(msg) - sim = HybridCircuitSimulator(qc, seed=seed, mode=hybrid_mode, nthreads=nthreads) + circuit = load(qc) + sim = HybridCircuitSimulator(circuit, seed=seed, mode=hybrid_mode, nthreads=nthreads) shots = options.get("shots", 1024) if self._SHOW_STATE_VECTOR and shots > 0: diff --git a/src/mqt/ddsim/pathqasmsimulator.py b/src/mqt/ddsim/pathqasmsimulator.py index 2669d0cc..f165ff62 100644 --- a/src/mqt/ddsim/pathqasmsimulator.py +++ b/src/mqt/ddsim/pathqasmsimulator.py @@ -7,15 +7,19 @@ from typing import TYPE_CHECKING, Any, cast if TYPE_CHECKING: + from qiskit import QuantumCircuit from quimb.tensor import Tensor, TensorNetwork + from mqt.core.ir import QuantumComputation + import locale -from qiskit import QuantumCircuit from qiskit.providers import Options from qiskit.result.models import ExperimentResult, ExperimentResultData from qiskit.transpiler import Target +from mqt.core import load + from .header import DDSIMHeader from .pyddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode from .qasmsimulator import QasmSimulatorBackend @@ -48,7 +52,7 @@ def read_tensor_network_file(filename: str) -> list[Tensor]: return tensors -def create_tensor_network(qc: QuantumCircuit) -> TensorNetwork: +def create_tensor_network(qc: QuantumComputation) -> TensorNetwork: """Create a tensor network from a quantum circuit. Args: @@ -62,12 +66,8 @@ def create_tensor_network(qc: QuantumCircuit) -> TensorNetwork: from mqt.ddsim import dump_tensor_network - if isinstance(qc, QuantumCircuit): - filename = qc.name + "_" + str(qc.num_qubits) + ".tensor" - nqubits = qc.num_qubits - else: - filename = "tensor.tensor" - nqubits = qc.header.n_qubits + filename = qc.name + "_" + str(qc.num_qubits) + ".tensor" + nqubits = qc.num_qubits dump_tensor_network(qc, filename) tensors = read_tensor_network_file(filename) @@ -96,7 +96,7 @@ def create_tensor_network(qc: QuantumCircuit) -> TensorNetwork: def get_simulation_path( - qc: QuantumCircuit, + qc: QuantumComputation, max_time: int = 60, max_repeats: int = 1024, parallel_runs: int = 1, @@ -132,7 +132,7 @@ def get_simulation_path( path = cast(list[tuple[int, int]], linear_to_ssa(info.path)) if dump_path: - filename = qc.name + "_" + str(qc.num_qubits) + ".path" if isinstance(qc, QuantumCircuit) else "simulation.path" + filename = qc.name + "_" + str(qc.num_qubits) + ".path" with pathlib.Path(filename).open("w", encoding=locale.getpreferredencoding(False)) as file: file.write(str(path)) @@ -219,7 +219,8 @@ def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResul if seed is not None: pathsim_configuration.seed = seed - sim = PathCircuitSimulator(qc, config=pathsim_configuration) + circuit = load(qc) + sim = PathCircuitSimulator(circuit, config=pathsim_configuration) # determine the contraction path using cotengra in case this is requested if pathsim_configuration.mode == PathSimulatorMode.cotengra: @@ -228,7 +229,7 @@ def _run_experiment(self, qc: QuantumCircuit, **options: Any) -> ExperimentResul dump_path = options.get("cotengra_dump_path", False) plot_ring = options.get("cotengra_plot_ring", False) path = get_simulation_path( - qc, + circuit, max_time=max_time, max_repeats=max_repeats, dump_path=dump_path, diff --git a/src/mqt/ddsim/primitives/estimator.py b/src/mqt/ddsim/primitives/estimator.py index 58f85643..9299eea8 100644 --- a/src/mqt/ddsim/primitives/estimator.py +++ b/src/mqt/ddsim/primitives/estimator.py @@ -11,6 +11,7 @@ from qiskit.primitives import EstimatorResult from qiskit.quantum_info import Pauli, PauliList +from mqt.core import load from mqt.ddsim.pyddsim import CircuitSimulator from mqt.ddsim.qasmsimulator import QasmSimulatorBackend @@ -55,10 +56,7 @@ def preprocessed_circuits( ) -> tuple[list[QuantumCircuit], list[list[QuantumCircuit]]]: """Generate quantum circuits for states and observables produced by preprocessing. - Returns: - Tuple: A tuple containing two entries: - - List: Quantum circuits list entered in run() method. - - List: Quantum circuit representations of the observables. + Returns: A tuple containing two entries: the quantum circuits for the states and observables. """ self._preprocessed_circuits = self._preprocessing() return self._preprocessed_circuits @@ -185,15 +183,16 @@ def _run_experiment( approximation_strategy = str(options.get("approximation_strategy", "fidelity")) seed = cast(int, options.get("seed_simulator", -1)) + qc = load(circ) sim = CircuitSimulator( - circ, + qc, approximation_step_fidelity=approximation_step_fidelity, approximation_steps=approximation_steps, approximation_strategy=approximation_strategy, seed=seed, ) - return [sim.expectation_value(observable=obs) for obs in obs_circ_list] + return [sim.expectation_value(observable=load(obs)) for obs in obs_circ_list] @staticmethod def _postprocessing(result_list: list[float], accum: list[int], metadata: list[dict[str, Any]]) -> EstimatorResult: diff --git a/src/mqt/ddsim/qasmsimulator.py b/src/mqt/ddsim/qasmsimulator.py index 78f268fa..c333c6f5 100644 --- a/src/mqt/ddsim/qasmsimulator.py +++ b/src/mqt/ddsim/qasmsimulator.py @@ -12,6 +12,8 @@ from qiskit.result.models import ExperimentResult, ExperimentResultData from qiskit.transpiler import Target +from mqt.core import load + from . import __version__ from .header import DDSIMHeader from .job import DDSIMJob @@ -178,8 +180,9 @@ def _run_experiment(self, qc: QuantumCircuit, **options: dict[str, Any]) -> Expe seed = cast(int, options.get("seed_simulator", -1)) shots = cast(int, options.get("shots", 1024)) + circuit = load(qc) sim = CircuitSimulator( - qc, + circuit, approximation_step_fidelity=approximation_step_fidelity, approximation_steps=approximation_steps, approximation_strategy=approximation_strategy, diff --git a/src/mqt/ddsim/stochasticnoisesimulator.py b/src/mqt/ddsim/stochasticnoisesimulator.py index 751685d7..4e3c4f59 100644 --- a/src/mqt/ddsim/stochasticnoisesimulator.py +++ b/src/mqt/ddsim/stochasticnoisesimulator.py @@ -9,6 +9,7 @@ from qiskit.result.models import ExperimentResult, ExperimentResultData from mqt import ddsim +from mqt.core import load from .header import DDSIMHeader from .qasmsimulator import QasmSimulatorBackend @@ -56,8 +57,9 @@ def _run_experiment(qc: QuantumCircuit, **options: dict[str, Any]) -> Experiment seed = cast(int, options.get("seed_simulator", -1)) shots = cast(int, options.get("shots", 1024)) + circ = load(qc) sim = ddsim.StochasticNoiseSimulator( - circ=qc, + circ=circ, approximation_step_fidelity=approximation_step_fidelity, approximation_steps=approximation_steps, approximation_strategy=approximation_strategy, diff --git a/src/mqt/ddsim/unitarysimulator.py b/src/mqt/ddsim/unitarysimulator.py index 93c7d63d..d4f5ae8f 100644 --- a/src/mqt/ddsim/unitarysimulator.py +++ b/src/mqt/ddsim/unitarysimulator.py @@ -12,6 +12,8 @@ from qiskit.result.models import ExperimentResult, ExperimentResultData from qiskit.transpiler import Target +from mqt.core import load + from .header import DDSIMHeader from .pyddsim import ConstructionMode, UnitarySimulator, get_matrix from .qasmsimulator import QasmSimulatorBackend @@ -70,7 +72,8 @@ def _run_experiment(cls, qc: QuantumCircuit, **options: Any) -> ExperimentResult ) raise QiskitError(msg) - sim = UnitarySimulator(qc, seed=seed, mode=construction_mode) + circuit = load(qc) + sim = UnitarySimulator(circuit, seed=seed, mode=construction_mode) sim.construct() # Extract resulting matrix from final DD and write data unitary: npt.NDArray[np.complex128] = np.zeros((2**qc.num_qubits, 2**qc.num_qubits), dtype=np.complex128) diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 0e7cea7d..790e3ba5 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() +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +list( + APPEND + CMAKE_INSTALL_RPATH + ${BASEPOINT} + ${BASEPOINT}/${CMAKE_INSTALL_LIBDIR} + ${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( pyddsim # Prefer thin LTO if available @@ -7,7 +26,7 @@ pybind11_add_module( # Source code goes here bindings.cpp) target_link_libraries(pyddsim PRIVATE MQT::DDSim MQT::ProjectOptions MQT::ProjectWarnings - MQT::CorePython pybind11_json) + pybind11_json) # Install directive for scikit-build-core install( diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 250535a9..2f445c34 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -11,7 +11,6 @@ #include "StochasticNoiseSimulator.hpp" #include "UnitarySimulator.hpp" #include "dd/FunctionalityConstruction.hpp" -#include "python/qiskit/QuantumCircuit.hpp" #include #include @@ -21,31 +20,13 @@ namespace py = pybind11; using namespace pybind11::literals; -static qc::QuantumComputation importCircuit(const py::object& circ) { - const py::object quantumCircuit = - py::module::import("qiskit").attr("QuantumCircuit"); - - 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 nor QuantumCircuit"); - } - - return qc; -} - template std::unique_ptr -constructSimulator(const py::object& circ, const double stepFidelity, - const unsigned int stepNumber, +constructSimulator(const qc::QuantumComputation& circ, + const double stepFidelity, const unsigned int stepNumber, const std::string& approximationStrategy, const std::int64_t seed, Args&&... args) { - auto qc = std::make_unique(importCircuit(circ)); + auto qc = std::make_unique(circ); const auto approx = ApproximationInfo{stepFidelity, stepNumber, ApproximationInfo::fromString(approximationStrategy)}; @@ -63,8 +44,9 @@ constructSimulator(const py::object& circ, const double stepFidelity, } template -std::unique_ptr constructSimulatorWithoutSeed(const py::object& circ, - Args&&... args) { +std::unique_ptr +constructSimulatorWithoutSeed(const qc::QuantumComputation& circ, + Args&&... args) { return constructSimulator(circ, 1., 1, "fidelity", -1, std::forward(args)...); } @@ -121,28 +103,10 @@ void getNumPyMatrix(UnitarySimulator& sim, sim.getNumberOfQubits()); } -void dumpTensorNetwork(const py::object& circ, const std::string& filename) { - const py::object quantumCircuit = - py::module::import("qiskit").attr("QuantumCircuit"); - - std::unique_ptr qc = - std::make_unique(); - - if (py::isinstance(circ)) { - auto&& file1 = circ.cast(); - qc->import(file1); - } else if (py::isinstance(circ, quantumCircuit)) { - qc::qiskit::QuantumCircuit::import(*qc, circ); - } else { - throw std::runtime_error("PyObject is neither py::str nor QuantumCircuit"); - } +void dumpTensorNetwork(qc::QuantumComputation& circ, + const std::string& filename) { std::ofstream ofs(filename); - dd::dumpTensorNetwork(ofs, *qc); -} - -dd::fp expectationValue(CircuitSimulator<>& sim, const py::object& observable) { - const auto observableCircuit = importCircuit(observable); - return sim.expectationValue(observableCircuit); + dd::dumpTensorNetwork(ofs, circ); } template @@ -204,7 +168,8 @@ PYBIND11_MODULE(pyddsim, m, py::mod_gil_not_used()) { .def(py::init<>(&constructSimulator>), "circ"_a, "approximation_step_fidelity"_a = 1., "approximation_steps"_a = 1, "approximation_strategy"_a = "fidelity", "seed"_a = -1) - .def("expectation_value", &expectationValue, "observable"_a); + .def("expectation_value", &CircuitSimulator<>::expectationValue, + "observable"_a); // Stoch simulator auto stochasticNoiseSimulator = diff --git a/test/python/hybridsimulator/test_hybrid_standalone_simulator.py b/test/python/hybridsimulator/test_hybrid_standalone_simulator.py index 224cef9b..3b55ad84 100644 --- a/test/python/hybridsimulator/test_hybrid_standalone_simulator.py +++ b/test/python/hybridsimulator/test_hybrid_standalone_simulator.py @@ -2,19 +2,18 @@ import unittest -from qiskit import QuantumCircuit, QuantumRegister - +from mqt.core.ir import QuantumComputation from mqt.ddsim import HybridCircuitSimulator, HybridMode class MQTStandaloneHybridSimulatorTest(unittest.TestCase): def setUp(self) -> None: - q = QuantumRegister(4) - circ = QuantumCircuit(q) - circ.h(q) + circ = QuantumComputation(4) + for i in range(4): + circ.h(i) circ.cz(3, 1) circ.cz(2, 0) - circ.measure_all(inplace=True) + circ.measure_all() self.circuit = circ self.non_zeros_in_matrix = 16 diff --git a/test/python/simulator/test_standalone_simulator.py b/test/python/simulator/test_standalone_simulator.py index 45009d43..14a31508 100644 --- a/test/python/simulator/test_standalone_simulator.py +++ b/test/python/simulator/test_standalone_simulator.py @@ -3,8 +3,8 @@ import pathlib import unittest -from qiskit import QuantumCircuit - +from mqt.core import load +from mqt.core.ir import QuantumComputation from mqt.ddsim import CircuitSimulator @@ -14,7 +14,8 @@ def setUp(self) -> None: def test_truly_standalone(self) -> None: filename = str(pathlib.Path(__file__).with_name("ghz_03.qasm").absolute()) - sim = CircuitSimulator(filename) + circ = load(filename) + sim = CircuitSimulator(circ) result = sim.simulate(1000) print(result) assert len(result.keys()) == self.nonzero_states_ghz @@ -22,7 +23,7 @@ def test_truly_standalone(self) -> None: assert "111" in result def test_standalone(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -34,7 +35,7 @@ def test_standalone(self) -> None: assert "111" in result def test_standalone_with_seed(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -49,11 +50,11 @@ def test_standalone_simple_approximation(self) -> None: import numpy as np # creates a state with <2% probability of measuring |1x> - circ = QuantumCircuit(2) + circ = QuantumComputation(2) circ.h(0) circ.cry(np.pi / 8, 0, 1) - circ.id(0) - circ.id(0) + circ.i(0) + circ.i(0) # create a simulator that approximates once and by at most 2% sim = CircuitSimulator(circ, approximation_step_fidelity=0.98, approximation_steps=1) @@ -66,17 +67,15 @@ def test_standalone_simple_approximation(self) -> None: @staticmethod def test_native_two_qubit_gates() -> None: - from qiskit.circuit.library import XXMinusYYGate, XXPlusYYGate - - qc = QuantumCircuit(2) + qc = QuantumComputation(2) qc.dcx(0, 1) qc.ecr(0, 1) qc.rxx(0.5, 0, 1) qc.rzz(0.5, 0, 1) qc.ryy(0.5, 0, 1) qc.rzx(0.5, 0, 1) - qc.append(XXMinusYYGate(0.5, 0.25), [0, 1]) - qc.append(XXPlusYYGate(0.5, 0.25), [0, 1]) + qc.xx_minus_yy(0.5, 0.25, 0, 1) + qc.xx_plus_yy(0.5, 0.25, 0, 1) print(qc) print(qc.global_phase) sim = CircuitSimulator(qc) @@ -89,16 +88,16 @@ def test_expectation_value_local_operators() -> None: max_qubits = 3 for qubits in range(1, max_qubits + 1): - qc = QuantumCircuit(qubits) + qc = QuantumComputation(qubits) sim = CircuitSimulator(qc) for i in range(qubits): - x_observable = QuantumCircuit(qubits) + x_observable = QuantumComputation(qubits) x_observable.x(i) assert sim.expectation_value(x_observable) == 0 - z_observable = QuantumCircuit(qubits) + z_observable = QuantumComputation(qubits) z_observable.z(i) assert sim.expectation_value(z_observable) == 1 - h_observable = QuantumCircuit(qubits) + h_observable = QuantumComputation(qubits) h_observable.h(i) assert np.allclose(sim.expectation_value(h_observable), 1 / np.sqrt(2)) @@ -108,11 +107,11 @@ def test_expectation_value_global_operators() -> None: max_qubits = 3 for qubits in range(1, max_qubits + 1): - qc = QuantumCircuit(qubits) + qc = QuantumComputation(qubits) sim = CircuitSimulator(qc) - x_observable = QuantumCircuit(qubits) - z_observable = QuantumCircuit(qubits) - h_observable = QuantumCircuit(qubits) + x_observable = QuantumComputation(qubits) + z_observable = QuantumComputation(qubits) + h_observable = QuantumComputation(qubits) for i in range(qubits): x_observable.x(i) z_observable.z(i) diff --git a/test/python/taskbasedsimulator/test_path_sim_standalone_simulator.py b/test/python/taskbasedsimulator/test_path_sim_standalone_simulator.py index 9603963d..c498a527 100644 --- a/test/python/taskbasedsimulator/test_path_sim_standalone_simulator.py +++ b/test/python/taskbasedsimulator/test_path_sim_standalone_simulator.py @@ -2,13 +2,8 @@ import unittest -from qiskit import QuantumCircuit - -from mqt.ddsim import ( - PathCircuitSimulator, - PathSimulatorConfiguration, - PathSimulatorMode, -) +from mqt.core.ir import QuantumComputation +from mqt.ddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode class MQTStandaloneSimulatorTests(unittest.TestCase): @@ -16,7 +11,7 @@ def setUp(self) -> None: self.nonzero_states_ghz = 2 def test_standalone(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -28,7 +23,7 @@ def test_standalone(self) -> None: assert "111" in result def test_standalone_with_config(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -40,7 +35,7 @@ def test_standalone_with_config(self) -> None: assert "111" in result def test_standalone_with_seed(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -52,7 +47,7 @@ def test_standalone_with_seed(self) -> None: assert "111" in result def test_standalone_individual_objects(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -64,7 +59,7 @@ def test_standalone_individual_objects(self) -> None: assert "111" in result def test_standalone_pairwise_only(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) @@ -81,7 +76,7 @@ def test_standalone_pairwise_only(self) -> None: assert "111" in result def test_standalone_gatecost_only(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) diff --git a/test/python/unitarysimulator/test_standalone_unitary_simulator.py b/test/python/unitarysimulator/test_standalone_unitary_simulator.py index 4cb8f21e..712ef305 100644 --- a/test/python/unitarysimulator/test_standalone_unitary_simulator.py +++ b/test/python/unitarysimulator/test_standalone_unitary_simulator.py @@ -4,14 +4,14 @@ import numpy as np import numpy.typing as npt -from qiskit import QuantumCircuit +from mqt.core.ir import QuantumComputation from mqt.ddsim import ConstructionMode, UnitarySimulator, get_matrix class MQTStandaloneUnitarySimulatorTests(unittest.TestCase): def setUp(self) -> None: - circ = QuantumCircuit(3) + circ = QuantumComputation(3) circ.h(0) circ.cx(0, 1) circ.cx(0, 2) From bb7ffd3058d42c7d6470a33f60b28150214966dd Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 18 Aug 2024 22:56:51 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20a=20comment=20just=20to=20tr?= =?UTF-8?q?igger=20CD=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 80a51e88..2a06b55d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,7 +5,7 @@ on: workflow_dispatch: pull_request: paths: - - .github/workflows/cd.yml + - .github/workflows/cd.yml # This is to make sure changes to the CD workflow are tested before deployment. concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}