Upgrade Guide

This document describes breaking changes and how to upgrade. For a complete list of changes including minor and patch releases, please refer to the changelog.

Unreleased

Removal of QDMI configuration through pyproject.toml

MQT Core no longer reads QDMI device definitions from a [tool.qdmi] table in pyproject.toml. Project discovery now looks only for qdmi.json. Move an existing table into a qdmi.json file beside the pyproject.toml. For example, replace this pyproject.toml table:

[tool.qdmi]
devices = [
  { id = "example.device", library = "libexample-device.so", prefix = "EXAMPLE" },
]

with this qdmi.json:

{
  "schema-version": 1,
  "qdmi": {
    "devices": [
      {
        "id": "example.device",
        "library": "libexample-device.so",
        "prefix": "EXAMPLE"
      }
    ]
  }
}

The JSON document adds the "schema-version": 1 key and nests the device array under qdmi. Every other key keeps its name and meaning. Relative paths still resolve against the file that declares them. MQT_CORE_QDMI_CONFIG_FILE, MQT_CORE_QDMI_CONFIG_JSON, the system and user files, and the packaged *.qdmi.json fragments do not change.

Python binding CMake helper

The add_mqt_python_binding_nanobind function is now called add_mqt_python_binding. Rename the calls in downstream CMakeLists.txt files:

add_mqt_python_binding(
  MYPACKAGE
  py_mypackage
  ${SOURCES}
  MODULE_NAME
  _core
  INSTALL_DIR
  .
  LINK_LIBS
  MQT::Core)

The former add_mqt_python_binding function built modules with pybind11. All MQT projects have switched from pybind11 to nanobind, so no build used that function. MQT Core no longer provides it.

CoreIR API cleanup

The CoreIR API cleanup requires the following migrations:

  • Replace getNmeasuredQubits() and num_measured_qubits with getNoutputQubits() and num_output_qubits, respectively.

  • Replace permutation-aware Operation::equals() and getUsedQubitsPermuted() calls by applying the permutation to cloned operations before comparing them.

  • Replace getHighestLogicalQubitIndex(), printStatistics(), and printPermutation() with initialLayout.maxValue(), the individual count accessors, and direct Permutation iteration, respectively.

  • Construct output-permutation measurements explicitly instead of calling appendMeasurementsAccordingToOutputPermutation().

The register lookup helpers getQubitRegister(), getPhysicalQubitIndex(), and physicalQubitIsAncillary() are now private implementation details.

Removal of the ZX-calculus library

MQT Core no longer provides the mqt-core-zx library, the MQT::CoreZX CMake target, the mqt-core/zx headers, or the global zx namespace. Remove these from downstream includes and link dependencies. Equivalence-checking users should use MQT QCEC; QCEC’s ZX implementation is internal and is not a replacement public API.

The MQT::Multiprecision target and the USE_SYSTEM_BOOST, MQT_CORE_WITH_GMP, and MQT_CORE_ZX_SYSTEM_BOOST CMake options have also been removed. MQT Core no longer discovers, fetches, or exports configuration for Boost.Multiprecision or GMP.

QDMI Python namespace

The Python QDMI API is available only in the QDMI namespace. Import QDMI entities such as Device, Job, and ProgramFormat from mqt.core.qdmi. Import functions and classes for device discovery, registration, and opening from mqt.core.qdmi.driver:

from mqt.core.qdmi.driver import open_device

device = open_device("mqt.ddsim.default")

MQT Core 4 removes mqt.core.fomac. Replace imports of QDMI entities with mqt.core.qdmi. Replace imports of registry functions and DeviceDefinition with mqt.core.qdmi.driver.

MQT Core 4 also removes mqt.core.qdmi.driver.Session. Use registered_device_ids() to discover devices and open_device() to open a fresh device session. Pass provider configuration overrides to open_device() when a device needs per-open configuration.

MQT Core 4 removes mqt.core.na.fomac. Import the neutral-atom specialization from mqt.core.na.qdmi.

MQT Core 4 also removes the FoMaC name from its C++ API. Apply these replacements:

  • fomac:: becomes qdmi::.

  • fomac/FoMaC.hpp becomes qdmi/Client.hpp.

  • fomac/Slurm.hpp becomes qdmi/Slurm.hpp.

  • na/fomac/Device.hpp becomes na/qdmi/Device.hpp.

  • MQT::CoreFoMaC becomes MQT::CoreQDMI.

  • MQT::CoreNAFoMaC becomes MQT::CoreNAQDMI.

  • mlir/Compiler/FoMaCAdapter.h and MQTCompilerFoMaCAdapter become mlir/Compiler/QDMIAdapter.h and MQTCompilerQDMIAdapter.

The class and function names do not change. For example:

#include "qdmi/Client.hpp"

auto device = qdmi::Session::openDevice("mqt.ddsim.default");

QDMI Qiskit primitive options

QDMISampler and QDMIEstimator no longer accept the MQT-specific options mapping. Pass shot and precision defaults directly, preferably through the backend factories:

sampler = backend.sampler(default_shots=2048)
estimator = backend.estimator(default_precision=0.01, default_shots=2048)

Replace QDMIEstimator(..., options={"default_shots": shots}) with QDMIEstimator(..., default_shots=shots). The sampler ignored its former options mapping, so remove that argument without replacement.

QIR runner

The QIR runner now invokes a selected QIR entry point as a parameterless i64 function instead of assuming an int main(int, char**). Use --entry-point to select among multiple entry points, --shots for repeated execution, and --seed for deterministic sampling.

Dynamic QIR inputs must use the current QIR 2.1 resource-management interface. Legacy qir-runner allocator and output overloads are no longer accepted.

DDSIM QDMI device

DDSIM now isolates the runtime, simulator state, random-number generator, and output sink of every QIR job, so concurrently submitted jobs no longer share execution state or write QIR records to process stdout.

QIR statevector extraction is supported only for Base-format jobs. The input must mark its first measurement boundary as irreversible, and no quantum work may follow that boundary. Adaptive-format statevector requests continue to return QDMI_ERROR_NOTSUPPORTED.

Runtime-configurable SC QDMI device

The built-in superconducting QDMI provider now parses its device description when each session is initialized. The mqt-core-qdmi-sc-device-gen target, SC-specific generator executable, sc::writeHeader, sc::writeJSONSchema, and generated DeviceMemberInitializers.hpp file have been removed. Replace generator API use with sc::Device and the sc::readJSON functions declared in qdmi/devices/sc/Configuration.hpp.

Runtime-configurable neutral-atom QDMI device

The built-in neutral-atom QDMI provider now parses its device description when each session is initialized. The mqt-core-qdmi-na-device-gen target, mqt-core-qdmi-na-device-generator executable, na::writeHeader, and generated DeviceMemberInitializers.hpp file have been removed. Replace generator API use with the na::Device configuration type and the na::readJSON functions in qdmi/devices/na/Configuration.hpp.

At runtime, use the registry session.device-config field or Python device_config and device_config_file arguments. Direct QDMI v1 clients pass inline JSON through CUSTOM1 or a file path through CUSTOM2.

Bundled QDMI devices in embedded builds

The bundled QDMI devices now have individual CMake options: BUILD_MQT_CORE_QDMI_DDSIM_DEVICE, BUILD_MQT_CORE_QDMI_NA_DEVICE, and BUILD_MQT_CORE_QDMI_SC_DEVICE. All three remain enabled by default in a standalone MQT Core build. They default to disabled when MQT Core is consumed through CMake’s FetchContent or add_subdirectory; embedded consumers can enable only the devices they need before making MQT Core available. The QDMI driver and FoMaC libraries remain available independently.

LLVM/MLIR required for all source builds

MQT Core now builds its MLIR-based compiler infrastructure unconditionally. LLVM 22.1+ (including MLIR) is therefore required when building MQT Core from source, including as a CMake dependency or Python package. The BUILD_MQT_CORE_MLIR CMake option has been removed. The QIR runner and QIR support in the DDSIM QDMI Device are also built unconditionally, so the BUILD_MQT_CORE_QIR_RUNNER and BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR options have been removed. Remove these three options from build scripts and presets.

We offer pre-built distributions for all supported platforms as part of the setup-mlir project at munich-quantum-software/setup-mlir. Please follow the instructions there to install the distribution for your platform. You can then point CMake to the installation directory using the -DMLIR_DIR=/path/to/mlir/installation/lib/cmake/mlir option.

For local development, you can configure MLIR_DIR once in a repository-local .env file (for example, MLIR_DIR=/path/to/installation/lib/cmake/mlir). MQT Core’s CMake setup will pick this up automatically when MLIR_DIR is not otherwise provided.

Known limitations:

  • Our pre-built distributions are incompatible with GCC on macOS. Use (Apple)Clang instead or compile LLVM from source using your preferred compiler.

  • AppleClang 17+ is required to build MQT Core due to some C++20 features that are not yet properly supported by older versions.

Removal of the density matrix support from the DD package

The density matrix support within the DD package has been removed. This change was made to reduce the maintenance burden of the package. Any libraries that depend on the density matrix functionality, such as MQT DDSIM, need to implement it on their own or use an alternative solution. In a related fashion, this PR also removes the noise operations from the MQT Core IR as they no longer serve a purpose.

Removal of the datastructures (sub)library

The datastructures (sub)library has been removed from the MQT Core repository. Its functionality has only ever been used in MQT QMAP since its inception. As a consequence, the code shall be moved to MQT QMAP once QMAP adopts an MQT Core version that includes this change.

Dev container

A dev container configuration is available to provide a consistent local development environment. Common IDEs like CLion and VS Code can open the repository directly inside the container. If you are on Windows, we recommend using Docker Desktop with the WSL 2 backend.

3.8.0

The shared library ABI version (SOVERSION) is increased from 3.7 to 3.8. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

QDMI updated to version 1.3.2

MQT Core already bundled QDMI 1.3.2 in the previous release, but now also requires at least that version when using a system-provided QDMI installation.

Bundled QDMI devices in embedded builds

The bundled QDMI devices now have individual CMake options: BUILD_MQT_CORE_QDMI_DDSIM_DEVICE, BUILD_MQT_CORE_QDMI_NA_DEVICE, and BUILD_MQT_CORE_QDMI_SC_DEVICE. All three remain enabled by default in a standalone MQT Core build. They default to disabled when MQT Core is consumed through CMake’s FetchContent or add_subdirectory; embedded consumers can enable only the devices they need before making MQT Core available. The QDMI driver and FoMaC libraries remain available independently.

QDMI runtime device registration

The unstable runtime-loading helpers have been replaced with registration by a stable device ID followed by an explicit open. In Python, replace add_dynamic_device_library(library_path, prefix, ...) with:

from mqt.core.fomac import DeviceDefinition, open_device, register_device

definition = DeviceDefinition("my.device", library_path, prefix, base_url="https://device.example")
register_device(definition)
device = open_device("my.device")

Per-backend session values can be passed directly to open_device("my.device", base_url=..., token=...). Every call creates a fresh device session without registering another device ID. Repeated integration setup can use register_device_if_absent(definition) instead of suppressing duplicate-ID errors; invalid definitions are still rejected, and a device disabled by higher-precedence configuration remains reserved.

The equivalent C++ flow is:

qdmi::DeviceDefinition definition{.id = "my.device",
                                  .library = libraryPath,
                                  .prefix = prefix};
auto& driver = qdmi::Driver::get();
driver.registerDevice(definition);
auto device = fomac::Session::openDevice("my.device");

Registration validates and stores metadata without loading native code. Opening an unknown or disabled ID fails. fomac::Session::openDevice creates a fresh owned session on every call. qdmi::Driver::open(id) retains its cached-device behavior for client callers.

See the QDMI device configuration guide for the versioned JSON and TOML formats, configuration precedence, and relocatable device manifests.

FoMaC program payload handling

FoMaC now distinguishes textual programs from exact binary payloads. In C++, use Device::submitJob(const std::string&, ...) for text formats and Device::submitJob(std::span<const std::byte>, ...) for binary formats. In Python, pass str for text and bytes for binary payloads. In particular, QIR *_STRING formats are text, while QIR *_MODULE formats are LLVM bitcode and must be submitted as bytes.

Job::getProgram() and Python’s Job.program remain the textual accessors and now reject binary or non-null-terminated payloads. Use Job::getProgramBytes() or Job.program_bytes to retrieve the exact submitted bytes. Calibration and batch-job formats cannot be submitted through these generic program APIs because their QDMI payloads have specialized representations.

QDMI child devices

The QDMI driver now translates device-library-specific QDMI_Child_Device handles into client-facing QDMI_Device handles backed by dedicated child sessions. Direct child devices can be queried through fomac::Device::getChildDevices() in C++ and Device.child_devices() in Python. Devices without child-device support continue to behave unchanged.

3.7.0

The shared library ABI version (SOVERSION) is increased from 3.6 to 3.7. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

nanobind updated to version 2.13.0

This release updates the nanobind dependency to version 2.13.0, which includes an ABI bump. Any existing code that uses the mqt-core Python bindings will need to be recompiled with the new nanobind version.

QDMI updated to version 1.3.2

While not a breaking change, this release updates the QDMI dependency to version 1.3.2

CMake presets

CMake presets have been added to provide a standardized and reproducible way to configure builds across different platforms. These presets are also used in our CI. They assume that MLIR_DIR is defined in your environment and pointing to an MLIR installation.

On Unix systems, the debug, release, and coverage presets can be used to configure, build, and test MQT Core.

cmake --preset release
cmake --build --preset release
ctest --preset release

Additionally, the lint preset can be used to configure and build MQT Core in preparation for a clang-tidy run.

If you are on Windows, use the debug-windows and release-windows presets.

3.6.0

The shared library ABI version (SOVERSION) is increased from 3.5 to 3.6. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

Changes to builtin QDMI devices

The builtin QDMI devices (with prefixes MQT_SC, MQT_NA, and MQT_DDSIM) are now all built as shared libraries by default. In turn, the shared library wrappers (with prefixes MQT_SC_DYN and MQT_NA_DYN) have been removed entirely. MQT Core’s QDMI driver will automatically load the shared libraries of the builtin devices if they are available in the library search path. If you were previously using the statically builtin devices, no changes should be necessary as the shared libraries are now the default. If you were previously using the shared library wrappers, you should switch to using the builtin devices instead, which are now shared libraries by default.

Broader operation support in QDMI Qiskit converter

The QDMI Qiskit converter now supports a broader range of operations, including multi-controlled gates such as mcx, mcz, mcrx, and more. As a consequence, these operations can now be directly used without requiring decomposition, for example, with the builtin DDSIM QDMI device.

Minimum supported Qiskit version

From this release onwards, MQT Core requires Qiskit version 1.1.0 or higher. This is due to the fact that we are relying on some fixes to Qiskit primitives that were introduced in that version. If you are using MQT Core with Qiskit, please ensure that you have updated to Qiskit 1.1.0 or higher to avoid any compatibility issues.

3.5.1

No breaking changes.

Component-based CMake installs

Fixed exported nlohmann_json CMake metadata so find_package(mqt-core CONFIG) no longer propagates an invalid .../COMPONENT include directory in component-based installations. Anyone relying on an installed version of mqt-core should update from 3.5.0 to 3.5.1.

3.5.0

The shared library ABI version (SOVERSION) is increased from 3.4 to 3.5. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

nanobind updated to version 2.12.0

This release updates the nanobind dependency to version 2.12.0, which includes an ABI bump. Any existing code that uses the mqt-core Python bindings will need to be recompiled with the new nanobind version.

3.4.0

The shared library ABI version (SOVERSION) is increased from 3.3 to 3.4. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

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.

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

QDMI-Qiskit integration

This release introduces a Qiskit BackendV2-compatible interface to QDMI devices. The mqt.core.plugins.qiskit module has been extended with QDMIProvider, QDMIBackend, and QDMIJob classes that allow running Qiskit circuits on QDMI-compliant devices.

Users can now execute Qiskit circuits directly on QDMI devices:

from mqt.core.plugins.qiskit import QDMIProvider

provider = QDMIProvider()
backend = provider.get_backend("MQT Core DDSIM QDMI Device")
job = backend.run(circuit, shots=1024)
result = job.result()

The backend automatically converts circuits to QASM, introspects device capabilities, validates circuits, and formats results. The existing FoMaC interface (mqt.core.fomac) remains fully supported for direct, low-level access to QDMI devices.

Install with Qiskit support: uv pip install "mqt-core[qiskit]"

See the Qiskit Backend documentation for details.

Argument name changes in QuantumComputation and CompoundOperation dunder methods

Since we enabled ty for type checking, it revealed that some of the dunder methods of QuantumComputation and CompoundOperation had incorrect argument names, which would prevent these classes from properly implementing the MutableSequence protocol. This release fixes these issues by renaming the arguments of the following methods:

  • QuantumComputation.__getitem__

  • QuantumComputation.__setitem__

  • QuantumComputation.__delitem__

  • QuantumComputation.insert

  • QuantumComputation.append

  • CompoundOperation.__getitem__

  • CompoundOperation.__setitem__

  • CompoundOperation.__delitem__

  • CompoundOperation.insert

  • CompoundOperation.append

All index arguments are now named index instead of idx (or i or slice) and all values are now named value instead of val (or op or ops).

DD Package evaluation

This release moves the DD Package evaluation functionality from within the mqt.core package to a dedicated script in the eval directory. In the process, the mqt-core-dd-compare entry point as well as the evaluation extra have been removed. The eval/dd_evaluation.py script acts as a drop-in replacement for the previous CLI entry point. Since the eval directory is not part of the Python package, this functionality is only available via source installations or by cloning the repository.

3.3.0

The shared library ABI version (SOVERSION) is increased from 3.2 to 3.3. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

IfElseOperation

This release introduces an IfElseOperation to the C++ library and the Python package to support Qiskit’s IfElseOp. The new operation replaces the ClassicControlledOperation.

An IfElseOperation can be added to a QuantumComputation using if_else().

qc.if_else(
    then_operation=StandardOperation(target=0, op_type=OpType.x),
    else_operation=StandardOperation(target=0, op_type=OpType.y),
    control_bit=0,
)

If no else operation is needed, the if_() method can be used.

qc.if_(op_type=OpType.x, target=0, control_bit=0)

End of support for Python 3.9

Starting with this release, MQT Core no longer supports Python 3.9. This is in line with the scheduled end of life of the version. As a result, MQT Core is no longer tested under Python 3.9 and no longer ships Python 3.9 wheels.

3.2.0

The shared library ABI version (SOVERSION) is increased from 3.1 to 3.2. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

With this release, the minimum required C++ version has been raised from C++17 to C++20. The default compilers of our test systems support all relevant features of the standard. Some frameworks we plan to integrate with even require C++20 by now.

The dd.BasisStates, ir.operations.ComparisonKind, ir.operations.Control.Type, and ir.operations.OpType enums are now exposed via pybind11’s new py::native_enum, which makes them compatible with Python’s enum.Enum class (PEP 435). As a result, the enums can no longer be initialized using a string. Instead of OpType("x"), use OpType.x.

3.1.0

The shared library ABI version (SOVERSION) is increased from 3.0 to 3.1. Thus, consuming libraries need to update their wheel repair configuration for cibuildwheel to ensure the mqt-core libraries are properly skipped in the wheel repair step.

Even though this is not a breaking change, it is worth mentioning to developers of MQT Core that all Python code (except tests) has been moved to the top-level python directory. Furthermore, the C++ code for the Python bindings has been moved to the top-level bindings directory.

DD Package

The makeZeroState, makeBasisState, makeGHZState, makeWState, and makeStateFromVector methods have been refactored to functions taking the DD package as an argument. These functions reside in the StateGeneration header. Any existing code that uses these methods must replace the respective calls with their function counterpart.

3.0.0

This major release introduces several breaking changes, including the removal of deprecated features and the introduction of new APIs. In preparation for this release, most direct dependents of MQT Core have been updated to use the new APIs. The following sections describe the most important changes and how to adapt your code accordingly. We intend to provide a more comprehensive migration guide for future releases.

Intermediate Representation (IR)

The OpenQASM parser has been encapsulated in its own library, which is now a dedicated target in the CMake build system. Any use of qc::QuantumComputation::import... needs to be replaced with the respective qasm3::Importer::load... function.

Several parsers have been removed, including the .real, .qc, .tfc, and GRCS parsers. The .real parser lives on as part of the MQT SyReC project. All others have been removed without replacement.

The Teleportation gate has been removed from the IR. This was a placeholder gate and was only used in a single method (in MQT QMAP), which is bound to be removed as part of MQT QMAP v3.0.0.

MQT QCEC, MQT QMAP, and MQT DDSIM have been updated to use the new API, which will be released in MQT QCEC v3.0.0, MQT QMAP v3.0.0 and MQT DDSIM v2.0.0.

DD Package

The DD package has undergone some initial refactoring to streamline the implementation and prepare it for future extensions. The Config template has been removed in favor of a constructor that takes the configuration as a parameter. Any existing code using dd::Package<...> needs to be updated to use dd::Package or dd::Package(numQubits, ...) instead. The MemoryManager and adjacent classes have been refactored to remove the template parameters. This should not have user-visible effects, but it is a breaking change nonetheless. Depending libraries may now also use the mqt-core Python package to interact with the DD package.

MQT QCEC and MQT DDSIM have been updated to use the new API, which will be released in MQT QCEC v3.0.0 and MQT DDSIM v2.0.0.

Neutral Atom Quantum Computing

The NAComputation class hierarchy has been refactored to use an MLIR-inspired design. This will act as a foundation for future extensions and improvements.

MQT QMAP has been updated to use the new API, which will be released in MQT QMAP v3.0.0.

General

MQT Core has moved to the munich-quantum-toolkit GitHub organization under https://github.com/munich-quantum-toolkit/core. While most links should be automatically redirected, please update any links in your code to point to the new location. All links in the documentation have been updated accordingly.

MQT Core now ships all its C++ libraries as shared libraries with the mqt-core Python package. Depending packages can now solely rely on the Python package for obtaining the C++ libraries. This is demonstrated in MQT QCEC v3.0.0, MQT QMAP v3.0.0 and MQT DDSIM v2.0.0, which will be released in the near future.

MQT Core now requires CMake 3.24 or higher. Most modern operating systems should have this version available in their package manager. Alternatively, CMake can be conveniently installed from PyPI using the cmake package.

It also requires the uv library version 0.5.20 or higher.