diff --git a/CHANGELOG.md b/CHANGELOG.md index 032c542e87..d1ecce5c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Return device handle from `add_dynamic_device_library` for direct backend creation ([#1381]) ([**@marcelwa**]) - ✨ Add IQM JSON support for job submission in Qiskit-QDMI Backend ([#1375]) ([**@marcelwa**]) - ✨ Add authentication support for QDMI sessions with token, username/password, auth file, auth URL, and project ID parameters ([#1355]) ([**@marcelwa**]) - ✨ Add a new QDMI device that represents a superconducting architecture featuring a coupling map ([#1328]) ([**@ystade**]) @@ -279,6 +280,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1381]: https://github.com/munich-quantum-toolkit/core/pull/1381 [#1375]: https://github.com/munich-quantum-toolkit/core/pull/1375 [#1371]: https://github.com/munich-quantum-toolkit/core/pull/1371 [#1359]: https://github.com/munich-quantum-toolkit/core/pull/1359 diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 03de2ca837..df98373c17 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -240,7 +240,8 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { const std::optional& custom2 = std::nullopt, const std::optional& custom3 = std::nullopt, const std::optional& custom4 = std::nullopt, - const std::optional& custom5 = std::nullopt) -> void { + const std::optional& custom5 = + std::nullopt) -> fomac::Session::Device { const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, .token = token, .authFile = authFile, @@ -252,8 +253,9 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .custom3 = custom3, .custom4 = custom4, .custom5 = custom5}; - qdmi::Driver::get().addDynamicDeviceLibrary(libraryPath, prefix, - config); + auto* const qdmiDevice = qdmi::Driver::get().addDynamicDeviceLibrary( + libraryPath, prefix, config); + return fomac::Session::Device::fromQDMIDevice(qdmiDevice); }, "library_path"_a, "prefix"_a, "base_url"_a = std::nullopt, "token"_a = std::nullopt, "auth_file"_a = std::nullopt, diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 2a99cf0f11..68fcb56660 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -612,6 +612,16 @@ class Session { * @param device The QDMI_Device handle to wrap. */ Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {} + /** + * @brief Creates a Device object from a QDMI_Device handle. + * @param device The QDMI_Device handle to wrap. + * @return A Device object wrapping the given handle. + * @note This is a factory method for use in bindings where Token + * construction is not accessible. + */ + [[nodiscard]] static auto fromQDMIDevice(QDMI_Device device) -> Device { + return Device(Session::Token{}, device); + } /// @returns the underlying QDMI_Device object. [[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; } // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 34fd17f124..7c477f0642 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -437,6 +437,8 @@ class Driver final { * library. * @param config Configuration for device session parameters. * + * @return A pointer to the newly created device. + * * @note This function is only available on non-Windows platforms. * * @throws std::runtime_error If the device cannot be initialized. @@ -444,7 +446,8 @@ class Driver final { */ auto addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix, - const DeviceSessionConfig& config = {}) -> void; + const DeviceSessionConfig& config = {}) + -> QDMI_Device; #endif // _WIN32 /** * @brief Allocates a new session. diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index e2d6ad8d93..4b23b8909a 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -297,7 +297,7 @@ if sys.platform != "win32": custom3: str | None = None, custom4: str | None = None, custom5: str | None = None, - ) -> None: + ) -> Device: """Load a dynamic device library into the QDMI driver. This function loads a shared library (.so, .dll, or .dylib) that implements @@ -320,6 +320,9 @@ if sys.platform != "win32": custom4: Optional custom configuration parameter 4. custom5: Optional custom configuration parameter 5. + Returns: + Device: The newly loaded device that can be used to create backends. + Raises: RuntimeError: If library loading fails or configuration is invalid. @@ -327,12 +330,12 @@ if sys.platform != "win32": Load a device library with configuration: >>> import mqt.core.fomac as fomac - >>> fomac.add_dynamic_device_library( + >>> device = fomac.add_dynamic_device_library( ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" ... ) - Now the device is available in sessions: + Now the device can be used directly: - >>> session = fomac.Session() - >>> devices = session.get_devices() + >>> from mqt.core.plugins.qiskit import QDMIBackend + >>> backend = QDMIBackend(device=device) """ diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index c0eadd10c0..f6d674337d 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -388,9 +388,10 @@ Driver::~Driver() { auto Driver::addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix, const DeviceSessionConfig& config) - -> void { + -> QDMI_Device { devices_.emplace_back(std::make_unique( std::make_unique(libName, prefix), config)); + return devices_.back().get(); } #endif diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index 1728f3b465..61c64b4f9c 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -705,6 +705,29 @@ TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { } } } + +TEST(DynamicDeviceLibraryTest, addDynamicDeviceLibraryReturnsDevice) { + // Test that addDynamicDeviceLibrary returns a valid device pointer + if constexpr (DYN_DEV_LIBS.empty()) { + GTEST_SKIP() << "No dynamic device libraries configured for testing."; + } + auto& driver = qdmi::Driver::get(); + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + const qdmi::DeviceSessionConfig config; + QDMI_Device device = nullptr; + ASSERT_NO_THROW( + { device = driver.addDynamicDeviceLibrary(lib, prefix, config); }); + ASSERT_NE(device, nullptr) + << "addDynamicDeviceLibrary should return a non-null device pointer"; + + // Verify the device is valid by querying its name + size_t size = 0; + EXPECT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &size), + QDMI_SUCCESS); + EXPECT_GT(size, 0) << "Device should have a non-empty name"; + } +} #endif // _WIN32 INSTANTIATE_TEST_SUITE_P(