Skip to content

Commit 913fd2d

Browse files
denialhaagpre-commit-ci[bot]burgholzer
authored
🎨🐍 Streamline Python bindings (#665)
## Description This PR further streamlines the implementation of Python bindings. It is a continuation of #624. ## Checklist: - [x] The pull request only contains commits that are focused and relevant to this change. - [x] ~~I have added appropriate tests that cover the new/changed functionality.~~ - [x] I have updated the documentation to reflect these changes. - [x] I have added entries to the changelog for any noteworthy additions, changes, fixes or removals. - [x] I have added migration instructions to the upgrade guide (if needed). - [x] The changes follow the project's style guidelines and introduce no new warnings. - [x] The changes are fully tested and pass the CI checks. - [x] I have reviewed my own code changes. --------- Signed-off-by: Daniel Haag <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Lukas Burgholzer <[email protected]>
1 parent f4f7e47 commit 913fd2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1004
-881
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,12 @@ jobs:
137137
if: fromJSON(needs.change-detection.outputs.run-cpp-linter)
138138
uses: munich-quantum-toolkit/workflows/.github/workflows/[email protected]
139139
with:
140-
setup-z3: true
141140
clang-version: 20
142-
setup-python: true
141+
cmake-args: -DBUILD_MQT_QMAP_BINDINGS=ON
142+
files-changed-only: true
143143
install-pkgs: "pybind11==3.0.0"
144-
cmake-args: "-DBUILD_MQT_QMAP_BINDINGS=ON"
144+
setup-python: true
145+
setup-z3: true
145146

146147
# run extensive Python tests whenever this is on a PR and the PR has the `extensive-python-ci` label
147148
python-tests:

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
<!-- Entries in each category are sorted by merge time, with the latest PRs appearing first. -->
2+
13
# Changelog
24

35
All notable changes to this project will be documented in this file.
@@ -9,9 +11,15 @@ This project adheres to [Semantic Versioning], with the exception that minor rel
911

1012
### Changed
1113

14+
- ♻️ Restructure the Python code to introduce modules ([#665]) ([**@denialhaag**])
15+
- ♻️ Restructure the C++ code for the Python bindings to mirror the introduced Python modules ([#665]) ([**@denialhaag**])
1216
- **Breaking**: ⬆️ Bump minimum required MQT Core version to `3.1.0` ([#694]) ([**@denialhaag**])
1317
- **Breaking**: ⬆️ Bump minimum required `pybind11` version to `3.0.0` ([#694]) ([**@denialhaag**])
1418

19+
### Removed
20+
21+
- 🔥 Remove CMake function `add_mqt_qmap_binding` ([#665]) ([**@denialhaag**])
22+
1523
## [3.1.3] - 2025-05-28
1624

1725
### Fixed
@@ -85,6 +93,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._
8593
<!-- PR links -->
8694

8795
[#694]: https://github.com/munich-quantum-toolkit/qmap/pull/694
96+
[#665]: https://github.com/munich-quantum-toolkit/qmap/pull/665
8897
[#661]: https://github.com/munich-quantum-toolkit/qmap/pull/661
8998
[#660]: https://github.com/munich-quantum-toolkit/qmap/pull/660
9099
[#659]: https://github.com/munich-quantum-toolkit/qmap/pull/659

CMakeLists.txt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ endif()
7474

7575
option(BUILD_MQT_QMAP_TESTS "Also build tests for the MQT QMAP project" ${MQT_QMAP_MASTER_PROJECT})
7676

77-
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
78-
include(ExternalDependencies)
79-
include(AddMQTQMAPBinding)
77+
include(cmake/ExternalDependencies.cmake)
8078

8179
# set the include directory for the build tree
8280
set(MQT_QMAP_INCLUDE_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include")

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ QMAP is available via [PyPI](https://pypi.org/project/mqt.qmap/) for Linux, macO
4646
Compiling a given quantum circuit to a certain device is as easy as
4747

4848
```python3
49-
from mqt import qmap
49+
from mqt.qmap.plugins.qiskit.sc import compile
5050
from qiskit import QuantumCircuit
5151
from qiskit.providers.fake_provider import GenericBackendV2
5252

@@ -59,13 +59,13 @@ arch = GenericBackendV2(
5959
num_qubits=5,
6060
coupling_map=[[0, 1], [1, 0], [1, 2], [2, 1], [1, 3], [3, 1], [3, 4], [4, 3]],
6161
)
62-
circ_mapped, results = qmap.compile(circ, arch=arch)
62+
circ_mapped, results = compile(circ, arch=arch)
6363
```
6464

6565
Optimizing a Clifford circuit is as easy as
6666

6767
```python3
68-
from mqt import qmap
68+
from mqt.qmap.plugins.qiskit.clifford_synthesis import optimize_clifford
6969
from qiskit import QuantumCircuit
7070

7171
circ = QuantumCircuit(2)
@@ -74,7 +74,7 @@ circ.cx(0, 1)
7474
circ.h(0)
7575
circ.h(1)
7676

77-
circ_opt, results = qmap.optimize_clifford(circ)
77+
circ_opt, results = optimize_clifford(circ)
7878
```
7979

8080
**Detailed documentation on all available methods, options, and input formats is available at [ReadTheDocs](https://mqt.readthedocs.io/projects/qmap).**

UPGRADING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ This document describes breaking changes and how to upgrade. For a complete list
44

55
## [Unreleased]
66

7+
With this release, the Python package has been restructured.
8+
In particular, the `mqt.qmap.pyqmap` module has been discontinued.
9+
Classes and functions can now be imported from the more descriptive `mqt.qmap.clifford_synthesis`, `mqt.qmap.hybrid_mapper`, `mqt.qmap.na`, and `mqt.qmap.sc` modules.
10+
The superconducting module's `compile()` function has been moved to `mqt.qmap.plugins.qiskit.sc`.
11+
The entrypoints `synthesize_clifford()` and `optimize_clifford()` of the Clifford synthesis module have been moved to `mqt.qmap.plugins.qiskit.clifford_synthesis`.
12+
713
## [3.1.0]
814

915
This minor release initiates the efforts to re-structure the Python bindings and make them more modular.

bindings/CMakeLists.txt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,8 @@ list(
2525
${BASEPOINT}/../../core/lib
2626
${BASEPOINT}/../../core/lib64)
2727

28-
add_mqt_qmap_binding(
29-
pyqmap
30-
bindings.cpp
31-
LINK_LIBS
32-
MQT::QMapSCExact
33-
MQT::QMapSCHeuristic
34-
MQT::QMapCliffordSynthesis
35-
MQT::QMapHybrid
36-
MQT::NASP
37-
MQT::QMapNAZoned
38-
MQT::CoreQASM
39-
pybind11_json)
40-
target_compile_definitions(pyqmap PRIVATE Z3_FOUND)
41-
4228
# add all modules
29+
add_subdirectory(clifford_synthesis)
30+
add_subdirectory(hybrid_mapper)
4331
add_subdirectory(na)
32+
add_subdirectory(sc)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
2+
# Copyright (c) 2025 Munich Quantum Software Company GmbH
3+
# All rights reserved.
4+
#
5+
# SPDX-License-Identifier: MIT
6+
#
7+
# Licensed under the MIT License
8+
9+
add_mqt_python_binding(
10+
QMAP
11+
${MQT_QMAP_TARGET_NAME}-clifford_synthesis-bindings
12+
clifford_synthesis.cpp
13+
MODULE_NAME
14+
clifford_synthesis
15+
INSTALL_DIR
16+
.
17+
LINK_LIBS
18+
MQT::QMapCliffordSynthesis
19+
MQT::CoreQASM
20+
pybind11_json)
21+
22+
# install the Python stub files in editable mode for better IDE support
23+
if(SKBUILD_STATE STREQUAL "editable")
24+
install(
25+
FILES ${PROJECT_SOURCE_DIR}/python/mqt/qmap/clifford_synthesis.pyi
26+
DESTINATION ./clifford_synthesis
27+
COMPONENT ${MQT_QMAP_TARGET_NAME}_Python)
28+
endif()
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright (c) 2023 - 2025 Chair for Design Automation, TUM
3+
* Copyright (c) 2025 Munich Quantum Software Company GmbH
4+
* All rights reserved.
5+
*
6+
* SPDX-License-Identifier: MIT
7+
*
8+
* Licensed under the MIT License
9+
*/
10+
11+
#include "cliffordsynthesis/CliffordSynthesizer.hpp"
12+
#include "cliffordsynthesis/Configuration.hpp"
13+
#include "cliffordsynthesis/Results.hpp"
14+
#include "cliffordsynthesis/Tableau.hpp"
15+
#include "cliffordsynthesis/TargetMetric.hpp"
16+
#include "ir/QuantumComputation.hpp"
17+
#include "qasm3/Importer.hpp"
18+
19+
#include <cstddef>
20+
#include <nlohmann/json.hpp>
21+
#include <plog/Severity.h>
22+
#include <pybind11/cast.h>
23+
#include <pybind11/detail/common.h>
24+
#include <pybind11/pybind11.h>
25+
#include <pybind11/pytypes.h>
26+
// NOLINTNEXTLINE(misc-include-cleaner)
27+
#include <pybind11/stl.h>
28+
#include <string>
29+
30+
namespace py = pybind11;
31+
using namespace pybind11::literals;
32+
33+
PYBIND11_MODULE(MQT_QMAP_MODULE_NAME, m, py::mod_gil_not_used()) {
34+
// Target metric for the Clifford synthesizer
35+
py::enum_<cs::TargetMetric>(m, "TargetMetric")
36+
.value("gates", cs::TargetMetric::Gates, "Optimize gate count.")
37+
.value("two_qubit_gates", cs::TargetMetric::TwoQubitGates,
38+
"Optimize two-qubit gate count.")
39+
.value("depth", cs::TargetMetric::Depth, "Optimize circuit depth.")
40+
.export_values()
41+
.def(py::init([](const std::string& name) {
42+
return cs::targetMetricFromString(name);
43+
}));
44+
py::implicitly_convertible<py::str, cs::TargetMetric>();
45+
46+
py::enum_<plog::Severity>(m, "Verbosity")
47+
.value("none", plog::Severity::none, "No output.")
48+
.value("fatal", plog::Severity::fatal, "Only show fatal errors.")
49+
.value("error", plog::Severity::error, "Show errors.")
50+
.value("warning", plog::Severity::warning, "Show warnings.")
51+
.value("info", plog::Severity::info, "Show general information.")
52+
.value("debug", plog::Severity::debug,
53+
"Show additional debug information.")
54+
.value("verbose", plog::Severity::verbose, "Show all information.")
55+
.export_values()
56+
.def(py::init([](const std::string& name) {
57+
return plog::severityFromString(name.c_str());
58+
}));
59+
py::implicitly_convertible<py::str, plog::Severity>();
60+
61+
// Configuration for the synthesis
62+
py::class_<cs::Configuration>(
63+
m, "SynthesisConfiguration",
64+
"Configuration options for the MQT QMAP Clifford synthesis tool.")
65+
.def(py::init<>())
66+
.def_readwrite("initial_timestep_limit",
67+
&cs::Configuration::initialTimestepLimit,
68+
"Initial timestep limit for the Clifford synthesis. "
69+
"Defaults to `0`, which implies that the initial timestep "
70+
"limit is determined automatically.")
71+
.def_readwrite(
72+
"minimal_timesteps", &cs::Configuration::minimalTimesteps,
73+
"Minimal timestep considered for the Clifford synthesis. "
74+
"This option limits the lower bound of the interval in "
75+
"which the binary search method looks for solutions. "
76+
"Set this if you know a lower bound for the circuit depth. "
77+
"Defaults to `0`, which implies that no lower bound for depth "
78+
"is known.")
79+
.def_readwrite(
80+
"use_maxsat", &cs::Configuration::useMaxSAT,
81+
"Use MaxSAT to solve the synthesis problem or to really on the "
82+
"binary search scheme for finding the optimum. Defaults to `false`.")
83+
.def_readwrite("linear_search", &cs::Configuration::linearSearch,
84+
"Use liner search instead of binary search "
85+
"scheme for finding the optimum. Defaults to `false`.")
86+
.def_readwrite(
87+
"target_metric", &cs::Configuration::target,
88+
"Target metric for the Clifford synthesis. Defaults to `gates`.")
89+
.def_readwrite("use_symmetry_breaking",
90+
&cs::Configuration::useSymmetryBreaking,
91+
"Use symmetry breaking clauses to speed up the synthesis "
92+
"process. Defaults to `true`.")
93+
.def_readwrite("dump_intermediate_results",
94+
&cs::Configuration::dumpIntermediateResults,
95+
"Dump intermediate results of the synthesis process. "
96+
"Defaults to `false`.")
97+
.def_readwrite("intermediate_results_path",
98+
&cs::Configuration::intermediateResultsPath,
99+
"Path to the directory where intermediate results should "
100+
"be dumped. Defaults to `./`. The path needs to include a "
101+
"path separator at the end.")
102+
.def_readwrite(
103+
"verbosity", &cs::Configuration::verbosity,
104+
"Verbosity level for the synthesis process. Defaults to 'warning'.")
105+
.def_readwrite("solver_parameters", &cs::Configuration::solverParameters,
106+
"Parameters to be passed to Z3 as dict[str, bool | int | "
107+
"float | str]")
108+
.def_readwrite(
109+
"minimize_gates_after_depth_optimization",
110+
&cs::Configuration::minimizeGatesAfterDepthOptimization,
111+
"Depth optimization might produce a circuit with more gates than "
112+
"necessary. This option enables an additional run of the synthesizer "
113+
"to minimize the overall number of gates. Defaults to `false`.")
114+
.def_readwrite(
115+
"try_higher_gate_limit_for_two_qubit_gate_optimization",
116+
&cs::Configuration::tryHigherGateLimitForTwoQubitGateOptimization,
117+
"When optimizing two-qubit gates, the synthesizer might fail "
118+
"to find an optimal solution for a certain timestep limit, but there "
119+
"might be a better solution for some higher timestep limit. This "
120+
"option enables an additional run of the synthesizer with a higher "
121+
"gate limit. Defaults to `false`.")
122+
.def_readwrite("gate_limit_factor", &cs::Configuration::gateLimitFactor,
123+
"Factor by which the gate limit is increased when "
124+
"trying to find a better solution for the two-qubit "
125+
"gate optimization. Defaults to `1.1`.")
126+
.def_readwrite(
127+
"minimize_gates_after_two_qubit_gate_optimization",
128+
&cs::Configuration::minimizeGatesAfterTwoQubitGateOptimization,
129+
"Two-qubit gate optimization might produce a circuit "
130+
"with more gates than necessary. This option enables "
131+
"an additional run of the synthesizer to minimize the "
132+
"overall number of gates. Defaults to `false`.")
133+
.def_readwrite("heuristic", &cs::Configuration::heuristic,
134+
"Use heuristic to synthesize the circuit. "
135+
"This method synthesizes shallow intermediate circuits "
136+
"and combines them. Defaults to `false`.")
137+
.def_readwrite("split_size", &cs::Configuration::splitSize,
138+
"Size of subcircuits used in heuristic. "
139+
"Defaults to `5`.")
140+
.def_readwrite(
141+
"n_threads_heuristic", &cs::Configuration::nThreadsHeuristic,
142+
"Maximum number of threads used for the heuristic optimizer. "
143+
"Defaults to the number of available threads on the system.")
144+
.def("json", &cs::Configuration::json,
145+
"Returns a JSON-style dictionary of all the information present in "
146+
"the :class:`.Configuration`")
147+
.def(
148+
"__repr__",
149+
[](const cs::Configuration& config) { return config.json().dump(2); },
150+
"Prints a JSON-formatted representation of all the information "
151+
"present in the :class:`.Configuration`");
152+
153+
// Results of the synthesis
154+
py::class_<cs::Results>(m, "SynthesisResults",
155+
"Results of the MQT QMAP Clifford synthesis tool.")
156+
.def(py::init<>())
157+
.def_property_readonly("gates", &cs::Results::getGates,
158+
"Returns the number of gates in the circuit.")
159+
.def_property_readonly("single_qubit_gates",
160+
&cs::Results::getSingleQubitGates,
161+
"Returns the number of single-qubit gates in the "
162+
"synthesized circuit.")
163+
.def_property_readonly("two_qubit_gates", &cs::Results::getTwoQubitGates,
164+
"Returns the number of two-qubit gates in the "
165+
"synthesized circuit.")
166+
.def_property_readonly("depth", &cs::Results::getDepth,
167+
"Returns the depth of the synthesized circuit.")
168+
.def_property_readonly("runtime", &cs::Results::getRuntime,
169+
"Returns the runtime of the synthesis in seconds.")
170+
.def_property_readonly("solver_calls", &cs::Results::getSolverCalls,
171+
"Returns the number of calls to the SAT solver.")
172+
.def_property_readonly(
173+
"circuit", &cs::Results::getResultCircuit,
174+
"Returns the synthesized circuit as a qasm string.")
175+
.def_property_readonly("tableau", &cs::Results::getResultTableau,
176+
"Returns a string representation of the "
177+
"synthesized circuit's tableau.")
178+
.def("sat", &cs::Results::sat,
179+
"Returns `true` if the synthesis was successful.")
180+
.def("unsat", &cs::Results::unsat,
181+
"Returns `true` if the synthesis was unsuccessful.");
182+
183+
auto tableau = py::class_<cs::Tableau>(
184+
m, "Tableau", "A class for representing stabilizer tableaus.");
185+
tableau.def(py::init<std::size_t, bool>(), "n"_a,
186+
"include_destabilizers"_a = false,
187+
"Creates a tableau for an n-qubit Clifford.");
188+
tableau.def(
189+
py::init<const std::string&>(), "tableau"_a,
190+
"Constructs a tableau from a string description. This can either be a "
191+
"semicolon separated binary matrix or a list of Pauli strings.");
192+
tableau.def(
193+
py::init<const std::string&, const std::string&>(), "stabilizers"_a,
194+
"destabilizers"_a,
195+
"Constructs a tableau from two lists of Pauli strings, the Stabilizers"
196+
"and Destabilizers.");
197+
198+
auto synthesizer = py::class_<cs::CliffordSynthesizer>(
199+
m, "CliffordSynthesizer", "A class for synthesizing Clifford circuits.");
200+
201+
synthesizer.def(py::init<cs::Tableau, cs::Tableau>(), "initial_tableau"_a,
202+
"target_tableau"_a,
203+
"Constructs a synthesizer for two tableaus representing the "
204+
"initial and target state.");
205+
synthesizer.def(py::init<cs::Tableau>(), "target_tableau"_a,
206+
"Constructs a synthesizer for a tableau representing the "
207+
"target state.");
208+
synthesizer.def(py::init<qc::QuantumComputation&, bool>(), "qc"_a,
209+
"use_destabilizers"_a
210+
"Constructs a synthesizer for a quantum computation "
211+
"representing the target state.");
212+
synthesizer.def(
213+
py::init<cs::Tableau, qc::QuantumComputation&>(), "initial_tableau"_a,
214+
"qc"_a,
215+
"Constructs a synthesizer for a quantum computation representing the "
216+
"target state that starts in an initial state represented by a tableau.");
217+
synthesizer.def("synthesize", &cs::CliffordSynthesizer::synthesize,
218+
"config"_a = cs::Configuration(),
219+
"Runs the synthesis with the given configuration.");
220+
synthesizer.def_property_readonly("results",
221+
&cs::CliffordSynthesizer::getResults,
222+
"Returns the results of the synthesis.");
223+
synthesizer.def_property_readonly(
224+
"result_circuit", [](cs::CliffordSynthesizer& self) {
225+
return qasm3::Importer::imports(self.getResults().getResultCircuit());
226+
});
227+
}

0 commit comments

Comments
 (0)