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
351 changes: 351 additions & 0 deletions .agent/plans/backport-nanobind-3-split-wheels-v3.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
ubuntu-24.04,
ubuntu-24.04-arm,
macos-26,
macos-26-intel,
windows-2025,
windows-11-arm,
]
Expand Down
22 changes: 5 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,9 @@ jobs:
strategy:
fail-fast: false
matrix:
include:
- runs-on: macos-26
compiler: clang
preset: debug
- runs-on: macos-26-intel
compiler: clang
preset: release
runs-on: [macos-26]
compiler: [clang]
preset: [release, debug]
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-tests-macos.yml@f33c11c5cf68957b3074fff11ae43cfce2aaf593 # v2.2.3
with:
runs-on: ${{ matrix.runs-on }}
Expand Down Expand Up @@ -105,7 +101,7 @@ jobs:
build-project: true
files-changed-only: true
setup-python: true
install-pkgs: "nanobind==2.15.0"
install-pkgs: "nanobind==3.0.1"
cpp-linter-extra-args: "-std=c++20"
permissions:
contents: read
Expand All @@ -118,14 +114,7 @@ jobs:
strategy:
fail-fast: false
matrix:
runs-on:
[
ubuntu-24.04,
ubuntu-24.04-arm,
macos-26,
macos-26-intel,
windows-2025,
]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-26, windows-2025]
uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-tests.yml@f33c11c5cf68957b3074fff11ae43cfce2aaf593 # v2.2.3
with:
runs-on: ${{ matrix.runs-on }}
Expand Down Expand Up @@ -173,7 +162,6 @@ jobs:
ubuntu-24.04,
ubuntu-24.04-arm,
macos-26,
macos-26-intel,
windows-2025,
windows-11-arm,
]
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ releases may include breaking changes.

## [Unreleased]

### Changed

- 💥 Drop support for x86 macOS and stop publishing the respective wheels
([#2259]) ([**@denialhaag**])
- ⬆️ Raise the macOS deployment target to 13.3 to enable `std::format` in libc++
([#2259]) ([**@denialhaag**])
- 💥 Require Python 3.11 or newer ([#2209]) ([**@denialhaag**],
[**@burgholzer**])
- ⬆️ Update `nanobind` to version 3.0.1 ([#2209], [#2283]) ([**@denialhaag**],
[**@burgholzer**])
- 📦 Publish one split-mode `cp311-abi3` wheel for GIL-enabled CPython 3.11 and
newer ([#2209]) ([**@denialhaag**], [**@burgholzer**])
- 📦 Publish one `cp315-abi3t` wheel for free-threaded CPython 3.15 and newer
([#2209]) ([**@denialhaag**], [**@burgholzer**])
- ⚡ Remove an extra dense copy from `VectorDD.get_vector` ([#2209])
([**@burgholzer**])
- 🐛 Protect process-wide DD, IR, and QDMI state for free-threaded Python
([#2209]) ([**@burgholzer**])

### Removed

- 💥 Remove MQT Core's optional MLIR stack, including its dialects, conversions,
Expand Down Expand Up @@ -731,8 +750,11 @@ for previous changelogs._
<!-- PR links -->

[#2314]: https://github.com/munich-quantum-toolkit/core/pull/2314
[#2283]: https://github.com/munich-quantum-toolkit/core/pull/2283
[#2259]: https://github.com/munich-quantum-toolkit/core/pull/2259
[#2258]: https://github.com/munich-quantum-toolkit/core/pull/2258
[#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232
[#2209]: https://github.com/munich-quantum-toolkit/core/pull/2209
[#2168]: https://github.com/munich-quantum-toolkit/core/pull/2168
[#2157]: https://github.com/munich-quantum-toolkit/core/pull/2157
[#2156]: https://github.com/munich-quantum-toolkit/core/pull/2156
Expand Down
7 changes: 1 addition & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,8 @@ if(BUILD_MQT_CORE_BINDINGS)
ON
CACHE BOOL "Prevent multiple searches for Python and instead cache the results.")

if(DISABLE_GIL)
message(STATUS "Disabling Python GIL")
add_compile_definitions(Py_GIL_DISABLED)
endif()

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

Expand Down
23 changes: 23 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ of changes including minor and patch releases, please refer to the

## [Unreleased]

### macOS support

MQT Core no longer supports x86 macOS. Use Apple silicon with macOS 13.3 or
newer. The new deployment target enables `std::format` in libc++.

### Removal of the QIR stack

MQT Core no longer provides the QIR runtime, JIT, or standalone runner. Remove
Expand All @@ -17,6 +22,24 @@ configurations.
The bundled DDSIM QDMI device no longer accepts QIR Base or Adaptive Profile
programs in string or module form. Use QASM2 or QASM3 with this device.

### Python 3.11 and split-mode wheels

MQT Core now requires Python 3.11 or newer. Upgrade the Python environment
before installing this release.

MQT Core now uses nanobind 3 split mode. One `cp311-abi3` wheel supports
GIL-enabled CPython 3.11 and newer. Free-threaded support starts with CPython
3.15 and uses a separate `cp315-abi3t` wheel. MQT Core no longer publishes
free-threaded CPython 3.13 or 3.14 wheels.

nanobind 3 changes the nanobind ABI. Rebuild downstream native Python extensions
that use MQT Core's nanobind-bound C++ types. Pure Python consumers do not need
to recompile anything.

The Python bindings depend on `nanobind-backend`, which supplies the
interpreter-specific nanobind runtime. This dependency does not change the C++
API or the Python import paths.

## [3.9.2]

### Optional QDMI shot counts
Expand Down
8 changes: 4 additions & 4 deletions bindings/dd/register_dd_package.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ Specifically, it
[](dd::Package& p, const dd::vEdge& v, const qc::NonUnitaryOperation& op,
const std::vector<bool>& measurements,
const qc::Permutation& perm = {}) {
static std::mt19937_64 rng(std::random_device{}());
static thread_local std::mt19937_64 rng(std::random_device{}());
auto measurementsCopy = measurements;
return std::pair{
applyMeasurement(op, v, p, rng, measurementsCopy, perm),
Expand Down Expand Up @@ -351,7 +351,7 @@ Specifically, it
"apply_reset",
[](dd::Package& p, const dd::vEdge& v, const qc::NonUnitaryOperation& op,
const qc::Permutation& perm = {}) {
static std::mt19937_64 rng(std::random_device{}());
static thread_local std::mt19937_64 rng(std::random_device{}());
return applyReset(op, v, p, rng, perm);
},
"vec"_a, "operation"_a, "permutation"_a = qc::Permutation{},
Expand Down Expand Up @@ -399,7 +399,7 @@ Specifically, it
dd.def(
"measure_collapsing",
[](dd::Package& p, dd::vEdge& v, const dd::Qubit q) {
static std::mt19937_64 rng(std::random_device{}());
static thread_local std::mt19937_64 rng(std::random_device{}());
return p.measureOneCollapsing(v, q, rng);
},
"vec"_a, "qubit"_a, R"pb(Measure a qubit and collapse the DD.
Expand All @@ -418,7 +418,7 @@ Specifically, it
dd.def(
"measure_all",
[](dd::Package& p, dd::vEdge& v, const bool collapse = false) {
static std::mt19937_64 rng(std::random_device{}());
static thread_local std::mt19937_64 rng(std::random_device{}());
return p.measureAll(v, collapse, rng);
},
"vec"_a, "collapse"_a = false, R"pb(Measure all qubits.
Expand Down
7 changes: 4 additions & 3 deletions bindings/dd/register_matrix_dds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/vector.h> // NOLINT(misc-include-cleaner)

#include <cmath>
#include <complex>
#include <cstddef>
#include <ios>
Expand All @@ -44,10 +43,11 @@ Matrix getMatrix(const dd::mEdge& m, const size_t numQubits,

if (numQubits == 0U) {
auto dataPtr = std::make_unique<std::complex<dd::fp>>(m.w);
auto* data = dataPtr.release();
auto* const data = dataPtr.get();
const nb::capsule owner(data, [](void* ptr) noexcept {
delete static_cast<std::complex<dd::fp>*>(ptr);
});
[[maybe_unused]] const auto* const releasedDataPtr = dataPtr.release();
return Matrix(data, {1, 1}, owner);
}

Expand All @@ -60,10 +60,11 @@ Matrix getMatrix(const dd::mEdge& m, const size_t numQubits,
dataPtr[(i * dim) + j] = c;
},
numQubits, threshold);
auto* data = dataPtr.release();
auto* const data = dataPtr.get();
const nb::capsule owner(data, [](void* ptr) noexcept {
delete[] static_cast<std::complex<dd::fp>*>(ptr);
});
[[maybe_unused]] const auto* const releasedDataPtr = dataPtr.release();
return Matrix(data, {dim, dim}, owner);
}

Expand Down
16 changes: 7 additions & 9 deletions bindings/dd/register_vector_dds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/vector.h> // NOLINT(misc-include-cleaner)

#include <algorithm>
#include <cmath>
#include <complex>
#include <cstddef>
#include <ios>
Expand All @@ -38,14 +36,14 @@ using Vector = nb::ndarray<nb::numpy, std::complex<dd::fp>, nb::ndim<1>>;

// NOLINTNEXTLINE(misc-use-internal-linkage)
Vector getVector(const dd::vEdge& v, const dd::fp threshold) {
auto vec = v.getVector(threshold);
auto dataPtr = std::make_unique<std::complex<dd::fp>[]>(vec.size());
std::ranges::copy(vec, dataPtr.get());
auto* data = dataPtr.release();
const nb::capsule owner(data, [](void* ptr) noexcept {
delete[] static_cast<std::complex<dd::fp>*>(ptr);
auto dataPtr = std::make_unique<dd::CVec>(v.getVector(threshold));
auto* const data = dataPtr->data();
const auto size = dataPtr->size();
const nb::capsule owner(dataPtr.get(), [](void* ptr) noexcept {
delete static_cast<dd::CVec*>(ptr);
});
return Vector(data, {vec.size()}, owner);
[[maybe_unused]] const auto* const releasedDataPtr = dataPtr.release();
return Vector(data, {size}, owner);
}

// NOLINTNEXTLINE(misc-use-internal-linkage)
Expand Down
4 changes: 2 additions & 2 deletions bindings/ir/operations/register_if_else_operation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Otherwise, the else operation is executed.
new (self) qc::IfElseOperation(std::move(thenPtr), std::move(elsePtr),
controlReg, expectedVal, kind);
},
"then_operation"_a, nb::arg("else_operation").none(true),
"then_operation"_a, nb::arg("else_operation").none(),
"control_register"_a, "expected_value"_a = 1U,
"comparison_kind"_a = qc::ComparisonKind::Eq);
ifElse.def(
Expand All @@ -81,7 +81,7 @@ Otherwise, the else operation is executed.
new (self) qc::IfElseOperation(std::move(thenPtr), std::move(elsePtr),
controlBit, expectedVal, kind);
},
"then_operation"_a, nb::arg("else_operation").none(true), "control_bit"_a,
"then_operation"_a, nb::arg("else_operation").none(), "control_bit"_a,
"expected_value"_a = true, "comparison_kind"_a = qc::ComparisonKind::Eq);

ifElse.def_prop_ro("then_operation", &qc::IfElseOperation::getThenOp,
Expand Down
29 changes: 27 additions & 2 deletions cmake/AddMQTPythonBinding.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ function(add_mqt_python_binding package_name target_name)
cmake_parse_arguments(ARG "" "MODULE_NAME;INSTALL_DIR" "LINK_LIBS" ${ARGN})
set(SOURCES ${ARG_UNPARSED_ARGUMENTS})

# nanobind 3.0 ignores scikit-build-core's abi3t signal on Windows. NB_ABI configures the nested
# nanobind_add_module call.
if(WIN32 AND Py_TARGET_ABI3T)
set(NB_ABI "${Python_VERSION_MAJOR}${Python_VERSION_MINOR}t")
endif()

nanobind_add_module(
# Name of the extension
${target_name}
# Target the stable ABI for Python 3.12+, which reduces the number of binary wheels
STABLE_ABI
# Enable free-threaded support
FREE_THREADED
# Suppress compiler warnings from the nanobind library
NB_SUPPRESS_WARNINGS
# Use nanobind's shared runtime. Split mode enables the Stable ABI internally.
BACKEND_MODULE
nanobind_backend
# Source files
${SOURCES})

Expand All @@ -30,10 +37,28 @@ function(add_mqt_python_binding package_name target_name)
set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${ARG_MODULE_NAME})
target_compile_definitions(${target_name}
PRIVATE MQT_${package_name}_MODULE_NAME=${ARG_MODULE_NAME})
set(module_name ${ARG_MODULE_NAME})
else()
# Use the target name as the module name
target_compile_definitions(${target_name}
PRIVATE MQT_${package_name}_MODULE_NAME=${target_name})
set(module_name ${target_name})
endif()

# Keep statically linked dependencies local.
if(APPLE)
target_link_options(${target_name} PRIVATE "LINKER:-exported_symbol,_PyInit_${module_name}")
elseif(UNIX)
target_link_options(${target_name} PRIVATE "LINKER:--exclude-libs,ALL")

# nanobind 3.0 omits section garbage collection from split-mode targets
target_link_options(
${target_name}
PRIVATE
"$<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:LINKER:--gc-sections>"
)
elseif(WIN32)
set_target_properties(${target_name} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS OFF)
endif()

# Add project libraries to the link libraries
Expand Down
2 changes: 1 addition & 1 deletion cmake/StandardProjectSettings.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ endif()
if(DEPLOY)
# set the macOS deployment target appropriately
set(CMAKE_OSX_DEPLOYMENT_TARGET
"11.0"
"13.3"
CACHE STRING "" FORCE)
endif()

Expand Down
1 change: 0 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ def format_url(self, _e: Entry) -> HRef: # ruff:ignore[no-self-use]
# they are not documented objects.
nitpick_ignore_regex = [
("py:class", r"Ellipsis"),
("py:class", r"mqt\.core\._compat\.typing\.Unpack"),
("py:class", r"ParametersType"),
("py:class", r"pennylane\.tape\.QuantumScriptOrBatch"),
("py:class", r"pennylane\.transforms\.core\.CompilePipeline"),
Expand Down
2 changes: 1 addition & 1 deletion eval/dd_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Licensed under the MIT License

# /// script
# requires-python = ">=3.10"
# requires-python = ">=3.11"
# dependencies = [
# "pandas[output-formatting]>=2.1.2",
# "pandas[output-formatting]>=2.2.3; python_version >= '3.13'",
Expand Down
11 changes: 7 additions & 4 deletions include/mqt-core/ir/Register.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "Definitions.hpp"

#include <atomic>
#include <cstddef>
#include <functional>
#include <stdexcept>
Expand Down Expand Up @@ -85,8 +86,9 @@ class QuantumRegister final : public Register<Qubit> {

protected:
static std::string generateName() {
static std::size_t counter = 0;
return "q" + std::to_string(counter++);
static std::atomic_size_t counter = 0;
return "q" +
std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
}
};

Expand All @@ -102,8 +104,9 @@ class ClassicalRegister final : public Register<Bit> {

protected:
static std::string generateName() {
static std::size_t counter = 0;
return "c" + std::to_string(counter++);
static std::atomic_size_t counter = 0;
return "c" +
std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
}
};

Expand Down
Loading
Loading