Skip to content

Commit ec8a6f1

Browse files
authored
✨🐍 DD Package Python bindings (#838)
## Description This PR adds long-overdue Python bindings for the MQT Core DD Package. As a starting point, it exposes the most important functionality and adds corresponding documentation as well as tests. Along the way, it fixes two small bugs in the `sample` method and the matrix DD traversal. ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are related to it. - [x] I have added appropriate tests and documentation. - [x] I have made sure that all CI jobs on GitHub pass. - [x] The pull request introduces no new warnings and follows the project's style guidelines.
2 parents 34f2bfb + 5e5c667 commit ec8a6f1

24 files changed

+2072
-29
lines changed

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585

8686
intersphinx_mapping = {
8787
"python": ("https://docs.python.org/3", None),
88+
"numpy": ("https://numpy.org/doc/stable/", None),
8889
"qiskit": ("https://docs.quantum.ibm.com/api/qiskit", None),
8990
"mqt": ("https://mqt.readthedocs.io/en/latest", None),
9091
"ddsim": ("https://mqt.readthedocs.io/projects/ddsim/en/latest", None),

docs/dd_package_evaluation.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ After running the target, you will see a `results_<your_argument>.json` file in
2525
import json
2626
from pathlib import Path
2727
28-
filepath = Path("../test/python/results_baseline.json")
28+
filepath = Path("../test/python/dd/evaluation/results_baseline.json")
2929
3030
with filepath.open(mode="r", encoding="utf-8") as f:
3131
data = json.load(f)
@@ -39,27 +39,27 @@ To compare the performance of your newly proposed changes to the existing implem
3939

4040
## Running the comparison
4141

42-
There are two ways to run the comparison. Either you can use the Python module {py:mod}`mqt.core.dd.evaluation` or you can use the CLI.
42+
There are two ways to run the comparison. Either you can use the Python module {py:mod}`mqt.core.dd_evaluation` or you can use the CLI.
4343
Both ways are shown below.
4444
In both cases, you need to have the `evaluation` extra of `mqt-core` (i.e., `mqt.core[evaluation]`) installed.
4545

4646
+++
4747

4848
### Using the Python package
4949

50-
The Python package provides a function {py:func}`~mqt.core.dd.evaluation.compare` that can be used to compare two generate result files.
50+
The Python package provides a function {py:func}`~mqt.core.dd_evaluation.compare` that can be used to compare two generate result files.
5151
The function takes two arguments, the file path of the baseline json and the file path of the json results from your changes.
5252
The function will then print a detailed comparison. An exemplary run is shown below.
5353

5454
```{code-cell} ipython3
55-
from mqt.core.dd.evaluation import compare
55+
from mqt.core.dd_evaluation import compare
5656
57-
baseline_path = "../test/python/results_baseline.json"
58-
feature_path = "../test/python/results_feature.json"
57+
baseline_path = "../test/python/dd/evaluation/results_baseline.json"
58+
feature_path = "../test/python/dd/evaluation/results_feature.json"
5959
compare(baseline_path, feature_path)
6060
```
6161

62-
Note that the method offers several parameters to customize the comparison. See {py:func}`mqt.core.dd.evaluation.compare`.
62+
Note that the method offers several parameters to customize the comparison. See {py:func}`mqt.core.dd_evaluation.compare`.
6363
An exemplary run adjusting the parameters is shown below.
6464

6565
```{code-cell} ipython3
@@ -72,13 +72,13 @@ In an even simpler fashion, the comparison can be run from the command line via
7272
Examples of such runs are shown below.
7373

7474
```{code-cell} ipython3
75-
! mqt-core-dd-compare ../test/python/results_baseline.json ../test/python/results_feature.json --factor=0.2 --only_changed
75+
! mqt-core-dd-compare ../test/python/dd/evaluation/results_baseline.json ../test/python/dd/evaluation/results_feature.json --factor=0.2 --only_changed
7676
```
7777

7878
```{code-cell} ipython3
79-
! mqt-core-dd-compare ../test/python/results_baseline.json ../test/python/results_feature.json --no_split --dd --task=functionality
79+
! mqt-core-dd-compare ../test/python/dd/evaluation/results_baseline.json ../test/python/dd/evaluation/results_feature.json --no_split --dd --task=functionality
8080
```
8181

8282
```{code-cell} ipython3
83-
! mqt-core-dd-compare ../test/python/results_baseline.json ../test/python/results_feature.json --dd --algorithm=bv --num_qubits=1024
83+
! mqt-core-dd-compare ../test/python/dd/evaluation/results_baseline.json ../test/python/dd/evaluation/results_feature.json --dd --algorithm=bv --num_qubits=1024
8484
```

include/mqt-core/dd/Edge.hpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ template <class Node> struct Edge {
308308
template <typename T = Node, isMatrixVariant<T> = true>
309309
void printMatrix(std::size_t numQubits) const;
310310

311-
private:
312311
/**
313312
* @brief Recursively traverse the DD and call a function for each non-zero
314313
* matrix entry.
@@ -331,7 +330,6 @@ template <class Node> struct Edge {
331330
///---------------------------------------------------------------------------
332331
/// \n Methods for density matrix DDs \n
333332
///---------------------------------------------------------------------------
334-
public:
335333
template <typename T = Node, isDensityMatrix<T> = true>
336334
[[maybe_unused]] static void setDensityConjugateTrue(Edge& e) {
337335
Node::setConjugateTempFlagTrue(e.p);

include/mqt-core/dd/Operations.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ template <class Config>
257257
qc::VectorDD
258258
applyClassicControlledOperation(const qc::ClassicControlledOperation& op,
259259
const qc::VectorDD& in, Package<Config>& dd,
260-
std::vector<bool>& measurements,
260+
const std::vector<bool>& measurements,
261261
const qc::Permutation& permutation = {}) {
262262
const auto& expectedValue = op.getExpectedValue();
263263
const auto& comparisonKind = op.getComparisonKind();

include/mqt-core/dd/Package.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,22 @@ template <class Config> class Package {
623623
return makeTwoQubitGateDD(mat, qc::Controls{}, target0, target1);
624624
}
625625

626+
/**
627+
* @brief Creates the DD for a two-qubit gate
628+
* @param mat Matrix representation of the gate
629+
* @param control Control qubit of the two-qubit gate
630+
* @param target0 First target qubit
631+
* @param target1 Second target qubit
632+
* @return DD representing the gate
633+
* @throws std::runtime_error if the number of qubits is larger than the
634+
* package configuration
635+
*/
636+
mEdge makeTwoQubitGateDD(const TwoQubitGateMatrix& mat,
637+
const qc::Control& control, const qc::Qubit target0,
638+
const qc::Qubit target1) {
639+
return makeTwoQubitGateDD(mat, qc::Controls{control}, target0, target1);
640+
}
641+
626642
/**
627643
Creates the DD for a two-qubit gate
628644
@param mat Matrix representation of the gate

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ qiskit = [
4343
]
4444

4545
[project.scripts]
46-
mqt-core-dd-compare = "mqt.core.dd.evaluation:main"
46+
mqt-core-dd-compare = "mqt.core.dd_evaluation:main"
4747
mqt-core-cli = "mqt.core.__main__:main"
4848

4949
[project.urls]
@@ -79,6 +79,7 @@ build.targets = [
7979
"mqt-core-ds",
8080
"mqt-core-na",
8181
"ir",
82+
"dd",
8283
]
8384

8485
metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"
@@ -213,7 +214,7 @@ isort.required-imports = ["from __future__ import annotations"]
213214
"I002", # Allow missing `from __future__ import annotations` import
214215
]
215216
"src/mqt/core/_compat/**.py" = ["TID251", "A005"]
216-
"src/mqt/core/dd/evaluation.py" = ["T201"]
217+
"src/mqt/core/dd_evaluation.py" = ["T201"]
217218
"src/mqt/core/__main__.py" = ["T201"]
218219

219220
[tool.ruff.lint.pydocstyle]

src/dd/Edge.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,8 +484,8 @@ void Edge<Node>::traverseMatrix(const std::complex<fp>& amp,
484484
const std::size_t x = i | (1ULL << nextLevel);
485485
const std::size_t y = j | (1ULL << nextLevel);
486486
if (isTerminal() || p->v < nextLevel) {
487-
traverseMatrix(c, i, j, f, nextLevel, threshold);
488-
traverseMatrix(c, x, y, f, nextLevel, threshold);
487+
traverseMatrix(amp, i, j, f, nextLevel, threshold);
488+
traverseMatrix(amp, x, y, f, nextLevel, threshold);
489489
return;
490490
}
491491

src/dd/Simulation.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,22 +124,24 @@ sample(const QuantumComputation& qc, const VectorDD& in, Package<Config>& dd,
124124
dd.decRef(e);
125125

126126
std::map<std::string, std::size_t> actualCounts{};
127+
const auto numBits =
128+
qc.getClassicalRegisters().empty() ? qc.getNqubits() : qc.getNcbits();
127129
for (const auto& [bitstring, count] : counts) {
128-
std::string measurement(qc.getNcbits(), '0');
130+
std::string measurement(numBits, '0');
129131
if (hasMeasurements) {
130132
// if the circuit contains measurements, we only want to return the
131133
// measured bits
132134
for (const auto& [qubit, bit] : measurementMap) {
133135
// measurement map specifies that the circuit `qubit` is measured into
134136
// a certain `bit`
135-
measurement[qc.getNcbits() - 1U - bit] =
137+
measurement[numBits - 1U - bit] =
136138
bitstring[bitstring.size() - 1U - qc.outputPermutation.at(qubit)];
137139
}
138140
} else {
139141
// otherwise, we consider the output permutation for determining where
140142
// to measure the qubits to
141143
for (const auto& [qubit, bit] : qc.outputPermutation) {
142-
measurement[qc.getNcbits() - 1U - bit] =
144+
measurement[numBits - 1U - bit] =
143145
bitstring[bitstring.size() - 1U - qubit];
144146
}
145147
}

0 commit comments

Comments
 (0)