From 27109f2bfd7a3ad7c637b17df4474f8f09682bd7 Mon Sep 17 00:00:00 2001 From: Lukas Burgholzer Date: Mon, 24 Aug 2026 19:45:37 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A5=20Adopt=20independent=20QDMI=20met?= =?UTF-8?q?adata=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove calibration-advisory and pulse metadata independently of driver replacement and program capabilities. Preserve calibration submission and pin the isolated QDMI cleanup for Core 4.1 development. Assisted-by: GPT-5.6 Sol via Codex --- .agent/plans/qdmi-v14-metadata-cleanup.md | 30 +++++++++++++++++++++++ CHANGELOG.md | 4 +++ UPGRADING.md | 10 ++++++++ bindings/qdmi/qdmi.cpp | 3 --- cmake/ExternalDependencies.cmake | 6 ++--- docs/Doxyfile | 2 +- docs/conf.py | 4 ++- docs/qir/index.md | 5 ---- include/mqt-core/qdmi/Client.hpp | 24 ++++++------------ include/mqt-core/qdmi/common/Common.hpp | 4 --- python/mqt/core/qdmi/__init__.pyi | 3 --- src/qdmi/Client.cpp | 5 ---- src/qdmi/devices/dd/Device.cpp | 7 ------ src/qdmi/devices/sc/Device.cpp | 5 ---- test/python/qdmi/test_qdmi.py | 7 ------ test/qdmi/driver/test_driver.cpp | 8 ------ test/qdmi/test_client.cpp | 6 ----- 17 files changed, 59 insertions(+), 74 deletions(-) create mode 100644 .agent/plans/qdmi-v14-metadata-cleanup.md diff --git a/.agent/plans/qdmi-v14-metadata-cleanup.md b/.agent/plans/qdmi-v14-metadata-cleanup.md new file mode 100644 index 0000000000..8524416144 --- /dev/null +++ b/.agent/plans/qdmi-v14-metadata-cleanup.md @@ -0,0 +1,30 @@ +# Adopt QDMI metadata removal + +Status: implemented and locally validated; hosted CI remains the merge gate. + +## Goal and scope + +Remove Core's calibration-advisory accessor and the bundled devices' obsolete +pulse metadata after QDMI PRs #512 and #513 remove these properties. This is +Core 4.1 work, independent of driver replacement, program capabilities, and +multi-program jobs. It must not enter the Core 4.0 release. + +## Decisions + +Keep calibration-job submission and the calibration status: these are not the +removed advisory. Keep the existing program-format enum and job interfaces. Pin +the independent QDMI cleanup while developing; replace that pin with a released +QDMI 1.4 before publishing artifacts. Clients and devices must use matching +headers because the regular property values change. + +The affected interfaces are in `include/mqt-core/qdmi/Client.hpp`, +`src/qdmi/Client.cpp`, and `bindings/qdmi/qdmi.cpp`. Bundled-device changes live +under `src/qdmi/devices/`. Remove only tests for the deleted API; preserve the +current optional-DDSIM build coverage and unrelated concurrency behavior. + +## Validation + +The release build and CTest passed (3,693 passed, one existing skip). All 249 +QDMI Python tests passed with both bundled devices enabled. Stub generation, +lint, and C++ lint passed. Keep both bundled devices enabled for the full Python +suite; the stub-generation environment deliberately disables the SC device. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2cc9f384..27015e16d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,9 @@ releases may include breaking changes. ### Removed +- 💥 Remove the C++ `Device::getNeedsCalibration` and Python + `Device.needs_calibration` APIs, and adopt QDMI's removal of the obsolete + calibration-readiness and pulse-support metadata ([#2233]) ([**@burgholzer**]) - 💥 Remove `qc::QuantumComputation`, `MQT::CoreIR`, and `MQT::CoreQASM`. Use compiler-backed QC/QCO APIs in v4 or the MQT Core v3 release series for legacy code ([#2111], [#2112], [#2288]) ([**@burgholzer**], [**@simon1hofmann**]) @@ -907,6 +910,7 @@ for previous changelogs._ [#2257]: https://github.com/munich-quantum-toolkit/core/pull/2257 [#2246]: https://github.com/munich-quantum-toolkit/core/pull/2246 [#2240]: https://github.com/munich-quantum-toolkit/core/pull/2240 +[#2233]: https://github.com/munich-quantum-toolkit/core/pull/2233 [#2232]: https://github.com/munich-quantum-toolkit/core/pull/2232 [#2228]: https://github.com/munich-quantum-toolkit/core/pull/2228 [#2224]: https://github.com/munich-quantum-toolkit/core/pull/2224 diff --git a/UPGRADING.md b/UPGRADING.md index 2d05eae44f..79c858a5eb 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -6,6 +6,16 @@ of changes including minor and patch releases, please refer to the ## [Unreleased] +### QDMI 1.4 metadata removal + +MQT Core removes `Device::getNeedsCalibration` and `Device.needs_calibration` +after QDMI removes its calibration advisory. Bundled devices no longer report +pulse-support metadata. The `CALIBRATION` status and calibration-job submission +remain available; this change does not alter program formats or job APIs. + +Rebuild clients, drivers, and devices against matching QDMI 1.4 headers because +the remaining regular device-property values are renumbered. + ### Removal of the classic circuit representation MQT Core 4 removes the complete classic circuit surface. This includes the C++ diff --git a/bindings/qdmi/qdmi.cpp b/bindings/qdmi/qdmi.cpp index 1339bd6b06..32c63d13ba 100644 --- a/bindings/qdmi/qdmi.cpp +++ b/bindings/qdmi/qdmi.cpp @@ -298,9 +298,6 @@ text, so the device must receive it as exact bytes. Pass ``bytes`` to device.def("coupling_map", &qdmi::Device::getCouplingMap, "Returns the coupling map of the device as a list of site pairs."); - device.def("needs_calibration", &qdmi::Device::getNeedsCalibration, - "Returns whether the device needs calibration."); - device.def("queue_length", &qdmi::Device::getQueueLength, "Returns the current queue length, or None if unavailable."); diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index a1d267880f..ecf7170644 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -67,11 +67,11 @@ if(BUILD_MQT_CORE_TESTS) endif() # cmake-format: off -set(QDMI_MINIMUM_VERSION 1.3.3 +set(QDMI_MINIMUM_VERSION 1.4.0 CACHE STRING "Minimum QDMI version") -set(QDMI_VERSION 1.3.3 +set(QDMI_VERSION 1.4.0 CACHE STRING "QDMI version") -set(QDMI_REV "18cfb67fd9042761d3005c2f8655751c1758f9c5" # v1.3.3 +set(QDMI_REV "3557d75d87e56f8b9c69b64feace6b49995433bb" # PR #513 CACHE STRING "QDMI identifier (tag, branch or commit hash)") set(QDMI_REPO_OWNER "Munich-Quantum-Software-Stack" CACHE STRING "QDMI repository owner (change when using a fork)") diff --git a/docs/Doxyfile b/docs/Doxyfile index e3ec51ebf9..8e5bbcb2c7 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -2101,7 +2101,7 @@ SKIP_FUNCTION_MACROS = YES # the path). If a tag file is not located in the directory in which Doxygen is # run, you must also specify the path to the tagfile here. -TAGFILES = _build/qdmi.tag=https://munich-quantum-software-stack.github.io/QDMI/v1.3.2/ +TAGFILES = _build/qdmi.tag=https://munich-quantum-software-stack.github.io/QDMI/pr-preview/pr-513/ # When a file name is specified after GENERATE_TAGFILE, Doxygen will create a # tag file that is based on the input files it reads. See section "Linking to diff --git a/docs/conf.py b/docs/conf.py index 8743d4ba7c..5e24b96a4b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -176,10 +176,12 @@ def format_url(self, _e: Entry) -> HRef: # ruff:ignore[no-self-use] cpp_api_tagfile = ("_build/doxygen/mqt-core.tag", "cpp/", "_build/doxygen/xml") +_qdmi_api_base = "https://munich-quantum-software-stack.github.io/QDMI/pr-preview/pr-513/" qdmi_api_tagfile = ( "_build/qdmi.tag", - "https://munich-quantum-software-stack.github.io/QDMI/v1.3.3/", + _qdmi_api_base, ) +qdmi_api_tagfile_url = f"{_qdmi_api_base}qdmi.tag" # -- Options for HTML output ------------------------------------------------- diff --git a/docs/qir/index.md b/docs/qir/index.md index b099eb800e..4054e7b309 100644 --- a/docs/qir/index.md +++ b/docs/qir/index.md @@ -109,8 +109,3 @@ to Base formats: the JIT stops the selected entry point immediately before the first call to a function marked `irreversible`, following the semantic boundary defined by the Base Profile. It rejects other profiles and Base Profile programs whose irreversible region is not terminal. - -The generic submission APIs intentionally reject QDMI calibration and batch-job -formats. Calibration jobs do not carry a program, while batch jobs contain job -handles rather than serialized program bytes. Their format identifiers remain -available for capability discovery; they require dedicated typed APIs. diff --git a/include/mqt-core/qdmi/Client.hpp b/include/mqt-core/qdmi/Client.hpp index 71fa9681f3..0d19cabc39 100644 --- a/include/mqt-core/qdmi/Client.hpp +++ b/include/mqt-core/qdmi/Client.hpp @@ -544,9 +544,6 @@ class Device { [[nodiscard]] std::optional>> getCouplingMap() const; - /// @see QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION - [[nodiscard]] std::optional getNeedsCalibration() const; - /// @see QDMI_DEVICE_PROPERTY_QUEUELENGTH [[nodiscard]] std::optional getQueueLength() const; @@ -668,19 +665,14 @@ class Device { const std::optional& custom4 = std::nullopt, const std::optional& custom5 = std::nullopt) const; - /** - * @brief Triggers a calibration run. - * @details A device that reports a nonzero - * `QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION` is asked to calibrate by submitting - * a job in the `QDMI_PROGRAM_FORMAT_CALIBRATION` format. QDMI does not - * require a program for such a job, so the payload is optional; when it is - * present, the device defines what it means, which is usually a - * configuration for the run. A calibration run executes no circuit, so no - * shot count is set. - * @param program The calibration payload. An empty span or `std::nullopt` - * means that the job has no payload. - * @see QDMI_job_submit - */ + /// Triggers a calibration run in the `QDMI_PROGRAM_FORMAT_CALIBRATION` + /// format. + /// + /// The payload is optional. When present, its meaning is device-specific. + /// A calibration run executes no circuit, so no shot count is set. + /// @param program The calibration payload. An empty span or `std::nullopt` + /// means that the job has no payload. + /// @see QDMI_job_submit [[nodiscard]] Job submitCalibrationJob( std::optional> program = std::nullopt, const std::optional& custom1 = std::nullopt, diff --git a/include/mqt-core/qdmi/common/Common.hpp b/include/mqt-core/qdmi/common/Common.hpp index d68eb8d275..22693b0ff8 100644 --- a/include/mqt-core/qdmi/common/Common.hpp +++ b/include/mqt-core/qdmi/common/Common.hpp @@ -372,8 +372,6 @@ constexpr auto toString(const QDMI_Device_Property prop) -> const char* { return "OPERATIONS"; case QDMI_DEVICE_PROPERTY_COUPLINGMAP: return "COUPLING MAP"; - case QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION: - return "NEEDS CALIBRATION"; case QDMI_DEVICE_PROPERTY_LENGTHUNIT: return "LENGTH UNIT"; case QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR: @@ -384,8 +382,6 @@ constexpr auto toString(const QDMI_Device_Property prop) -> const char* { return "DURATION SCALE FACTOR"; case QDMI_DEVICE_PROPERTY_MINATOMDISTANCE: return "MIN ATOM DISTANCE"; - case QDMI_DEVICE_PROPERTY_PULSESUPPORT: - return "PULSE SUPPORT"; case QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS: return "SUPPORTED PROGRAM FORMATS"; case QDMI_DEVICE_PROPERTY_CHILDDEVICES: diff --git a/python/mqt/core/qdmi/__init__.pyi b/python/mqt/core/qdmi/__init__.pyi index 04ad90bf02..b04cf97722 100644 --- a/python/mqt/core/qdmi/__init__.pyi +++ b/python/mqt/core/qdmi/__init__.pyi @@ -247,9 +247,6 @@ class Device: def coupling_map(self) -> list[tuple[Device.Site, Device.Site]] | None: """Returns the coupling map of the device as a list of site pairs.""" - def needs_calibration(self) -> int | None: - """Returns whether the device needs calibration.""" - def queue_length(self) -> int | None: """Returns the current queue length, or None if unavailable.""" diff --git a/src/qdmi/Client.cpp b/src/qdmi/Client.cpp index f960114667..b5533895b3 100644 --- a/src/qdmi/Client.cpp +++ b/src/qdmi/Client.cpp @@ -295,11 +295,6 @@ Device::getCouplingMap() const { return couplingMap; } -std::optional Device::getNeedsCalibration() const { - return queryProperty>( - QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION); -} - std::optional Device::getQueueLength() const { return queryProperty>(QDMI_DEVICE_PROPERTY_QUEUELENGTH); } diff --git a/src/qdmi/devices/dd/Device.cpp b/src/qdmi/devices/dd/Device.cpp index 16d61150b5..9398a96121 100644 --- a/src/qdmi/devices/dd/Device.cpp +++ b/src/qdmi/devices/dd/Device.cpp @@ -248,13 +248,6 @@ auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, status_.load(), prop, size, value, sizeRet) ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_QUBITSNUM, size_t, qubitsNum_, prop, size, value, sizeRet) - // This device never needs calibration - ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, size_t, 0, - prop, size, value, sizeRet) - // This device does not support pulse-level control - ADD_SINGLE_VALUE_PROPERTY( - QDMI_DEVICE_PROPERTY_PULSESUPPORT, QDMI_Device_Pulse_Support_Level, - QDMI_DEVICE_PULSE_SUPPORT_LEVEL_NONE, prop, size, value, sizeRet) // Expose default length and time units ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_LENGTHUNIT, "um", prop, size, value, sizeRet) diff --git a/src/qdmi/devices/sc/Device.cpp b/src/qdmi/devices/sc/Device.cpp index 3b1b01ea2b..2cf749c8ae 100644 --- a/src/qdmi/devices/sc/Device.cpp +++ b/src/qdmi/devices/sc/Device.cpp @@ -246,11 +246,6 @@ int MQT_SC_QDMI_Device_Session_impl_d::queryDeviceProperty( sizeRet) ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_QUBITSNUM, size_t, qubitsNum, property, size, value, sizeRet) - ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, size_t, 0, - property, size, value, sizeRet) - ADD_SINGLE_VALUE_PROPERTY( - QDMI_DEVICE_PROPERTY_PULSESUPPORT, QDMI_Device_Pulse_Support_Level, - QDMI_DEVICE_PULSE_SUPPORT_LEVEL_NONE, property, size, value, sizeRet) ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_DURATIONUNIT, durationUnit.c_str(), property, size, value, sizeRet) ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR, double, diff --git a/test/python/qdmi/test_qdmi.py b/test/python/qdmi/test_qdmi.py index 9fef03e6f5..192b8c7fa8 100644 --- a/test/python/qdmi/test_qdmi.py +++ b/test/python/qdmi/test_qdmi.py @@ -165,13 +165,6 @@ def test_device_coupling_map(device: Device) -> None: assert all(isinstance(site, Device.Site) for pair in cm for site in pair) -def test_device_needs_calibration(device: Device) -> None: - """Test that the device needs calibration is an integer.""" - needs_cal = device.needs_calibration() - if needs_cal is not None: - assert isinstance(needs_cal, int) - - def test_device_queue_length(device: Device) -> None: """Test that the optional device queue length is a non-negative integer.""" queue_length = device.queue_length() diff --git a/test/qdmi/driver/test_driver.cpp b/test/qdmi/driver/test_driver.cpp index cb14aecea3..91a0adab6a 100644 --- a/test/qdmi/driver/test_driver.cpp +++ b/test/qdmi/driver/test_driver.cpp @@ -830,14 +830,6 @@ TEST_P(DriverTest, QuerySessionProperty) { << "Device must return `INVALIDARGUMENT` if the buffer is too small."; } -TEST_P(DriverTest, QueryNeedsCalibration) { - size_t needsCalibration = 0; - const auto ret = QDMI_device_query_device_property( - device, QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, sizeof(size_t), - &needsCalibration, nullptr); - EXPECT_EQ(ret, QDMI_SUCCESS); - EXPECT_THAT(needsCalibration, testing::AnyOf(0, 1)); -} constexpr std::array DEVICES{ "MQT SC Default QDMI Device", #ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE diff --git a/test/qdmi/test_client.cpp b/test/qdmi/test_client.cpp index dc42231615..58850dac4c 100644 --- a/test/qdmi/test_client.cpp +++ b/test/qdmi/test_client.cpp @@ -371,8 +371,6 @@ TEST(QDMITest, DevicePropertyToString) { EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_OPERATIONS), "OPERATIONS"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_COUPLINGMAP), "COUPLING MAP"); - EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION), - "NEEDS CALIBRATION"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LENGTHUNIT), "LENGTH UNIT"); EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR), "LENGTH SCALE FACTOR"); @@ -512,10 +510,6 @@ TEST_P(DeviceTest, CouplingMap) { EXPECT_NO_THROW(std::ignore = device.getCouplingMap()); } -TEST_P(DeviceTest, NeedsCalibration) { - EXPECT_NO_THROW(std::ignore = device.getNeedsCalibration()); -} - #ifdef MQT_CORE_QDMI_HAS_DDSIM_DEVICE TEST_F(DDSimulatorDeviceTest, QueueLengthIsUnavailable) { EXPECT_EQ(device.getQueueLength(), std::nullopt);