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 Jan 29, 2024
1 parent 225e078 commit 8209611
Show file tree
Hide file tree
Showing 15 changed files with 109 additions and 119 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ repos:
additional_dependencies:
- numpy
- pytest
- mqt.core~=2.2.2

# Check for spelling
- repo: https://github.com/codespell-project/codespell
Expand Down
18 changes: 12 additions & 6 deletions cmake/ExternalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ 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 @@ -19,12 +31,6 @@ if(BUILD_MQT_DDSIM_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")
Expand Down
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
17 changes: 7 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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]
Expand Down Expand Up @@ -34,7 +39,7 @@ classifiers = [
]
requires-python = ">=3.8"
dependencies = [
"qiskit[qasm3-import]>=0.45.0"
"mqt.core[qiskit]~=2.2.2",
]
dynamic = ["version"]

Expand Down Expand Up @@ -103,14 +108,6 @@ sdist.exclude = [
"**/plots",
"**/test",
"**/tests",
"extern/mqt-core/extern/json/include",
"extern/mqt-core/extern/googletest",
"extern/mqt-core/extern/boost/config/checks",
"extern/mqt-core/extern/boost/config/tools",
"extern/mqt-core/extern/boost/multiprecision/config",
"extern/mqt-core/extern/boost/multiprecision/example",
"extern/mqt-core/extern/boost/multiprecision/performance",
"extern/mqt-core/extern/boost/multiprecision/tools"
]

[tool.scikit-build.cmake.define]
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.io 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
29 changes: 17 additions & 12 deletions src/mqt/ddsim/pathqasmsimulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from qiskit import QuantumCircuit
from quimb.tensor import Tensor, TensorNetwork

from qiskit import QuantumCircuit
from mqt.core import QuantumComputation

from qiskit.providers import Options
from qiskit.result.models import ExperimentResult, ExperimentResultData
from qiskit.transpiler import Target

from mqt.core.io import load

from .header import DDSIMHeader
from .pyddsim import PathCircuitSimulator, PathSimulatorConfiguration, PathSimulatorMode
from .qasmsimulator import QasmSimulatorBackend
Expand All @@ -38,18 +42,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 @@ -78,7 +78,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 @@ -101,7 +101,7 @@ def get_simulation_path(
path = 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") as file:
file.write(str(path))

Expand Down Expand Up @@ -181,7 +181,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 @@ -190,7 +191,11 @@ 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, max_time=max_time, max_repeats=max_repeats, dump_path=dump_path, plot_ring=plot_ring
circuit,
max_time=max_time,
max_repeats=max_repeats,
dump_path=dump_path,
plot_ring=plot_ring,
)
sim.set_simulation_path(path, False)

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 @@ -16,6 +16,7 @@
)
from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp

from mqt.core.io import load
from mqt.ddsim.pyddsim import CircuitSimulator
from mqt.ddsim.qasmsimulator import QasmSimulatorBackend

Expand Down Expand Up @@ -235,15 +236,16 @@ def _run_experiment(
approximation_strategy = options.get("approximation_strategy", "fidelity")
seed = 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]) -> 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 @@ -15,6 +15,8 @@
from qiskit.transpiler import Target
from qiskit.utils.multiprocessing import local_hardware_info

from mqt.core.io import load

from . import __version__
from .header import DDSIMHeader
from .job import DDSIMJob
Expand Down Expand Up @@ -161,8 +163,9 @@ def _run_experiment(self, qc: QuantumCircuit, **options: dict[str, Any]) -> Expe
seed = options.get("seed_simulator", -1)
shots = 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
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.io 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
57 changes: 10 additions & 47 deletions src/python/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include "HybridSchrodingerFeynmanSimulator.hpp"
#include "PathSimulator.hpp"
#include "UnitarySimulator.hpp"
#include "python/qiskit/QuantumCircuit.hpp"

#include <memory>
#include <pybind11/numpy.h>
Expand All @@ -17,33 +16,14 @@
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<py::str>(circ)) {
const auto file = circ.cast<std::string>();
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<class Simulator, typename... Args>
std::unique_ptr<Simulator> constructSimulator(const py::object& circ,
const double stepFidelity,
const unsigned int stepNumber,
const std::string& approximationStrategy,
const std::int64_t seed,
std::unique_ptr<Simulator> 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<qc::QuantumComputation>(importCircuit(circ));
auto qc = std::make_unique<qc::QuantumComputation>(circ);
const auto approx = ApproximationInfo{stepFidelity, stepNumber, ApproximationInfo::fromString(approximationStrategy)};
if constexpr (std::is_same_v<Simulator, PathSimulator<>>) {
return std::make_unique<Simulator>(std::move(qc),
Expand All @@ -62,7 +42,7 @@ std::unique_ptr<Simulator> constructSimulator(const py::object& circ,
}

template<class Simulator, typename... Args>
std::unique_ptr<Simulator> constructSimulatorWithoutSeed(const py::object& circ, Args&&... args) {
std::unique_ptr<Simulator> constructSimulatorWithoutSeed(const qc::QuantumComputation& circ, Args&&... args) {
return constructSimulator<Simulator>(circ, 1., 1, "fidelity", -1, std::forward<Args>(args)...);
}

Expand Down Expand Up @@ -114,26 +94,9 @@ void getNumPyMatrix(UnitarySimulator<Config>& sim, py::array_t<std::complex<dd::
getNumPyMatrixRec(e, std::complex<dd::fp>{1.0, 0.0}, 0, 0, dim, dataPtr);
}

void dumpTensorNetwork(const py::object& circ, const std::string& filename) {
const py::object quantumCircuit = py::module::import("qiskit").attr("QuantumCircuit");

std::unique_ptr<qc::QuantumComputation> qc = std::make_unique<qc::QuantumComputation>();

if (py::isinstance<py::str>(circ)) {
auto&& file1 = circ.cast<std::string>();
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);
qc->dump(ofs, qc::Format::Tensor);
}

dd::fp expectationValue(CircuitSimulator<>& sim, const py::object& observable) {
const auto observableCircuit = importCircuit(observable);
return sim.expectationValue(observableCircuit);
circ.dump(ofs, qc::Format::Tensor);
}

template<class Sim>
Expand Down Expand Up @@ -171,7 +134,7 @@ PYBIND11_MODULE(pyddsim, m) {
"approximation_steps"_a = 1,
"approximation_strategy"_a = "fidelity",
"seed"_a = -1)
.def("expectation_value", &expectationValue, "observable"_a);
.def("expectation_value", &CircuitSimulator<>::expectationValue, "observable"_a);

// Hybrid Schrödinger-Feynman Simulator
py::enum_<HybridSchrodingerFeynmanSimulator<>::Mode>(m, "HybridMode")
Expand Down
1 change: 1 addition & 0 deletions test/python/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ scikit-build-core==0.6.1
setuptools-scm==7.0.0
pybind11==2.11.0
pytest==7.0.0
mqt.core==2.2.2
qiskit==0.45.0
11 changes: 5 additions & 6 deletions test/python/hybridsimulator/test_hybrid_standalone_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

import unittest

from qiskit import QuantumCircuit, QuantumRegister

from mqt import ddsim
from mqt.core import QuantumComputation


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

Expand Down
Loading

0 comments on commit 8209611

Please sign in to comment.