Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ jobs:
clang-version: 20
cmake-args: -DBUILD_MQT_QCEC_BINDINGS=ON
files-changed-only: true
install-pkgs: "pybind11==3.0.1"
install-pkgs: "nanobind==2.10.2"
setup-python: true
cpp-linter-extra-args: "-std=c++20"

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Changed

- ♻️ Migrate Python bindings from `pybind11` to `nanobind` ([#817]) ([**@denialhaag**])
- 📦️ Provide Stable ABI wheels for Python 3.12+ ([#817]) ([**@denialhaag**])
- ⬆️ Bump minimum required `mqt-core` version to `3.4.0` ([#817]) ([**@denialhaag**])
- 👷 Stop testing on `ubuntu-22.04` and `ubuntu-22.04-arm` runners ([#796]) ([**@denialhaag**])
- 👷 Stop testing with `clang-19` and start testing with `clang-21` ([#796]) ([**@denialhaag**])
- 👷 Fix macOS tests with Homebrew Clang via new `munich-quantum-toolkit/workflows` version ([#796]) ([**@denialhaag**])
Expand Down Expand Up @@ -112,6 +115,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._

<!-- PR links -->

[#817]: https://github.com/munich-quantum-toolkit/qcec/pull/817
[#796]: https://github.com/munich-quantum-toolkit/qcec/pull/796
[#735]: https://github.com/munich-quantum-toolkit/qcec/pull/735
[#730]: https://github.com/munich-quantum-toolkit/qcec/pull/730
Expand Down
8 changes: 3 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Licensed under the MIT License

# set required cmake version
cmake_minimum_required(VERSION 3.24...4.0)
cmake_minimum_required(VERSION 3.24...4.2)

project(
mqt-qcec
Expand Down Expand Up @@ -37,10 +37,8 @@ if(BUILD_MQT_QCEC_BINDINGS)
endif()

# top-level call to find Python
find_package(
Python 3.10 REQUIRED
COMPONENTS Interpreter Development.Module
OPTIONAL_COMPONENTS Development.SABIModule)
find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module
${SKBUILD_SABI_COMPONENT})
endif()

# check if this is the master project or used via add_subdirectory
Expand Down
18 changes: 15 additions & 3 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ This document describes breaking changes and how to upgrade. For a complete list

## [Unreleased]

### Removal of Python 3.13t wheels
### Python wheels

This release contains two changes to the distributed wheels.

First, we have removed all wheels for Python 3.13t.
Free-threading Python was introduced as an experimental feature in Python 3.13.
It became stable in Python 3.14.
To conserve space on PyPI and to reduce the CD build times, we have removed all wheels for Python 3.13t from our CI.
We continue to provide wheels for the regular Python versions 3.10 to 3.14, as well as 3.14t.

Second, for Python 3.12+, we are now providing Stable ABI wheels instead of separate version-specific wheels.
This was enabled by migrating our Python bindings from `pybind11` to `nanobind`.

Both of these changes were made in the interest of conserving PyPI space and reducing CI/CD build times.
The full list of wheels now reads:

- 3.10
- 3.11
- 3.12+ Stable ABI
- 3.14t

Comment thread
denialhaag marked this conversation as resolved.
## [3.3.0]

Expand Down
5 changes: 2 additions & 3 deletions bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ list(

file(GLOB_RECURSE QCEC_SOURCES **.cpp)

add_mqt_python_binding(
add_mqt_python_binding_nanobind(
QCEC
${MQT_QCEC_TARGET_NAME}-bindings
${QCEC_SOURCES}
Expand All @@ -36,8 +36,7 @@ add_mqt_python_binding(
INSTALL_DIR
.
LINK_LIBS
MQT::QCEC
pybind11_json)
MQT::QCEC)

# install the Python stub files in editable mode for better IDE support
if(SKBUILD_STATE STREQUAL "editable")
Expand Down
17 changes: 6 additions & 11 deletions bindings/application_scheme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,16 @@

#include "checker/dd/applicationscheme/ApplicationScheme.hpp"

#include <pybind11/native_enum.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // NOLINT(misc-include-cleaner)
#include <nanobind/nanobind.h>

namespace ec {

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

// NOLINTNEXTLINE(misc-use-internal-linkage)
void registerApplicationSchema(const py::module& mod) {
py::native_enum<ApplicationSchemeType>(
mod, "ApplicationScheme", "enum.Enum",
"Enumeration describing the application order of operations.")
void registerApplicationSchema(const nb::module_& m) {
nb::enum_<ApplicationSchemeType>(m, "ApplicationScheme", nb::is_arithmetic())
.value("sequential", ApplicationSchemeType::Sequential)
.value("reference", ApplicationSchemeType::Sequential)
.value("one_to_one", ApplicationSchemeType::OneToOne)
Expand All @@ -37,8 +33,7 @@ void registerApplicationSchema(const py::module& mod) {
"second circuit. Referred to as *compilation_flow* in "
":cite:p:`burgholzer2020verifyingResultsIBM`.")
.value("compilation_flow", ApplicationSchemeType::GateCost)
.value("proportional", ApplicationSchemeType::Proportional)
.finalize();
.value("proportional", ApplicationSchemeType::Proportional);
}

} // namespace ec
31 changes: 15 additions & 16 deletions bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,27 @@
* Licensed under the MIT License
*/

#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // NOLINT(misc-include-cleaner)
#include <nanobind/nanobind.h>

namespace ec {

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

// forward declarations
void registerApplicationSchema(const py::module& mod);
void registerConfiguration(const py::module& mod);
void registerEquivalenceCheckingManager(const py::module& mod);
void registerEquivalenceCriterion(const py::module& mod);
void registerStateType(const py::module& mod);
void registerApplicationSchema(const nb::module_& m);
void registerConfiguration(const nb::module_& m);
void registerEquivalenceCheckingManager(const nb::module_& m);
void registerEquivalenceCriterion(const nb::module_& m);
void registerStateType(const nb::module_& m);

// NOLINTNEXTLINE(misc-include-cleaner)
PYBIND11_MODULE(MQT_QCEC_MODULE_NAME, mod, py::mod_gil_not_used()) {
registerApplicationSchema(mod);
registerConfiguration(mod);
registerEquivalenceCheckingManager(mod);
registerEquivalenceCriterion(mod);
registerStateType(mod);
// NOLINTNEXTLINE(performance-unnecessary-value-param)
NB_MODULE(MQT_QCEC_MODULE_NAME, m) {
registerApplicationSchema(m);
registerConfiguration(m);
registerEquivalenceCheckingManager(m);
registerEquivalenceCriterion(m);
registerStateType(m);
}

} // namespace ec
154 changes: 78 additions & 76 deletions bindings/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,109 +10,111 @@

#include "Configuration.hpp"

#include <pybind11/pybind11.h>
#include <pybind11/stl.h> // NOLINT(misc-include-cleaner)
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)

namespace ec {

namespace py = pybind11;
using namespace pybind11::literals;
namespace nb = nanobind;
using namespace nb::literals;

// NOLINTNEXTLINE(misc-use-internal-linkage)
void registerConfiguration(const py::module& mod) {
void registerConfiguration(const nb::module_& m) {
// Class definitions
auto configuration = py::class_<Configuration>(mod, "Configuration");
auto configuration = nb::class_<Configuration>(m, "Configuration");
auto execution =
py::class_<Configuration::Execution>(configuration, "Execution");
nb::class_<Configuration::Execution>(configuration, "Execution");
auto optimizations =
py::class_<Configuration::Optimizations>(configuration, "Optimizations");
nb::class_<Configuration::Optimizations>(configuration, "Optimizations");
auto application =
py::class_<Configuration::Application>(configuration, "Application");
nb::class_<Configuration::Application>(configuration, "Application");
auto functionality =
py::class_<Configuration::Functionality>(configuration, "Functionality");
nb::class_<Configuration::Functionality>(configuration, "Functionality");
auto simulation =
py::class_<Configuration::Simulation>(configuration, "Simulation");
nb::class_<Configuration::Simulation>(configuration, "Simulation");
auto parameterized =
py::class_<Configuration::Parameterized>(configuration, "Parameterized");
nb::class_<Configuration::Parameterized>(configuration, "Parameterized");

// Configuration
configuration.def(py::init<>())
.def_readwrite("execution", &Configuration::execution)
.def_readwrite("optimizations", &Configuration::optimizations)
.def_readwrite("application", &Configuration::application)
.def_readwrite("functionality", &Configuration::functionality)
.def_readwrite("simulation", &Configuration::simulation)
.def_readwrite("parameterized", &Configuration::parameterized)
.def("json", &Configuration::json)
configuration.def(nb::init<>())
.def_rw("execution", &Configuration::execution)
.def_rw("optimizations", &Configuration::optimizations)
.def_rw("application", &Configuration::application)
.def_rw("functionality", &Configuration::functionality)
.def_rw("simulation", &Configuration::simulation)
.def_rw("parameterized", &Configuration::parameterized)
.def("json",
[](const Configuration& config) {
const nb::module_ json = nb::module_::import_("json");
const nb::object loads = json.attr("loads");
return loads(config.json().dump());
})
.def("__repr__", &Configuration::toString);

// execution options
execution.def(py::init<>())
.def_readwrite("parallel", &Configuration::Execution::parallel)
.def_readwrite("nthreads", &Configuration::Execution::nthreads)
.def_readwrite("timeout", &Configuration::Execution::timeout)
.def_readwrite("run_construction_checker",
&Configuration::Execution::runConstructionChecker)
.def_readwrite("run_simulation_checker",
&Configuration::Execution::runSimulationChecker)
.def_readwrite("run_alternating_checker",
&Configuration::Execution::runAlternatingChecker)
.def_readwrite("run_zx_checker", &Configuration::Execution::runZXChecker)
.def_readwrite("numerical_tolerance",
&Configuration::Execution::numericalTolerance)
.def_readwrite("set_all_ancillae_garbage",
&Configuration::Execution::setAllAncillaeGarbage);
execution.def(nb::init<>())
.def_rw("parallel", &Configuration::Execution::parallel)
.def_rw("nthreads", &Configuration::Execution::nthreads)
.def_rw("timeout", &Configuration::Execution::timeout)
.def_rw("run_construction_checker",
&Configuration::Execution::runConstructionChecker)
.def_rw("run_simulation_checker",
&Configuration::Execution::runSimulationChecker)
.def_rw("run_alternating_checker",
&Configuration::Execution::runAlternatingChecker)
.def_rw("run_zx_checker", &Configuration::Execution::runZXChecker)
.def_rw("numerical_tolerance",
&Configuration::Execution::numericalTolerance)
.def_rw("set_all_ancillae_garbage",
&Configuration::Execution::setAllAncillaeGarbage);

// optimization options
optimizations.def(py::init<>())
.def_readwrite("fuse_single_qubit_gates",
&Configuration::Optimizations::fuseSingleQubitGates)
.def_readwrite("reconstruct_swaps",
&Configuration::Optimizations::reconstructSWAPs)
.def_readwrite(
"remove_diagonal_gates_before_measure",
&Configuration::Optimizations::removeDiagonalGatesBeforeMeasure)
.def_readwrite("transform_dynamic_circuit",
&Configuration::Optimizations::transformDynamicCircuit)
.def_readwrite("reorder_operations",
&Configuration::Optimizations::reorderOperations)
.def_readwrite(
"backpropagate_output_permutation",
&Configuration::Optimizations::backpropagateOutputPermutation)
.def_readwrite("elide_permutations",
&Configuration::Optimizations::elidePermutations);
optimizations.def(nb::init<>())
.def_rw("fuse_single_qubit_gates",
&Configuration::Optimizations::fuseSingleQubitGates)
.def_rw("reconstruct_swaps",
&Configuration::Optimizations::reconstructSWAPs)
.def_rw("remove_diagonal_gates_before_measure",
&Configuration::Optimizations::removeDiagonalGatesBeforeMeasure)
.def_rw("transform_dynamic_circuit",
&Configuration::Optimizations::transformDynamicCircuit)
.def_rw("reorder_operations",
&Configuration::Optimizations::reorderOperations)
.def_rw("backpropagate_output_permutation",
&Configuration::Optimizations::backpropagateOutputPermutation)
.def_rw("elide_permutations",
&Configuration::Optimizations::elidePermutations);

// application options
application.def(py::init<>())
.def_readwrite("construction_scheme",
&Configuration::Application::constructionScheme)
.def_readwrite("simulation_scheme",
&Configuration::Application::simulationScheme)
.def_readwrite("alternating_scheme",
&Configuration::Application::alternatingScheme)
.def_readwrite("profile", &Configuration::Application::profile);
application.def(nb::init<>())
.def_rw("construction_scheme",
&Configuration::Application::constructionScheme)
.def_rw("simulation_scheme",
&Configuration::Application::simulationScheme)
.def_rw("alternating_scheme",
&Configuration::Application::alternatingScheme)
.def_rw("profile", &Configuration::Application::profile);

// functionality options
functionality.def(py::init<>())
.def_readwrite("trace_threshold",
&Configuration::Functionality::traceThreshold)
.def_readwrite("check_partial_equivalence",
&Configuration::Functionality::checkPartialEquivalence);
functionality.def(nb::init<>())
.def_rw("trace_threshold", &Configuration::Functionality::traceThreshold)
.def_rw("check_partial_equivalence",
&Configuration::Functionality::checkPartialEquivalence);

// simulation options
simulation.def(py::init<>())
.def_readwrite("fidelity_threshold",
&Configuration::Simulation::fidelityThreshold)
.def_readwrite("max_sims", &Configuration::Simulation::maxSims)
.def_readwrite("state_type", &Configuration::Simulation::stateType)
.def_readwrite("seed", &Configuration::Simulation::seed);
simulation.def(nb::init<>())
.def_rw("fidelity_threshold",
&Configuration::Simulation::fidelityThreshold)
.def_rw("max_sims", &Configuration::Simulation::maxSims)
.def_rw("state_type", &Configuration::Simulation::stateType)
.def_rw("seed", &Configuration::Simulation::seed);

// parameterized options
parameterized.def(py::init<>())
.def_readwrite("parameterized_tolerance",
&Configuration::Parameterized::parameterizedTol)
.def_readwrite("additional_instantiations",
&Configuration::Parameterized::nAdditionalInstantiations);
parameterized.def(nb::init<>())
.def_rw("parameterized_tolerance",
&Configuration::Parameterized::parameterizedTol)
.def_rw("additional_instantiations",
&Configuration::Parameterized::nAdditionalInstantiations);
Comment thread
burgholzer marked this conversation as resolved.
}

} // namespace ec
Loading
Loading