Skip to content

Commit

Permalink
✨ switch to mqt-core Python package
Browse files Browse the repository at this point in the history
Signed-off-by: burgholzer <burgholzer@me.com>
  • Loading branch information
burgholzer committed Aug 18, 2024
1 parent 769dda3 commit bdf73ed
Show file tree
Hide file tree
Showing 18 changed files with 168 additions and 113 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,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
Expand Down
15 changes: 14 additions & 1 deletion cmake/ExternalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)")
Expand Down
22 changes: 22 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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")

Expand Down
23 changes: 19 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
[build-system]
requires = ["scikit-build-core>=0.10.1", "setuptools-scm>=7", "pybind11>=2.13"]
requires = [
"scikit-build-core>=0.10.1",
"setuptools-scm>=7",
"pybind11>=2.13",
"mqt.core @ git+https://github.com/cda-tum/mqt-core@shared-libs",
]
build-backend = "scikit_build_core.build"

[project]
Expand Down Expand Up @@ -35,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"]

Expand Down Expand Up @@ -280,13 +285,23 @@ test-skip = ["cp38-macosx_arm64", "cp313*"] # skip testing on Python 3.13 until
build-frontend = "build[uv]"
free-threaded-support = true

# 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" }
11 changes: 11 additions & 0 deletions src/mqt/ddsim/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
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 .provider import DDSIMProvider
from .pyddsim import (
Expand Down
4 changes: 3 additions & 1 deletion src/mqt/ddsim/deterministicnoisesimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,8 +50,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,
Expand Down
5 changes: 4 additions & 1 deletion src/mqt/ddsim/hybridqasmsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,7 +76,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:
Expand Down
25 changes: 13 additions & 12 deletions src/mqt/ddsim/pathqasmsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -40,18 +44,14 @@ 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:
import quimb.tensor as qtn
import sparse

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)
Expand Down Expand Up @@ -80,7 +80,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,
Expand All @@ -103,7 +103,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))

Expand Down Expand Up @@ -183,7 +183,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:
Expand All @@ -192,7 +193,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,
Expand Down
6 changes: 4 additions & 2 deletions src/mqt/ddsim/primitives/estimator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -185,15 +186,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:
Expand Down
5 changes: 4 additions & 1 deletion src/mqt/ddsim/qasmsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -151,8 +153,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,
Expand Down
4 changes: 3 additions & 1 deletion src/mqt/ddsim/stochasticnoisesimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,8 +56,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,
Expand Down
5 changes: 4 additions & 1 deletion src/mqt/ddsim/unitarysimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -66,7 +68,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)
Expand Down
21 changes: 20 additions & 1 deletion src/python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand Down
Loading

0 comments on commit bdf73ed

Please sign in to comment.