From 074c7ff3d1e4659e994857ccd12bd97527e49dec Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Wed, 3 Dec 2025 23:21:55 +0100 Subject: [PATCH 01/72] :sparkles: QDMI authentication via FoMaC --- bindings/fomac/fomac.cpp | 29 ++++++- include/mqt-core/fomac/FoMaC.hpp | 123 +++++++++++++++++++++++---- python/mqt/core/fomac.pyi | 106 ++++++++++++++++++++++- src/fomac/FoMaC.cpp | 140 +++++++++++++++++++++++++++++-- src/na/fomac/Device.cpp | 2 +- test/fomac/test_fomac.cpp | 123 ++++++++++++++++++++++++++- 6 files changed, 490 insertions(+), 33 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 1e97739e65..6bf8b4c9f3 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -26,6 +26,23 @@ namespace py = pybind11; using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { + // SessionParameter enum + py::native_enum( + m, "SessionParameter", "enum.Enum", + "Session parameters for authentication and configuration.") + .value("TOKEN", fomac::SessionParameter::TOKEN) + .value("AUTHFILE", fomac::SessionParameter::AUTHFILE) + .value("AUTHURL", fomac::SessionParameter::AUTHURL) + .value("USERNAME", fomac::SessionParameter::USERNAME) + .value("PASSWORD", fomac::SessionParameter::PASSWORD) + .value("PROJECTID", fomac::SessionParameter::PROJECTID) + .value("CUSTOM1", fomac::SessionParameter::CUSTOM1) + .value("CUSTOM2", fomac::SessionParameter::CUSTOM2) + .value("CUSTOM3", fomac::SessionParameter::CUSTOM3) + .value("CUSTOM4", fomac::SessionParameter::CUSTOM4) + .value("CUSTOM5", fomac::SessionParameter::CUSTOM5) + .finalize(); + // Job class auto job = py::class_(m, "Job"); job.def("check", &fomac::FoMaC::Job::check); @@ -183,7 +200,17 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { device.def(py::self == py::self); // NOLINT(misc-redundant-expression) device.def(py::self != py::self); // NOLINT(misc-redundant-expression) - m.def("devices", &fomac::FoMaC::getDevices); + // FoMaC class (exposed as Session in Python) + auto fomac = py::class_(m, "Session"); + fomac.def(py::init<>()); + fomac.def("set_session_parameter", &fomac::FoMaC::setSessionParameter, + "param"_a, "value"_a); + fomac.def("get_devices", &fomac::FoMaC::getDevices); + + // Module-level convenience functions (use default session) + m.def("set_session_parameter", &fomac::setSessionParameter, "param"_a, + "value"_a); + m.def("devices", &fomac::getDevices); } } // namespace mqt diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 55304bfdf3..f1673f34d1 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -18,11 +18,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -141,30 +143,64 @@ concept maybe_optional_value_or_string_or_vector = value_or_string_or_vector>; /// @returns the string representation of the given QDMI_STATUS. -auto toString(QDMI_STATUS result) -> std::string; +auto toString(const QDMI_STATUS result) -> std::string; /// @returns the string representation of the given QDMI_Site_Property. -auto toString(QDMI_Site_Property prop) -> std::string; +auto toString(const QDMI_Site_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Operation_Property. -auto toString(QDMI_Operation_Property prop) -> std::string; +auto toString(const QDMI_Operation_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Device_Property. -auto toString(QDMI_Device_Property prop) -> std::string; +auto toString(const QDMI_Device_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Session_Property. -constexpr auto toString(QDMI_Session_Property prop) -> std::string { +constexpr auto toString(const QDMI_Session_Property prop) -> std::string { if (prop == QDMI_SESSION_PROPERTY_DEVICES) { return "QDMI_SESSION_PROPERTY_DEVICES"; } return "QDMI_SESSION_PROPERTY_UNKNOWN"; } +/** + * @brief Session parameters for authentication and configuration. + * @details These parameters must be set before the session is initialized + * (i.e., before the first call to getDevices()). + * @see QDMI_SESSION_PARAMETER_T + */ +enum class SessionParameter { + /// Authentication token + TOKEN = QDMI_SESSION_PARAMETER_TOKEN, + /// Path to authentication file + AUTHFILE = QDMI_SESSION_PARAMETER_AUTHFILE, + /// URL to authentication server + AUTHURL = QDMI_SESSION_PARAMETER_AUTHURL, + /// Username for authentication + USERNAME = QDMI_SESSION_PARAMETER_USERNAME, + /// Password for authentication + PASSWORD = QDMI_SESSION_PARAMETER_PASSWORD, + /// Project ID for session + PROJECTID = QDMI_SESSION_PARAMETER_PROJECTID, + /// Custom parameter 1 (driver-defined) + CUSTOM1 = QDMI_SESSION_PARAMETER_CUSTOM1, + /// Custom parameter 2 (driver-defined) + CUSTOM2 = QDMI_SESSION_PARAMETER_CUSTOM2, + /// Custom parameter 3 (driver-defined) + CUSTOM3 = QDMI_SESSION_PARAMETER_CUSTOM3, + /// Custom parameter 4 (driver-defined) + CUSTOM4 = QDMI_SESSION_PARAMETER_CUSTOM4, + /// Custom parameter 5 (driver-defined) + CUSTOM5 = QDMI_SESSION_PARAMETER_CUSTOM5 +}; + +/// @returns the string representation of the given SessionParameter. +auto toString(const SessionParameter param) -> std::string; + /// Throws an exception corresponding to the given QDMI_STATUS code. -[[noreturn]] auto throwError(int result, const std::string& msg) -> void; +[[noreturn]] auto throwError(const int result, const std::string& msg) -> void; /// Throws an exception if the result indicates an error. -inline auto throwIfError(int result, const std::string& msg) -> void { +inline auto throwIfError(const int result, const std::string& msg) -> void { switch (result) { case QDMI_SUCCESS: break; @@ -180,7 +216,6 @@ inline auto throwIfError(int result, const std::string& msg) -> void { * @brief Class representing the FoMaC library. * @details This class provides methods to query available devices and * manage the QDMI session. - * @note This class is a singleton. * @see QDMI_Session */ class FoMaC { @@ -674,12 +709,13 @@ class FoMaC { private: QDMI_Session session_ = nullptr; + mutable std::mutex mutex_; + bool initialized_ = false; + std::unordered_map pendingParameters_; + + /// @brief Ensures the session is initialized, applying pending parameters + auto ensureInitialized() -> void; - FoMaC(); - static auto get() -> FoMaC& { - static FoMaC instance; - return instance; - } template [[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const -> T { @@ -696,14 +732,65 @@ class FoMaC { } public: + /** + * @brief Constructs a new FoMaC session. + * @details Creates and allocates a new QDMI session. The session is not + * initialized until the first call to getDevices() or after setting + * authentication parameters. + */ + FoMaC(); + + /** + * @brief Destructor that releases the QDMI session. + */ virtual ~FoMaC(); - // Delete copy constructors and assignment operators to prevent copying the - // singleton instance. + + // Delete copy constructors and assignment operators FoMaC(const FoMaC&) = delete; FoMaC& operator=(const FoMaC&) = delete; - FoMaC(FoMaC&&) = default; - FoMaC& operator=(FoMaC&&) = default; + + // Allow move semantics + FoMaC(FoMaC&&) noexcept; + FoMaC& operator=(FoMaC&&) noexcept; + + /** + * @brief Set a session parameter for authentication. + * @details This method must be called before the first call to getDevices(). + * Once the session is initialized, parameters cannot be changed. + * For AUTHFILE parameter, the file path is validated for existence. + * For AUTHURL parameter, basic URL format validation is performed. + * @param param The parameter to set + * @param value The value to set + * @throws std::runtime_error if the session is already initialized + * @throws std::runtime_error if validation fails (file not found, invalid + * URL) + * @see QDMI_session_set_parameter + */ + auto setSessionParameter(const SessionParameter param, + const std::string& value) -> void; + /// @see QDMI_SESSION_PROPERTY_DEVICES - [[nodiscard]] static auto getDevices() -> std::vector; + [[nodiscard]] auto getDevices() -> std::vector; }; + +/** + * @brief Get devices from a default FoMaC session. + * @details This is a convenience function that uses a static default FoMaC + * instance. For custom authentication or multiple sessions, create FoMaC + * instances directly. + * @return Vector of available devices + */ +[[nodiscard]] auto getDevices() -> std::vector; + +/** + * @brief Set a session parameter on the default FoMaC session. + * @details This is a convenience function for the default session. + * For multiple sessions, create FoMaC instances directly. + * @param param The parameter to set + * @param value The value to set + * @see FoMaC::setSessionParameter + */ +auto setSessionParameter(SessionParameter param, const std::string& value) + -> void; + } // namespace fomac diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index c81cda9415..57a9bcfa68 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -13,7 +13,10 @@ __all__ = [ "Device", "Job", "ProgramFormat", + "Session", + "SessionParameter", "devices", + "set_session_parameter", ] class ProgramFormat(Enum): @@ -34,6 +37,73 @@ class ProgramFormat(Enum): CUSTOM4 = ... CUSTOM5 = ... +class SessionParameter(Enum): + """Session parameters for authentication and configuration. + + These parameters must be set before the first call to devices(). + """ + + TOKEN = ... + """Authentication token""" + AUTHFILE = ... + """Path to authentication file""" + AUTHURL = ... + """URL to authentication server""" + USERNAME = ... + """Username for authentication""" + PASSWORD = ... + """Password for authentication""" + PROJECTID = ... + """Project ID for session""" + CUSTOM1 = ... + """Custom parameter 1 (driver-defined)""" + CUSTOM2 = ... + """Custom parameter 2 (driver-defined)""" + CUSTOM3 = ... + """Custom parameter 3 (driver-defined)""" + CUSTOM4 = ... + """Custom parameter 4 (driver-defined)""" + CUSTOM5 = ... + """Custom parameter 5 (driver-defined)""" + +class Session: + """A FoMaC session for managing QDMI devices. + + Allows creating isolated sessions with independent authentication settings. + """ + + def __init__(self) -> None: + """Create a new FoMaC session.""" + + def set_session_parameter(self, param: SessionParameter, value: str) -> None: + """Set a session parameter for authentication. + + This method must be called before the first call to get_devices(). + Once the session is initialized, parameters cannot be changed. + + Args: + param: The session parameter to set + value: The parameter value as a string + + Raises: + RuntimeError: If session is already initialized + RuntimeError: If AUTHFILE does not exist + RuntimeError: If AUTHURL has invalid format + + Example: + >>> from mqt.core.fomac import Session, SessionParameter + >>> session = Session() + >>> session.set_session_parameter(SessionParameter.TOKEN, "my_token") + >>> devices = session.get_devices() + """ + + def get_devices(self) -> list[Device]: + """Get available devices from this session. + + Returns: + List of available devices + """ + class Job: """A job represents a submitted quantum program execution.""" @@ -205,5 +275,39 @@ class Device: def __ne__(self, other: object) -> bool: """Checks if two devices are not equal.""" +def set_session_parameter(param: SessionParameter, value: str) -> None: + """Set a session parameter for authentication on the default session. + + This function must be called before the first call to devices(). + Once the session is initialized, parameters cannot be changed. + + For custom sessions or multiple sessions, use the Session class directly. + + Args: + param: The session parameter to set + value: The parameter value as a string + + Raises: + RuntimeError: If session is already initialized + RuntimeError: If AUTHFILE does not exist + RuntimeError: If AUTHURL has invalid format + + Example: + >>> import mqt.core.fomac as fomac + >>> fomac.set_session_parameter(fomac.SessionParameter.TOKEN, "my_token") + >>> devices = fomac.devices() + + # Or use Session class for isolated sessions: + >>> session = fomac.Session() + >>> session.set_session_parameter(fomac.SessionParameter.TOKEN, "token") + >>> session_devices = session.get_devices() + """ + def devices() -> list[Device]: - """Returns a list of available devices.""" + """Returns a list of available devices from the default session. + + For custom sessions, create a Session instance and use its get_devices() method. + + Returns: + List of available devices + """ diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 945884f0b0..5a0da94320 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -14,10 +14,13 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include #include @@ -73,7 +76,36 @@ auto toString(const QDMI_STATUS result) -> std::string { } unreachable(); } -auto toString(QDMI_Site_Property prop) -> std::string { + +auto toString(const SessionParameter param) -> std::string { + switch (param) { + case SessionParameter::TOKEN: + return "TOKEN"; + case SessionParameter::AUTHFILE: + return "AUTHFILE"; + case SessionParameter::AUTHURL: + return "AUTHURL"; + case SessionParameter::USERNAME: + return "USERNAME"; + case SessionParameter::PASSWORD: + return "PASSWORD"; + case SessionParameter::PROJECTID: + return "PROJECTID"; + case SessionParameter::CUSTOM1: + return "CUSTOM1"; + case SessionParameter::CUSTOM2: + return "CUSTOM2"; + case SessionParameter::CUSTOM3: + return "CUSTOM3"; + case SessionParameter::CUSTOM4: + return "CUSTOM4"; + case SessionParameter::CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +auto toString(const QDMI_Site_Property prop) -> std::string { switch (prop) { case QDMI_SITE_PROPERTY_INDEX: return "QDMI_SITE_PROPERTY_INDEX"; @@ -111,7 +143,7 @@ auto toString(QDMI_Site_Property prop) -> std::string { } unreachable(); } -auto toString(QDMI_Operation_Property prop) -> std::string { +auto toString(const QDMI_Operation_Property prop) -> std::string { switch (prop) { case QDMI_OPERATION_PROPERTY_NAME: return "QDMI_OPERATION_PROPERTY_NAME"; @@ -145,7 +177,7 @@ auto toString(QDMI_Operation_Property prop) -> std::string { } unreachable(); } -auto toString(QDMI_Device_Property prop) -> std::string { +auto toString(const QDMI_Device_Property prop) -> std::string { switch (prop) { case QDMI_DEVICE_PROPERTY_NAME: return "QDMI_DEVICE_PROPERTY_NAME"; @@ -189,7 +221,7 @@ auto toString(QDMI_Device_Property prop) -> std::string { } unreachable(); } -auto throwError(int result, const std::string& msg) -> void { +auto throwError(const int result, const std::string& msg) -> void { std::ostringstream ss; ss << msg << ": " << toString(static_cast(result)) << "."; switch (result) { @@ -778,12 +810,86 @@ auto FoMaC::Job::getSparseProbabilities() const FoMaC::FoMaC() { QDMI_session_alloc(&session_); - QDMI_session_init(session_); + // Initialization is deferred to ensureInitialized() } -FoMaC::~FoMaC() { QDMI_session_free(session_); } + +FoMaC::~FoMaC() { + if (session_ != nullptr) { + QDMI_session_free(session_); + } +} + +FoMaC::FoMaC(FoMaC&& other) noexcept + : session_(other.session_), initialized_(other.initialized_), + pendingParameters_(std::move(other.pendingParameters_)) { + other.session_ = nullptr; + other.initialized_ = false; +} + +FoMaC& FoMaC::operator=(FoMaC&& other) noexcept { + if (this != &other) { + if (session_ != nullptr) { + QDMI_session_free(session_); + } + session_ = other.session_; + initialized_ = other.initialized_; + pendingParameters_ = std::move(other.pendingParameters_); + other.session_ = nullptr; + other.initialized_ = false; + } + return *this; +} + +auto FoMaC::ensureInitialized() -> void { + std::lock_guard lock(mutex_); + if (initialized_) { + return; + } + + // Apply all pending parameters + for (const auto& [param, value] : pendingParameters_) { + const auto qdmiParam = static_cast(param); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + value.size() + 1, value.c_str()), + "Setting session parameter " + toString(param)); + } + + // Initialize the session + throwIfError(QDMI_session_init(session_), "Initializing session"); + initialized_ = true; +} + +auto FoMaC::setSessionParameter(const SessionParameter param, + const std::string& value) -> void { + std::lock_guard lock(mutex_); + + if (initialized_) { + throw std::runtime_error( + "Cannot set session parameter after session is initialized"); + } + + // Validate parameters + if (param == SessionParameter::AUTHFILE) { + if (!std::filesystem::exists(value)) { + throw std::runtime_error("Authentication file does not exist: " + value); + } + } else if (param == SessionParameter::AUTHURL) { + // Basic URL validation + static const std::regex urlPattern( + R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); + if (!std::regex_match(value, urlPattern)) { + throw std::runtime_error("Invalid URL format: " + value); + } + } + + pendingParameters_[param] = value; +} + auto FoMaC::getDevices() -> std::vector { - const auto& qdmiDevices = get().queryProperty>( - QDMI_SESSION_PROPERTY_DEVICES); + ensureInitialized(); + + const auto& qdmiDevices = + queryProperty>(QDMI_SESSION_PROPERTY_DEVICES); std::vector devices; devices.reserve(qdmiDevices.size()); std::ranges::transform( @@ -791,4 +897,22 @@ auto FoMaC::getDevices() -> std::vector { [](const QDMI_Device& dev) -> Device { return {Token{}, dev}; }); return devices; } + +// Module-level convenience functions for default session +namespace { +auto getDefaultSession() -> FoMaC& { + static FoMaC instance; + return instance; +} +} // namespace + +auto getDevices() -> std::vector { + return getDefaultSession().getDevices(); +} + +auto setSessionParameter(const SessionParameter param, const std::string& value) + -> void { + getDefaultSession().setSessionParameter(param, value); +} + } // namespace fomac diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index dd528f1330..17f4248957 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -577,7 +577,7 @@ FoMaC::Device::Device(const fomac::FoMaC::Device& device) : fomac::FoMaC::Device(device) {} auto FoMaC::getDevices() -> std::vector { std::vector devices; - for (const auto& d : fomac::FoMaC::getDevices()) { + for (const auto& d : fomac::getDevices()) { if (auto r = Device::tryCreateFromDevice(d); r.has_value()) { devices.emplace_back(r.value()); } diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 3a4ac9cad8..7447904dc3 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -11,7 +11,9 @@ #include "fomac/FoMaC.hpp" #include +#include #include +#include #include #include #include @@ -53,7 +55,7 @@ class DDSimulatorDeviceTest : public testing::Test { private: static auto getDDSimulatorDevice() -> FoMaC::Device { - for (const auto& dev : FoMaC::getDevices()) { + for (const auto& dev : fomac::getDevices()) { if (dev.getName() == "MQT Core DDSIM QDMI Device") { return dev; } @@ -596,13 +598,126 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { EXPECT_NEAR(it11->second, 0.5, 1e-10); } +TEST(AuthenticationTest, SessionParameterToString) { + EXPECT_EQ(toString(SessionParameter::TOKEN), "TOKEN"); + EXPECT_EQ(toString(SessionParameter::AUTHFILE), "AUTHFILE"); + EXPECT_EQ(toString(SessionParameter::AUTHURL), "AUTHURL"); + EXPECT_EQ(toString(SessionParameter::USERNAME), "USERNAME"); + EXPECT_EQ(toString(SessionParameter::PASSWORD), "PASSWORD"); + EXPECT_EQ(toString(SessionParameter::PROJECTID), "PROJECTID"); + EXPECT_EQ(toString(SessionParameter::CUSTOM1), "CUSTOM1"); + EXPECT_EQ(toString(SessionParameter::CUSTOM2), "CUSTOM2"); + EXPECT_EQ(toString(SessionParameter::CUSTOM3), "CUSTOM3"); + EXPECT_EQ(toString(SessionParameter::CUSTOM4), "CUSTOM4"); + EXPECT_EQ(toString(SessionParameter::CUSTOM5), "CUSTOM5"); +} + +TEST(AuthenticationTest, ValidURLAccepted) { + FoMaC session; + EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::AUTHURL, + "https://example.com")); + EXPECT_NO_THROW(session.setSessionParameter( + SessionParameter::AUTHURL, "http://auth.server.com:8080/api")); + EXPECT_NO_THROW(session.setSessionParameter( + SessionParameter::AUTHURL, "https://auth.example.com/token?param=value")); +} + +TEST(AuthenticationTest, InvalidURLRejected) { + FoMaC session; + EXPECT_THROW( + session.setSessionParameter(SessionParameter::AUTHURL, "not-a-url"), + std::runtime_error); + EXPECT_THROW(session.setSessionParameter(SessionParameter::AUTHURL, + "ftp://invalid.com"), + std::runtime_error); + EXPECT_THROW( + session.setSessionParameter(SessionParameter::AUTHURL, "example.com"), + std::runtime_error); +} + +TEST(AuthenticationTest, NonexistentFileRejected) { + FoMaC session; + EXPECT_THROW(session.setSessionParameter(SessionParameter::AUTHFILE, + "/nonexistent/path/to/file.txt"), + std::runtime_error); + EXPECT_THROW( + session.setSessionParameter(SessionParameter::AUTHFILE, + "/tmp/this_file_does_not_exist_12345.txt"), + std::runtime_error); +} + +TEST(AuthenticationTest, ExistingFileAccepted) { + FoMaC session; + // Create a temporary file for testing + const char* tmpFile = std::tmpnam(nullptr); + { + std::ofstream ofs(tmpFile); + ofs << "test_token_content"; + } + + // Existing file should be accepted + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::AUTHFILE, tmpFile)); + + // Clean up + std::remove(tmpFile); +} + +TEST(AuthenticationTest, TokenParameterAccepted) { + FoMaC session; + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::TOKEN, "my_token_123")); + EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::TOKEN, "")); + EXPECT_NO_THROW(session.setSessionParameter( + SessionParameter::TOKEN, + "very_long_token_with_special_characters_!@#$%^&*()")); +} + +TEST(AuthenticationTest, UsernameAndPasswordParametersAccepted) { + FoMaC session; + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::USERNAME, "user123")); + EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::PASSWORD, + "secure_password")); +} + +TEST(AuthenticationTest, ProjectIDParameterAccepted) { + FoMaC session; + EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::PROJECTID, + "project-123-abc")); +} + +TEST(AuthenticationTest, CustomParametersAccepted) { + FoMaC session; + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::CUSTOM1, "custom_value_1")); + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::CUSTOM2, "custom_value_2")); + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::CUSTOM3, "custom_value_3")); + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::CUSTOM4, "custom_value_4")); + EXPECT_NO_THROW( + session.setSessionParameter(SessionParameter::CUSTOM5, "custom_value_5")); +} + +TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { + FoMaC session; + auto devices = session.getDevices(); + EXPECT_FALSE(devices.empty()); + + // Try to set a parameter - should fail + EXPECT_THROW(session.setSessionParameter(SessionParameter::TOKEN, "token"), + std::runtime_error); +} + INSTANTIATE_TEST_SUITE_P( // Custom instantiation name DeviceTest, // Test suite name DeviceTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), + testing::ValuesIn(fomac::getDevices()), [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names @@ -616,7 +731,7 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name SiteTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), + testing::ValuesIn(fomac::getDevices()), [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names @@ -630,7 +745,7 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name OperationTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), + testing::ValuesIn(fomac::getDevices()), [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names From f1d43606616bffd3914bbb50e9ed86df7402712d Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Wed, 3 Dec 2025 23:48:25 +0100 Subject: [PATCH 02/72] :rotating_light: Fix `clang-tidy` warnings --- src/fomac/FoMaC.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 5a0da94320..ca15e2930c 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -861,7 +861,7 @@ auto FoMaC::ensureInitialized() -> void { auto FoMaC::setSessionParameter(const SessionParameter param, const std::string& value) -> void { - std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (initialized_) { throw std::runtime_error( @@ -875,9 +875,9 @@ auto FoMaC::setSessionParameter(const SessionParameter param, } } else if (param == SessionParameter::AUTHURL) { // Basic URL validation - static const std::regex urlPattern( + static const std::regex URL_PATTERN( R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); - if (!std::regex_match(value, urlPattern)) { + if (!std::regex_match(value, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + value); } } From c184cc3840af934861c326d6e47ddd84c871fff6 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 13:56:37 +0100 Subject: [PATCH 03/72] :rotating_light: Fix `clang-tidy` warnings --- src/fomac/FoMaC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index ca15e2930c..6d12fb1748 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -841,7 +841,7 @@ FoMaC& FoMaC::operator=(FoMaC&& other) noexcept { } auto FoMaC::ensureInitialized() -> void { - std::lock_guard lock(mutex_); + const std::scoped_lock lock(mutex_); if (initialized_) { return; } @@ -861,7 +861,7 @@ auto FoMaC::ensureInitialized() -> void { auto FoMaC::setSessionParameter(const SessionParameter param, const std::string& value) -> void { - const std::scoped_lock lock(mutex_); + const std::scoped_lock lock(mutex_); if (initialized_) { throw std::runtime_error( From 9d17fe3669b86d5c230309c1c155c4868ff8e3f2 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 14:15:20 +0100 Subject: [PATCH 04/72] :recycle: Don't re-expose session parameter enum --- bindings/fomac/fomac.cpp | 50 ++++++++-------- include/mqt-core/fomac/FoMaC.hpp | 43 ++------------ src/fomac/FoMaC.cpp | 34 +++++------ test/fomac/test_fomac.cpp | 97 +++++++++++++++++--------------- 4 files changed, 101 insertions(+), 123 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 6bf8b4c9f3..f13cc1d086 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -26,21 +26,33 @@ namespace py = pybind11; using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { + // FoMaC class (exposed as Session in Python) + auto fomac = py::class_(m, "Session"); + fomac.def(py::init<>()); + fomac.def("set_session_parameter", &fomac::FoMaC::setSessionParameter, + "param"_a, "value"_a); + fomac.def("get_devices", &fomac::FoMaC::getDevices); + + // Module-level convenience functions (use default session) + m.def("set_session_parameter", &fomac::setSessionParameter, "param"_a, + "value"_a); + m.def("devices", &fomac::getDevices); + // SessionParameter enum - py::native_enum( - m, "SessionParameter", "enum.Enum", + py::native_enum( + fomac, "SessionParameter", "enum.Enum", "Session parameters for authentication and configuration.") - .value("TOKEN", fomac::SessionParameter::TOKEN) - .value("AUTHFILE", fomac::SessionParameter::AUTHFILE) - .value("AUTHURL", fomac::SessionParameter::AUTHURL) - .value("USERNAME", fomac::SessionParameter::USERNAME) - .value("PASSWORD", fomac::SessionParameter::PASSWORD) - .value("PROJECTID", fomac::SessionParameter::PROJECTID) - .value("CUSTOM1", fomac::SessionParameter::CUSTOM1) - .value("CUSTOM2", fomac::SessionParameter::CUSTOM2) - .value("CUSTOM3", fomac::SessionParameter::CUSTOM3) - .value("CUSTOM4", fomac::SessionParameter::CUSTOM4) - .value("CUSTOM5", fomac::SessionParameter::CUSTOM5) + .value("TOKEN", QDMI_SESSION_PARAMETER_TOKEN) + .value("AUTHFILE", QDMI_SESSION_PARAMETER_AUTHFILE) + .value("AUTHURL", QDMI_SESSION_PARAMETER_AUTHURL) + .value("USERNAME", QDMI_SESSION_PARAMETER_USERNAME) + .value("PASSWORD", QDMI_SESSION_PARAMETER_PASSWORD) + .value("PROJECTID", QDMI_SESSION_PARAMETER_PROJECTID) + .value("CUSTOM1", QDMI_SESSION_PARAMETER_CUSTOM1) + .value("CUSTOM2", QDMI_SESSION_PARAMETER_CUSTOM2) + .value("CUSTOM3", QDMI_SESSION_PARAMETER_CUSTOM3) + .value("CUSTOM4", QDMI_SESSION_PARAMETER_CUSTOM4) + .value("CUSTOM5", QDMI_SESSION_PARAMETER_CUSTOM5) .finalize(); // Job class @@ -199,18 +211,6 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { }); device.def(py::self == py::self); // NOLINT(misc-redundant-expression) device.def(py::self != py::self); // NOLINT(misc-redundant-expression) - - // FoMaC class (exposed as Session in Python) - auto fomac = py::class_(m, "Session"); - fomac.def(py::init<>()); - fomac.def("set_session_parameter", &fomac::FoMaC::setSessionParameter, - "param"_a, "value"_a); - fomac.def("get_devices", &fomac::FoMaC::getDevices); - - // Module-level convenience functions (use default session) - m.def("set_session_parameter", &fomac::setSessionParameter, "param"_a, - "value"_a); - m.def("devices", &fomac::getDevices); } } // namespace mqt diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index f1673f34d1..ee55996bd9 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -162,39 +162,8 @@ constexpr auto toString(const QDMI_Session_Property prop) -> std::string { return "QDMI_SESSION_PROPERTY_UNKNOWN"; } -/** - * @brief Session parameters for authentication and configuration. - * @details These parameters must be set before the session is initialized - * (i.e., before the first call to getDevices()). - * @see QDMI_SESSION_PARAMETER_T - */ -enum class SessionParameter { - /// Authentication token - TOKEN = QDMI_SESSION_PARAMETER_TOKEN, - /// Path to authentication file - AUTHFILE = QDMI_SESSION_PARAMETER_AUTHFILE, - /// URL to authentication server - AUTHURL = QDMI_SESSION_PARAMETER_AUTHURL, - /// Username for authentication - USERNAME = QDMI_SESSION_PARAMETER_USERNAME, - /// Password for authentication - PASSWORD = QDMI_SESSION_PARAMETER_PASSWORD, - /// Project ID for session - PROJECTID = QDMI_SESSION_PARAMETER_PROJECTID, - /// Custom parameter 1 (driver-defined) - CUSTOM1 = QDMI_SESSION_PARAMETER_CUSTOM1, - /// Custom parameter 2 (driver-defined) - CUSTOM2 = QDMI_SESSION_PARAMETER_CUSTOM2, - /// Custom parameter 3 (driver-defined) - CUSTOM3 = QDMI_SESSION_PARAMETER_CUSTOM3, - /// Custom parameter 4 (driver-defined) - CUSTOM4 = QDMI_SESSION_PARAMETER_CUSTOM4, - /// Custom parameter 5 (driver-defined) - CUSTOM5 = QDMI_SESSION_PARAMETER_CUSTOM5 -}; - -/// @returns the string representation of the given SessionParameter. -auto toString(const SessionParameter param) -> std::string; +/// @returns the string representation of the given QDMI_SESSION_PARAMETER_T. +auto toString(const QDMI_SESSION_PARAMETER_T param) -> std::string; /// Throws an exception corresponding to the given QDMI_STATUS code. [[noreturn]] auto throwError(const int result, const std::string& msg) -> void; @@ -711,7 +680,7 @@ class FoMaC { QDMI_Session session_ = nullptr; mutable std::mutex mutex_; bool initialized_ = false; - std::unordered_map pendingParameters_; + std::unordered_map pendingParameters_; /// @brief Ensures the session is initialized, applying pending parameters auto ensureInitialized() -> void; @@ -766,7 +735,7 @@ class FoMaC { * URL) * @see QDMI_session_set_parameter */ - auto setSessionParameter(const SessionParameter param, + auto setSessionParameter(const QDMI_SESSION_PARAMETER_T param, const std::string& value) -> void; /// @see QDMI_SESSION_PROPERTY_DEVICES @@ -790,7 +759,7 @@ class FoMaC { * @param value The value to set * @see FoMaC::setSessionParameter */ -auto setSessionParameter(SessionParameter param, const std::string& value) - -> void; +auto setSessionParameter(QDMI_SESSION_PARAMETER_T param, + const std::string& value) -> void; } // namespace fomac diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 6d12fb1748..08c3fad2be 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -77,29 +77,29 @@ auto toString(const QDMI_STATUS result) -> std::string { unreachable(); } -auto toString(const SessionParameter param) -> std::string { +auto toString(const QDMI_SESSION_PARAMETER_T param) -> std::string { switch (param) { - case SessionParameter::TOKEN: + case QDMI_SESSION_PARAMETER_TOKEN: return "TOKEN"; - case SessionParameter::AUTHFILE: + case QDMI_SESSION_PARAMETER_AUTHFILE: return "AUTHFILE"; - case SessionParameter::AUTHURL: + case QDMI_SESSION_PARAMETER_AUTHURL: return "AUTHURL"; - case SessionParameter::USERNAME: + case QDMI_SESSION_PARAMETER_USERNAME: return "USERNAME"; - case SessionParameter::PASSWORD: + case QDMI_SESSION_PARAMETER_PASSWORD: return "PASSWORD"; - case SessionParameter::PROJECTID: + case QDMI_SESSION_PARAMETER_PROJECTID: return "PROJECTID"; - case SessionParameter::CUSTOM1: + case QDMI_SESSION_PARAMETER_CUSTOM1: return "CUSTOM1"; - case SessionParameter::CUSTOM2: + case QDMI_SESSION_PARAMETER_CUSTOM2: return "CUSTOM2"; - case SessionParameter::CUSTOM3: + case QDMI_SESSION_PARAMETER_CUSTOM3: return "CUSTOM3"; - case SessionParameter::CUSTOM4: + case QDMI_SESSION_PARAMETER_CUSTOM4: return "CUSTOM4"; - case SessionParameter::CUSTOM5: + case QDMI_SESSION_PARAMETER_CUSTOM5: return "CUSTOM5"; } unreachable(); @@ -859,7 +859,7 @@ auto FoMaC::ensureInitialized() -> void { initialized_ = true; } -auto FoMaC::setSessionParameter(const SessionParameter param, +auto FoMaC::setSessionParameter(const QDMI_SESSION_PARAMETER_T param, const std::string& value) -> void { const std::scoped_lock lock(mutex_); @@ -869,11 +869,11 @@ auto FoMaC::setSessionParameter(const SessionParameter param, } // Validate parameters - if (param == SessionParameter::AUTHFILE) { + if (param == QDMI_SESSION_PARAMETER_AUTHFILE) { if (!std::filesystem::exists(value)) { throw std::runtime_error("Authentication file does not exist: " + value); } - } else if (param == SessionParameter::AUTHURL) { + } else if (param == QDMI_SESSION_PARAMETER_AUTHURL) { // Basic URL validation static const std::regex URL_PATTERN( R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); @@ -910,8 +910,8 @@ auto getDevices() -> std::vector { return getDefaultSession().getDevices(); } -auto setSessionParameter(const SessionParameter param, const std::string& value) - -> void { +auto setSessionParameter(const QDMI_SESSION_PARAMETER_T param, + const std::string& value) -> void { getDefaultSession().setSessionParameter(param, value); } diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 7447904dc3..351127e004 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include #include @@ -599,49 +601,50 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { } TEST(AuthenticationTest, SessionParameterToString) { - EXPECT_EQ(toString(SessionParameter::TOKEN), "TOKEN"); - EXPECT_EQ(toString(SessionParameter::AUTHFILE), "AUTHFILE"); - EXPECT_EQ(toString(SessionParameter::AUTHURL), "AUTHURL"); - EXPECT_EQ(toString(SessionParameter::USERNAME), "USERNAME"); - EXPECT_EQ(toString(SessionParameter::PASSWORD), "PASSWORD"); - EXPECT_EQ(toString(SessionParameter::PROJECTID), "PROJECTID"); - EXPECT_EQ(toString(SessionParameter::CUSTOM1), "CUSTOM1"); - EXPECT_EQ(toString(SessionParameter::CUSTOM2), "CUSTOM2"); - EXPECT_EQ(toString(SessionParameter::CUSTOM3), "CUSTOM3"); - EXPECT_EQ(toString(SessionParameter::CUSTOM4), "CUSTOM4"); - EXPECT_EQ(toString(SessionParameter::CUSTOM5), "CUSTOM5"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTHFILE"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTHURL"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECTID"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } TEST(AuthenticationTest, ValidURLAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::AUTHURL, + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, "https://example.com")); EXPECT_NO_THROW(session.setSessionParameter( - SessionParameter::AUTHURL, "http://auth.server.com:8080/api")); + QDMI_SESSION_PARAMETER_AUTHURL, "http://auth.server.com:8080/api")); EXPECT_NO_THROW(session.setSessionParameter( - SessionParameter::AUTHURL, "https://auth.example.com/token?param=value")); + QDMI_SESSION_PARAMETER_AUTHURL, + "https://auth.example.com/token?param=value")); } TEST(AuthenticationTest, InvalidURLRejected) { FoMaC session; EXPECT_THROW( - session.setSessionParameter(SessionParameter::AUTHURL, "not-a-url"), + session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, "not-a-url"), std::runtime_error); - EXPECT_THROW(session.setSessionParameter(SessionParameter::AUTHURL, + EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, "ftp://invalid.com"), std::runtime_error); - EXPECT_THROW( - session.setSessionParameter(SessionParameter::AUTHURL, "example.com"), - std::runtime_error); + EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, + "example.com"), + std::runtime_error); } TEST(AuthenticationTest, NonexistentFileRejected) { FoMaC session; - EXPECT_THROW(session.setSessionParameter(SessionParameter::AUTHFILE, + EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, "/nonexistent/path/to/file.txt"), std::runtime_error); EXPECT_THROW( - session.setSessionParameter(SessionParameter::AUTHFILE, + session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, "/tmp/this_file_does_not_exist_12345.txt"), std::runtime_error); } @@ -649,56 +652,61 @@ TEST(AuthenticationTest, NonexistentFileRejected) { TEST(AuthenticationTest, ExistingFileAccepted) { FoMaC session; // Create a temporary file for testing - const char* tmpFile = std::tmpnam(nullptr); + const auto tmpPath = std::filesystem::temp_directory_path() / + ("test_fomac_auth_" + std::to_string( + std::hash{}( + std::this_thread::get_id())) + + ".txt"); { - std::ofstream ofs(tmpFile); + std::ofstream ofs(tmpPath); ofs << "test_token_content"; } // Existing file should be accepted - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::AUTHFILE, tmpFile)); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, + tmpPath.string())); // Clean up - std::remove(tmpFile); + std::filesystem::remove(tmpPath); } TEST(AuthenticationTest, TokenParameterAccepted) { FoMaC session; + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, + "my_token_123")); EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::TOKEN, "my_token_123")); - EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::TOKEN, "")); + session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, "")); EXPECT_NO_THROW(session.setSessionParameter( - SessionParameter::TOKEN, + QDMI_SESSION_PARAMETER_TOKEN, "very_long_token_with_special_characters_!@#$%^&*()")); } TEST(AuthenticationTest, UsernameAndPasswordParametersAccepted) { FoMaC session; EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::USERNAME, "user123")); - EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::PASSWORD, + session.setSessionParameter(QDMI_SESSION_PARAMETER_USERNAME, "user123")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_PASSWORD, "secure_password")); } TEST(AuthenticationTest, ProjectIDParameterAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(SessionParameter::PROJECTID, + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_PROJECTID, "project-123-abc")); } TEST(AuthenticationTest, CustomParametersAccepted) { FoMaC session; - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::CUSTOM1, "custom_value_1")); - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::CUSTOM2, "custom_value_2")); - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::CUSTOM3, "custom_value_3")); - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::CUSTOM4, "custom_value_4")); - EXPECT_NO_THROW( - session.setSessionParameter(SessionParameter::CUSTOM5, "custom_value_5")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM1, + "custom_value_1")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM2, + "custom_value_2")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM3, + "custom_value_3")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM4, + "custom_value_4")); + EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM5, + "custom_value_5")); } TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { @@ -707,8 +715,9 @@ TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { EXPECT_FALSE(devices.empty()); // Try to set a parameter - should fail - EXPECT_THROW(session.setSessionParameter(SessionParameter::TOKEN, "token"), - std::runtime_error); + EXPECT_THROW( + session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, "token"), + std::runtime_error); } INSTANTIATE_TEST_SUITE_P( From 97a863cba45339d16524700858e737176beca9b3 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 14:19:44 +0100 Subject: [PATCH 05/72] :recycle: Rename `setSessionParameter` to `setParameter` --- bindings/fomac/fomac.cpp | 6 +-- include/mqt-core/fomac/FoMaC.hpp | 10 ++-- src/fomac/FoMaC.cpp | 10 ++-- test/fomac/test_fomac.cpp | 91 +++++++++++++++----------------- 4 files changed, 56 insertions(+), 61 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index f13cc1d086..3ac1ff69d6 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -29,13 +29,11 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { // FoMaC class (exposed as Session in Python) auto fomac = py::class_(m, "Session"); fomac.def(py::init<>()); - fomac.def("set_session_parameter", &fomac::FoMaC::setSessionParameter, - "param"_a, "value"_a); + fomac.def("set_parameter", &fomac::FoMaC::setParameter, "param"_a, "value"_a); fomac.def("get_devices", &fomac::FoMaC::getDevices); // Module-level convenience functions (use default session) - m.def("set_session_parameter", &fomac::setSessionParameter, "param"_a, - "value"_a); + m.def("set_parameter", &fomac::setParameter, "param"_a, "value"_a); m.def("devices", &fomac::getDevices); // SessionParameter enum diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index ee55996bd9..609974e53b 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -735,8 +735,8 @@ class FoMaC { * URL) * @see QDMI_session_set_parameter */ - auto setSessionParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void; + auto setParameter(const QDMI_SESSION_PARAMETER_T param, + const std::string& value) -> void; /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] auto getDevices() -> std::vector; @@ -757,9 +757,9 @@ class FoMaC { * For multiple sessions, create FoMaC instances directly. * @param param The parameter to set * @param value The value to set - * @see FoMaC::setSessionParameter + * @see FoMaC::setParameter */ -auto setSessionParameter(QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void; +auto setParameter(QDMI_SESSION_PARAMETER_T param, const std::string& value) + -> void; } // namespace fomac diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 08c3fad2be..5a7337f191 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -859,8 +859,8 @@ auto FoMaC::ensureInitialized() -> void { initialized_ = true; } -auto FoMaC::setSessionParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void { +auto FoMaC::setParameter(const QDMI_SESSION_PARAMETER_T param, + const std::string& value) -> void { const std::scoped_lock lock(mutex_); if (initialized_) { @@ -910,9 +910,9 @@ auto getDevices() -> std::vector { return getDefaultSession().getDevices(); } -auto setSessionParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void { - getDefaultSession().setSessionParameter(param, value); +auto setParameter(const QDMI_SESSION_PARAMETER_T param, + const std::string& value) -> void { + getDefaultSession().setParameter(param, value); } } // namespace fomac diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 351127e004..a6ecb11ee0 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -616,46 +616,45 @@ TEST(AuthenticationTest, SessionParameterToString) { TEST(AuthenticationTest, ValidURLAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "https://example.com")); - EXPECT_NO_THROW(session.setSessionParameter( - QDMI_SESSION_PARAMETER_AUTHURL, "http://auth.server.com:8080/api")); - EXPECT_NO_THROW(session.setSessionParameter( - QDMI_SESSION_PARAMETER_AUTHURL, - "https://auth.example.com/token?param=value")); + EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, + "https://example.com")); + EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, + "http://auth.server.com:8080/api")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, + "https://auth.example.com/token?param=value")); } TEST(AuthenticationTest, InvalidURLRejected) { FoMaC session; EXPECT_THROW( - session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, "not-a-url"), + session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "not-a-url"), + std::runtime_error); + EXPECT_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "ftp://invalid.com"), + std::runtime_error); + EXPECT_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "example.com"), std::runtime_error); - EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "ftp://invalid.com"), - std::runtime_error); - EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "example.com"), - std::runtime_error); } TEST(AuthenticationTest, NonexistentFileRejected) { FoMaC session; - EXPECT_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, - "/nonexistent/path/to/file.txt"), + EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, + "/nonexistent/path/to/file.txt"), + std::runtime_error); + EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, + "/tmp/this_file_does_not_exist_12345.txt"), std::runtime_error); - EXPECT_THROW( - session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, - "/tmp/this_file_does_not_exist_12345.txt"), - std::runtime_error); } TEST(AuthenticationTest, ExistingFileAccepted) { FoMaC session; // Create a temporary file for testing const auto tmpPath = std::filesystem::temp_directory_path() / - ("test_fomac_auth_" + std::to_string( - std::hash{}( - std::this_thread::get_id())) + + ("test_fomac_auth_" + + std::to_string(std::hash{}( + std::this_thread::get_id())) + ".txt"); { std::ofstream ofs(tmpPath); @@ -663,8 +662,8 @@ TEST(AuthenticationTest, ExistingFileAccepted) { } // Existing file should be accepted - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_AUTHFILE, - tmpPath.string())); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, tmpPath.string())); // Clean up std::filesystem::remove(tmpPath); @@ -672,11 +671,10 @@ TEST(AuthenticationTest, ExistingFileAccepted) { TEST(AuthenticationTest, TokenParameterAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, - "my_token_123")); EXPECT_NO_THROW( - session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, "")); - EXPECT_NO_THROW(session.setSessionParameter( + session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "my_token_123")); + EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "")); + EXPECT_NO_THROW(session.setParameter( QDMI_SESSION_PARAMETER_TOKEN, "very_long_token_with_special_characters_!@#$%^&*()")); } @@ -684,29 +682,29 @@ TEST(AuthenticationTest, TokenParameterAccepted) { TEST(AuthenticationTest, UsernameAndPasswordParametersAccepted) { FoMaC session; EXPECT_NO_THROW( - session.setSessionParameter(QDMI_SESSION_PARAMETER_USERNAME, "user123")); - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_PASSWORD, - "secure_password")); + session.setParameter(QDMI_SESSION_PARAMETER_USERNAME, "user123")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_PASSWORD, "secure_password")); } TEST(AuthenticationTest, ProjectIDParameterAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_PROJECTID, - "project-123-abc")); + EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_PROJECTID, + "project-123-abc")); } TEST(AuthenticationTest, CustomParametersAccepted) { FoMaC session; - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM1, - "custom_value_1")); - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM2, - "custom_value_2")); - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM3, - "custom_value_3")); - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM4, - "custom_value_4")); - EXPECT_NO_THROW(session.setSessionParameter(QDMI_SESSION_PARAMETER_CUSTOM5, - "custom_value_5")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM1, "custom_value_1")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM2, "custom_value_2")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM3, "custom_value_3")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM4, "custom_value_4")); + EXPECT_NO_THROW( + session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM5, "custom_value_5")); } TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { @@ -715,9 +713,8 @@ TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { EXPECT_FALSE(devices.empty()); // Try to set a parameter - should fail - EXPECT_THROW( - session.setSessionParameter(QDMI_SESSION_PARAMETER_TOKEN, "token"), - std::runtime_error); + EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "token"), + std::runtime_error); } INSTANTIATE_TEST_SUITE_P( From 5705a51eb96c97aac80c4dd9db55ac989511d2a4 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 14:21:35 +0100 Subject: [PATCH 06/72] :fire: Remove module-level convenience functions for parameter setting and device fetching --- bindings/fomac/fomac.cpp | 4 ---- include/mqt-core/fomac/FoMaC.hpp | 21 --------------------- src/fomac/FoMaC.cpp | 18 ------------------ 3 files changed, 43 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 3ac1ff69d6..45d907f081 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -32,10 +32,6 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { fomac.def("set_parameter", &fomac::FoMaC::setParameter, "param"_a, "value"_a); fomac.def("get_devices", &fomac::FoMaC::getDevices); - // Module-level convenience functions (use default session) - m.def("set_parameter", &fomac::setParameter, "param"_a, "value"_a); - m.def("devices", &fomac::getDevices); - // SessionParameter enum py::native_enum( fomac, "SessionParameter", "enum.Enum", diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 609974e53b..489b38836f 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -741,25 +741,4 @@ class FoMaC { /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] auto getDevices() -> std::vector; }; - -/** - * @brief Get devices from a default FoMaC session. - * @details This is a convenience function that uses a static default FoMaC - * instance. For custom authentication or multiple sessions, create FoMaC - * instances directly. - * @return Vector of available devices - */ -[[nodiscard]] auto getDevices() -> std::vector; - -/** - * @brief Set a session parameter on the default FoMaC session. - * @details This is a convenience function for the default session. - * For multiple sessions, create FoMaC instances directly. - * @param param The parameter to set - * @param value The value to set - * @see FoMaC::setParameter - */ -auto setParameter(QDMI_SESSION_PARAMETER_T param, const std::string& value) - -> void; - } // namespace fomac diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 5a7337f191..fce313f116 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -897,22 +897,4 @@ auto FoMaC::getDevices() -> std::vector { [](const QDMI_Device& dev) -> Device { return {Token{}, dev}; }); return devices; } - -// Module-level convenience functions for default session -namespace { -auto getDefaultSession() -> FoMaC& { - static FoMaC instance; - return instance; -} -} // namespace - -auto getDevices() -> std::vector { - return getDefaultSession().getDevices(); -} - -auto setParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void { - getDefaultSession().setParameter(param, value); -} - } // namespace fomac From b2f883378da717c7c23b3993a1df7369556a8d38 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 14:27:42 +0100 Subject: [PATCH 07/72] :label: Propagate changes to `.pyi` --- python/mqt/core/fomac.pyi | 100 ++++++++++++-------------------------- 1 file changed, 30 insertions(+), 70 deletions(-) diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 57a9bcfa68..51c85008d3 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -14,9 +14,6 @@ __all__ = [ "Job", "ProgramFormat", "Session", - "SessionParameter", - "devices", - "set_session_parameter", ] class ProgramFormat(Enum): @@ -37,45 +34,45 @@ class ProgramFormat(Enum): CUSTOM4 = ... CUSTOM5 = ... -class SessionParameter(Enum): - """Session parameters for authentication and configuration. - - These parameters must be set before the first call to devices(). - """ - - TOKEN = ... - """Authentication token""" - AUTHFILE = ... - """Path to authentication file""" - AUTHURL = ... - """URL to authentication server""" - USERNAME = ... - """Username for authentication""" - PASSWORD = ... - """Password for authentication""" - PROJECTID = ... - """Project ID for session""" - CUSTOM1 = ... - """Custom parameter 1 (driver-defined)""" - CUSTOM2 = ... - """Custom parameter 2 (driver-defined)""" - CUSTOM3 = ... - """Custom parameter 3 (driver-defined)""" - CUSTOM4 = ... - """Custom parameter 4 (driver-defined)""" - CUSTOM5 = ... - """Custom parameter 5 (driver-defined)""" - class Session: """A FoMaC session for managing QDMI devices. Allows creating isolated sessions with independent authentication settings. """ + class SessionParameter(Enum): + """Session parameters for authentication and configuration. + + These parameters must be set before the first call to devices(). + """ + + TOKEN = ... + """Authentication token""" + AUTHFILE = ... + """Path to authentication file""" + AUTHURL = ... + """URL to authentication server""" + USERNAME = ... + """Username for authentication""" + PASSWORD = ... + """Password for authentication""" + PROJECTID = ... + """Project ID for session""" + CUSTOM1 = ... + """Custom parameter 1 (driver-defined)""" + CUSTOM2 = ... + """Custom parameter 2 (driver-defined)""" + CUSTOM3 = ... + """Custom parameter 3 (driver-defined)""" + CUSTOM4 = ... + """Custom parameter 4 (driver-defined)""" + CUSTOM5 = ... + """Custom parameter 5 (driver-defined)""" + def __init__(self) -> None: """Create a new FoMaC session.""" - def set_session_parameter(self, param: SessionParameter, value: str) -> None: + def set_parameter(self, param: SessionParameter, value: str) -> None: """Set a session parameter for authentication. This method must be called before the first call to get_devices(). @@ -274,40 +271,3 @@ class Device: """Checks if two devices are equal.""" def __ne__(self, other: object) -> bool: """Checks if two devices are not equal.""" - -def set_session_parameter(param: SessionParameter, value: str) -> None: - """Set a session parameter for authentication on the default session. - - This function must be called before the first call to devices(). - Once the session is initialized, parameters cannot be changed. - - For custom sessions or multiple sessions, use the Session class directly. - - Args: - param: The session parameter to set - value: The parameter value as a string - - Raises: - RuntimeError: If session is already initialized - RuntimeError: If AUTHFILE does not exist - RuntimeError: If AUTHURL has invalid format - - Example: - >>> import mqt.core.fomac as fomac - >>> fomac.set_session_parameter(fomac.SessionParameter.TOKEN, "my_token") - >>> devices = fomac.devices() - - # Or use Session class for isolated sessions: - >>> session = fomac.Session() - >>> session.set_session_parameter(fomac.SessionParameter.TOKEN, "token") - >>> session_devices = session.get_devices() - """ - -def devices() -> list[Device]: - """Returns a list of available devices from the default session. - - For custom sessions, create a Session instance and use its get_devices() method. - - Returns: - List of available devices - """ From 436947397c5856747ae7693afdf61fbe1cbccb24 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 14:53:35 +0100 Subject: [PATCH 08/72] :recycle: Session authentication via constructor parameters --- bindings/fomac/fomac.cpp | 48 ++++++++++--------- include/mqt-core/fomac/FoMaC.hpp | 46 ++++++++++-------- python/mqt/core/fomac.pyi | 81 +++++++++++++++----------------- src/fomac/FoMaC.cpp | 60 ++++++++++++----------- src/na/fomac/Device.cpp | 3 +- 5 files changed, 125 insertions(+), 113 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 45d907f081..00af947b99 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -27,27 +27,33 @@ using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { // FoMaC class (exposed as Session in Python) - auto fomac = py::class_(m, "Session"); - fomac.def(py::init<>()); - fomac.def("set_parameter", &fomac::FoMaC::setParameter, "param"_a, "value"_a); - fomac.def("get_devices", &fomac::FoMaC::getDevices); - - // SessionParameter enum - py::native_enum( - fomac, "SessionParameter", "enum.Enum", - "Session parameters for authentication and configuration.") - .value("TOKEN", QDMI_SESSION_PARAMETER_TOKEN) - .value("AUTHFILE", QDMI_SESSION_PARAMETER_AUTHFILE) - .value("AUTHURL", QDMI_SESSION_PARAMETER_AUTHURL) - .value("USERNAME", QDMI_SESSION_PARAMETER_USERNAME) - .value("PASSWORD", QDMI_SESSION_PARAMETER_PASSWORD) - .value("PROJECTID", QDMI_SESSION_PARAMETER_PROJECTID) - .value("CUSTOM1", QDMI_SESSION_PARAMETER_CUSTOM1) - .value("CUSTOM2", QDMI_SESSION_PARAMETER_CUSTOM2) - .value("CUSTOM3", QDMI_SESSION_PARAMETER_CUSTOM3) - .value("CUSTOM4", QDMI_SESSION_PARAMETER_CUSTOM4) - .value("CUSTOM5", QDMI_SESSION_PARAMETER_CUSTOM5) - .finalize(); + auto session = py::class_(m, "Session"); + + // Constructor with keyword arguments for authentication + const fomac::SessionConfig defaultConfig; + session.def( + py::init([](const std::optional& token, + const std::optional& authFile, + const std::optional& authUrl, + const std::optional& username, + const std::optional& password, + const std::optional& projectId) -> fomac::FoMaC { + fomac::SessionConfig config; + config.token = token; + config.authFile = authFile; + config.authUrl = authUrl; + config.username = username; + config.password = password; + config.projectId = projectId; + return fomac::FoMaC{config}; + }), + "token"_a = defaultConfig.token, "auth_file"_a = defaultConfig.authFile, + "auth_url"_a = defaultConfig.authUrl, + "username"_a = defaultConfig.username, + "password"_a = defaultConfig.password, + "project_id"_a = defaultConfig.projectId); + + session.def("get_devices", &fomac::FoMaC::getDevices); // Job class auto job = py::class_(m, "Job"); diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 489b38836f..7fcb5a417c 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -181,6 +181,27 @@ inline auto throwIfError(const int result, const std::string& msg) -> void { } } +/** + * @brief Configuration structure for session authentication parameters. + * @details All parameters are optional. Only set the parameters needed for + * your authentication method. Parameters are validated when the session is + * initialized. + */ +struct SessionConfig { + /// Authentication token + std::optional token; + /// Path to file containing authentication information + std::optional authFile; + /// URL to authentication server + std::optional authUrl; + /// Username for authentication + std::optional username; + /// Password for authentication + std::optional password; + /// Project ID for session + std::optional projectId; +}; + /** * @brief Class representing the FoMaC library. * @details This class provides methods to query available devices and @@ -702,12 +723,13 @@ class FoMaC { public: /** - * @brief Constructs a new FoMaC session. + * @brief Constructs a new FoMaC session with optional authentication. + * @param config Optional session configuration containing authentication + * parameters. If not provided, uses default (no authentication). * @details Creates and allocates a new QDMI session. The session is not - * initialized until the first call to getDevices() or after setting - * authentication parameters. + * initialized until the first call to getDevices(). */ - FoMaC(); + explicit FoMaC(const SessionConfig& config = {}); /** * @brief Destructor that releases the QDMI session. @@ -722,22 +744,6 @@ class FoMaC { FoMaC(FoMaC&&) noexcept; FoMaC& operator=(FoMaC&&) noexcept; - /** - * @brief Set a session parameter for authentication. - * @details This method must be called before the first call to getDevices(). - * Once the session is initialized, parameters cannot be changed. - * For AUTHFILE parameter, the file path is validated for existence. - * For AUTHURL parameter, basic URL format validation is performed. - * @param param The parameter to set - * @param value The value to set - * @throws std::runtime_error if the session is already initialized - * @throws std::runtime_error if validation fails (file not found, invalid - * URL) - * @see QDMI_session_set_parameter - */ - auto setParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void; - /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] auto getDevices() -> std::vector; }; diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 51c85008d3..16187e5a91 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -38,59 +38,52 @@ class Session: """A FoMaC session for managing QDMI devices. Allows creating isolated sessions with independent authentication settings. + All authentication parameters are optional and can be provided as keyword + arguments to the constructor. """ - class SessionParameter(Enum): - """Session parameters for authentication and configuration. - - These parameters must be set before the first call to devices(). - """ - - TOKEN = ... - """Authentication token""" - AUTHFILE = ... - """Path to authentication file""" - AUTHURL = ... - """URL to authentication server""" - USERNAME = ... - """Username for authentication""" - PASSWORD = ... - """Password for authentication""" - PROJECTID = ... - """Project ID for session""" - CUSTOM1 = ... - """Custom parameter 1 (driver-defined)""" - CUSTOM2 = ... - """Custom parameter 2 (driver-defined)""" - CUSTOM3 = ... - """Custom parameter 3 (driver-defined)""" - CUSTOM4 = ... - """Custom parameter 4 (driver-defined)""" - CUSTOM5 = ... - """Custom parameter 5 (driver-defined)""" - - def __init__(self) -> None: - """Create a new FoMaC session.""" - - def set_parameter(self, param: SessionParameter, value: str) -> None: - """Set a session parameter for authentication. - - This method must be called before the first call to get_devices(). - Once the session is initialized, parameters cannot be changed. + def __init__( + self, + *, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + project_id: str | None = None, + ) -> None: + """Create a new FoMaC session with optional authentication. Args: - param: The session parameter to set - value: The parameter value as a string + token: Authentication token + auth_file: Path to file containing authentication information + auth_url: URL to authentication server + username: Username for authentication + password: Password for authentication + project_id: Project ID for session Raises: - RuntimeError: If session is already initialized - RuntimeError: If AUTHFILE does not exist - RuntimeError: If AUTHURL has invalid format + RuntimeError: If auth_file does not exist + RuntimeError: If auth_url has invalid format Example: - >>> from mqt.core.fomac import Session, SessionParameter + >>> from mqt.core.fomac import Session + >>> # Session without authentication >>> session = Session() - >>> session.set_session_parameter(SessionParameter.TOKEN, "my_token") + >>> devices = session.get_devices() + >>> + >>> # Session with token authentication + >>> session = Session(token="my_secret_token") + >>> devices = session.get_devices() + >>> + >>> # Session with file-based authentication + >>> session = Session(auth_file="/path/to/auth.json") + >>> devices = session.get_devices() + >>> + >>> # Session with multiple parameters + >>> session = Session( + ... auth_url="https://auth.example.com", username="user", password="pass", project_id="project-123" + ... ) >>> devices = session.get_devices() """ diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index fce313f116..c75d685ab4 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -808,8 +808,40 @@ auto FoMaC::Job::getSparseProbabilities() const return probabilities; } -FoMaC::FoMaC() { +FoMaC::FoMaC(const SessionConfig& config) { QDMI_session_alloc(&session_); + + // Populate pending parameters from config + if (config.token) { + pendingParameters_[QDMI_SESSION_PARAMETER_TOKEN] = *config.token; + } + if (config.authFile) { + // Validate file existence + if (!std::filesystem::exists(*config.authFile)) { + throw std::runtime_error("Authentication file does not exist: " + + *config.authFile); + } + pendingParameters_[QDMI_SESSION_PARAMETER_AUTHFILE] = *config.authFile; + } + if (config.authUrl) { + // Validate URL format + static const std::regex URL_PATTERN( + R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); + if (!std::regex_match(*config.authUrl, URL_PATTERN)) { + throw std::runtime_error("Invalid URL format: " + *config.authUrl); + } + pendingParameters_[QDMI_SESSION_PARAMETER_AUTHURL] = *config.authUrl; + } + if (config.username) { + pendingParameters_[QDMI_SESSION_PARAMETER_USERNAME] = *config.username; + } + if (config.password) { + pendingParameters_[QDMI_SESSION_PARAMETER_PASSWORD] = *config.password; + } + if (config.projectId) { + pendingParameters_[QDMI_SESSION_PARAMETER_PROJECTID] = *config.projectId; + } + // Initialization is deferred to ensureInitialized() } @@ -859,32 +891,6 @@ auto FoMaC::ensureInitialized() -> void { initialized_ = true; } -auto FoMaC::setParameter(const QDMI_SESSION_PARAMETER_T param, - const std::string& value) -> void { - const std::scoped_lock lock(mutex_); - - if (initialized_) { - throw std::runtime_error( - "Cannot set session parameter after session is initialized"); - } - - // Validate parameters - if (param == QDMI_SESSION_PARAMETER_AUTHFILE) { - if (!std::filesystem::exists(value)) { - throw std::runtime_error("Authentication file does not exist: " + value); - } - } else if (param == QDMI_SESSION_PARAMETER_AUTHURL) { - // Basic URL validation - static const std::regex URL_PATTERN( - R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); - if (!std::regex_match(value, URL_PATTERN)) { - throw std::runtime_error("Invalid URL format: " + value); - } - } - - pendingParameters_[param] = value; -} - auto FoMaC::getDevices() -> std::vector { ensureInitialized(); diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index 17f4248957..6bbe250b07 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -577,7 +577,8 @@ FoMaC::Device::Device(const fomac::FoMaC::Device& device) : fomac::FoMaC::Device(device) {} auto FoMaC::getDevices() -> std::vector { std::vector devices; - for (const auto& d : fomac::getDevices()) { + fomac::FoMaC session; + for (const auto& d : session.getDevices()) { if (auto r = Device::tryCreateFromDevice(d); r.has_value()) { devices.emplace_back(r.value()); } From 84e6f357d11087472e11a251162738f8916cf55c Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 15:05:00 +0100 Subject: [PATCH 09/72] :recycle: Rename the `FoMaC` class to `Session` --- bindings/fomac/fomac.cpp | 182 +++++++++++------------ bindings/na/fomac/fomac.cpp | 14 +- include/mqt-core/fomac/FoMaC.hpp | 33 ++--- include/mqt-core/na/fomac/Device.hpp | 27 ++-- src/fomac/FoMaC.cpp | 208 ++++++++++++++------------- src/na/fomac/Device.cpp | 47 +++--- test/fomac/test_fomac.cpp | 143 ++++-------------- test/na/fomac/test_fomac.cpp | 4 +- 8 files changed, 287 insertions(+), 371 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 00af947b99..4f875481f9 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -26,52 +26,52 @@ namespace py = pybind11; using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { - // FoMaC class (exposed as Session in Python) - auto session = py::class_(m, "Session"); + auto session = py::class_(m, "Session"); - // Constructor with keyword arguments for authentication const fomac::SessionConfig defaultConfig; session.def( - py::init([](const std::optional& token, - const std::optional& authFile, - const std::optional& authUrl, - const std::optional& username, - const std::optional& password, - const std::optional& projectId) -> fomac::FoMaC { - fomac::SessionConfig config; - config.token = token; - config.authFile = authFile; - config.authUrl = authUrl; - config.username = username; - config.password = password; - config.projectId = projectId; - return fomac::FoMaC{config}; - }), + py::init( + [](const std::optional& token, + const std::optional& authFile, + const std::optional& authUrl, + const std::optional& username, + const std::optional& password, + const std::optional& projectId) -> fomac::Session { + fomac::SessionConfig config; + config.token = token; + config.authFile = authFile; + config.authUrl = authUrl; + config.username = username; + config.password = password; + config.projectId = projectId; + return fomac::Session{config}; + }), "token"_a = defaultConfig.token, "auth_file"_a = defaultConfig.authFile, "auth_url"_a = defaultConfig.authUrl, "username"_a = defaultConfig.username, "password"_a = defaultConfig.password, "project_id"_a = defaultConfig.projectId); - session.def("get_devices", &fomac::FoMaC::getDevices); + session.def("get_devices", &fomac::Session::getDevices); // Job class - auto job = py::class_(m, "Job"); - job.def("check", &fomac::FoMaC::Job::check); - job.def("wait", &fomac::FoMaC::Job::wait, "timeout"_a = 0); - job.def("cancel", &fomac::FoMaC::Job::cancel); - job.def("get_shots", &fomac::FoMaC::Job::getShots); - job.def("get_counts", &fomac::FoMaC::Job::getCounts); - job.def("get_dense_statevector", &fomac::FoMaC::Job::getDenseStateVector); - job.def("get_dense_probabilities", &fomac::FoMaC::Job::getDenseProbabilities); - job.def("get_sparse_statevector", &fomac::FoMaC::Job::getSparseStateVector); + auto job = py::class_(m, "Job"); + job.def("check", &fomac::Session::Job::check); + job.def("wait", &fomac::Session::Job::wait, "timeout"_a = 0); + job.def("cancel", &fomac::Session::Job::cancel); + job.def("get_shots", &fomac::Session::Job::getShots); + job.def("get_counts", &fomac::Session::Job::getCounts); + job.def("get_dense_statevector", &fomac::Session::Job::getDenseStateVector); + job.def("get_dense_probabilities", + &fomac::Session::Job::getDenseProbabilities); + job.def("get_sparse_statevector", &fomac::Session::Job::getSparseStateVector); job.def("get_sparse_probabilities", - &fomac::FoMaC::Job::getSparseProbabilities); - job.def_property_readonly("id", &fomac::FoMaC::Job::getId); + &fomac::Session::Job::getSparseProbabilities); + job.def_property_readonly("id", &fomac::Session::Job::getId); job.def_property_readonly("program_format", - &fomac::FoMaC::Job::getProgramFormat); - job.def_property_readonly("program", &fomac::FoMaC::Job::getProgram); - job.def_property_readonly("num_shots", &fomac::FoMaC::Job::getNumShots); + &fomac::Session::Job::getProgramFormat); + job.def_property_readonly("program", &fomac::Session::Job::getProgram); + job.def_property_readonly("num_shots", &fomac::Session::Job::getNumShots); job.def(py::self == py::self); // NOLINT(misc-redundant-expression) job.def(py::self != py::self); // NOLINT(misc-redundant-expression) @@ -108,7 +108,7 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .export_values() .finalize(); - auto device = py::class_(m, "Device"); + auto device = py::class_(m, "Device"); py::native_enum(device, "Status", "enum.Enum", "Enumeration of device status.") @@ -121,92 +121,92 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .export_values() .finalize(); - auto site = py::class_(device, "Site"); - site.def("index", &fomac::FoMaC::Device::Site::getIndex); - site.def("t1", &fomac::FoMaC::Device::Site::getT1); - site.def("t2", &fomac::FoMaC::Device::Site::getT2); - site.def("name", &fomac::FoMaC::Device::Site::getName); - site.def("x_coordinate", &fomac::FoMaC::Device::Site::getXCoordinate); - site.def("y_coordinate", &fomac::FoMaC::Device::Site::getYCoordinate); - site.def("z_coordinate", &fomac::FoMaC::Device::Site::getZCoordinate); - site.def("is_zone", &fomac::FoMaC::Device::Site::isZone); - site.def("x_extent", &fomac::FoMaC::Device::Site::getXExtent); - site.def("y_extent", &fomac::FoMaC::Device::Site::getYExtent); - site.def("z_extent", &fomac::FoMaC::Device::Site::getZExtent); - site.def("module_index", &fomac::FoMaC::Device::Site::getModuleIndex); - site.def("submodule_index", &fomac::FoMaC::Device::Site::getSubmoduleIndex); - site.def("__repr__", [](const fomac::FoMaC::Device::Site& s) { + auto site = py::class_(device, "Site"); + site.def("index", &fomac::Session::Device::Site::getIndex); + site.def("t1", &fomac::Session::Device::Site::getT1); + site.def("t2", &fomac::Session::Device::Site::getT2); + site.def("name", &fomac::Session::Device::Site::getName); + site.def("x_coordinate", &fomac::Session::Device::Site::getXCoordinate); + site.def("y_coordinate", &fomac::Session::Device::Site::getYCoordinate); + site.def("z_coordinate", &fomac::Session::Device::Site::getZCoordinate); + site.def("is_zone", &fomac::Session::Device::Site::isZone); + site.def("x_extent", &fomac::Session::Device::Site::getXExtent); + site.def("y_extent", &fomac::Session::Device::Site::getYExtent); + site.def("z_extent", &fomac::Session::Device::Site::getZExtent); + site.def("module_index", &fomac::Session::Device::Site::getModuleIndex); + site.def("submodule_index", &fomac::Session::Device::Site::getSubmoduleIndex); + site.def("__repr__", [](const fomac::Session::Device::Site& s) { return ""; }); site.def(py::self == py::self); // NOLINT(misc-redundant-expression) site.def(py::self != py::self); // NOLINT(misc-redundant-expression) auto operation = - py::class_(device, "Operation"); - operation.def("name", &fomac::FoMaC::Device::Operation::getName, - "sites"_a = std::vector{}, + py::class_(device, "Operation"); + operation.def("name", &fomac::Session::Device::Operation::getName, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("qubits_num", &fomac::FoMaC::Device::Operation::getQubitsNum, - "sites"_a = std::vector{}, + operation.def("qubits_num", &fomac::Session::Device::Operation::getQubitsNum, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("parameters_num", - &fomac::FoMaC::Device::Operation::getParametersNum, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getParametersNum, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("duration", &fomac::FoMaC::Device::Operation::getDuration, - "sites"_a = std::vector{}, + operation.def("duration", &fomac::Session::Device::Operation::getDuration, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("fidelity", &fomac::FoMaC::Device::Operation::getFidelity, - "sites"_a = std::vector{}, + operation.def("fidelity", &fomac::Session::Device::Operation::getFidelity, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("interaction_radius", - &fomac::FoMaC::Device::Operation::getInteractionRadius, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getInteractionRadius, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("blocking_radius", - &fomac::FoMaC::Device::Operation::getBlockingRadius, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getBlockingRadius, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("idling_fidelity", - &fomac::FoMaC::Device::Operation::getIdlingFidelity, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getIdlingFidelity, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("is_zoned", &fomac::FoMaC::Device::Operation::isZoned); - operation.def("sites", &fomac::FoMaC::Device::Operation::getSites); - operation.def("site_pairs", &fomac::FoMaC::Device::Operation::getSitePairs); + operation.def("is_zoned", &fomac::Session::Device::Operation::isZoned); + operation.def("sites", &fomac::Session::Device::Operation::getSites); + operation.def("site_pairs", &fomac::Session::Device::Operation::getSitePairs); operation.def("mean_shuttling_speed", - &fomac::FoMaC::Device::Operation::getMeanShuttlingSpeed, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getMeanShuttlingSpeed, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("__repr__", [](const fomac::FoMaC::Device::Operation& op) { + operation.def("__repr__", [](const fomac::Session::Device::Operation& op) { return ""; }); operation.def(py::self == py::self); // NOLINT(misc-redundant-expression) operation.def(py::self != py::self); // NOLINT(misc-redundant-expression) - device.def("name", &fomac::FoMaC::Device::getName); - device.def("version", &fomac::FoMaC::Device::getVersion); - device.def("status", &fomac::FoMaC::Device::getStatus); - device.def("library_version", &fomac::FoMaC::Device::getLibraryVersion); - device.def("qubits_num", &fomac::FoMaC::Device::getQubitsNum); - device.def("sites", &fomac::FoMaC::Device::getSites); - device.def("regular_sites", &fomac::FoMaC::Device::getRegularSites); - device.def("zones", &fomac::FoMaC::Device::getZones); - device.def("operations", &fomac::FoMaC::Device::getOperations); - device.def("coupling_map", &fomac::FoMaC::Device::getCouplingMap); - device.def("needs_calibration", &fomac::FoMaC::Device::getNeedsCalibration); - device.def("length_unit", &fomac::FoMaC::Device::getLengthUnit); + device.def("name", &fomac::Session::Device::getName); + device.def("version", &fomac::Session::Device::getVersion); + device.def("status", &fomac::Session::Device::getStatus); + device.def("library_version", &fomac::Session::Device::getLibraryVersion); + device.def("qubits_num", &fomac::Session::Device::getQubitsNum); + device.def("sites", &fomac::Session::Device::getSites); + device.def("regular_sites", &fomac::Session::Device::getRegularSites); + device.def("zones", &fomac::Session::Device::getZones); + device.def("operations", &fomac::Session::Device::getOperations); + device.def("coupling_map", &fomac::Session::Device::getCouplingMap); + device.def("needs_calibration", &fomac::Session::Device::getNeedsCalibration); + device.def("length_unit", &fomac::Session::Device::getLengthUnit); device.def("length_scale_factor", - &fomac::FoMaC::Device::getLengthScaleFactor); - device.def("duration_unit", &fomac::FoMaC::Device::getDurationUnit); + &fomac::Session::Device::getLengthScaleFactor); + device.def("duration_unit", &fomac::Session::Device::getDurationUnit); device.def("duration_scale_factor", - &fomac::FoMaC::Device::getDurationScaleFactor); - device.def("min_atom_distance", &fomac::FoMaC::Device::getMinAtomDistance); + &fomac::Session::Device::getDurationScaleFactor); + device.def("min_atom_distance", &fomac::Session::Device::getMinAtomDistance); device.def("supported_program_formats", - &fomac::FoMaC::Device::getSupportedProgramFormats); - device.def("submit_job", &fomac::FoMaC::Device::submitJob, "program"_a, + &fomac::Session::Device::getSupportedProgramFormats); + device.def("submit_job", &fomac::Session::Device::submitJob, "program"_a, "program_format"_a, "num_shots"_a); - device.def("__repr__", [](const fomac::FoMaC::Device& dev) { + device.def("__repr__", [](const fomac::Session::Device& dev) { return ""; }); device.def(py::self == py::self); // NOLINT(misc-redundant-expression) diff --git a/bindings/na/fomac/fomac.cpp b/bindings/na/fomac/fomac.cpp index f2082ffd42..2f65fae240 100644 --- a/bindings/na/fomac/fomac.cpp +++ b/bindings/na/fomac/fomac.cpp @@ -42,7 +42,7 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { pybind11::module_::import("mqt.core.fomac"); auto device = - py::class_(m, "Device"); + py::class_(m, "Device"); auto lattice = py::class_(device, "Lattice"); @@ -91,22 +91,22 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { lattice.def(py::self == py::self); lattice.def(py::self != py::self); - device.def_property_readonly("traps", &na::FoMaC::Device::getTraps); - device.def_property_readonly("t1", [](const na::FoMaC::Device& dev) { + device.def_property_readonly("traps", &na::Session::Device::getTraps); + device.def_property_readonly("t1", [](const na::Session::Device& dev) { return dev.getDecoherenceTimes().t1; }); - device.def_property_readonly("t2", [](const na::FoMaC::Device& dev) { + device.def_property_readonly("t2", [](const na::Session::Device& dev) { return dev.getDecoherenceTimes().t2; }); - device.def("__repr__", [](const fomac::FoMaC::Device& dev) { + device.def("__repr__", [](const fomac::Session::Device& dev) { return ""; }); device.def(py::self == py::self); device.def(py::self != py::self); - m.def("devices", &na::FoMaC::getDevices); + m.def("devices", &na::Session::getDevices); device.def_static("try_create_from_device", - &na::FoMaC::Device::tryCreateFromDevice, "device"_a); + &na::Session::Device::tryCreateFromDevice, "device"_a); } // NOLINTEND(misc-redundant-expression) } // namespace mqt diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 7fcb5a417c..4e72080522 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -185,7 +185,7 @@ inline auto throwIfError(const int result, const std::string& msg) -> void { * @brief Configuration structure for session authentication parameters. * @details All parameters are optional. Only set the parameters needed for * your authentication method. Parameters are validated when the session is - * initialized. + * constructed. */ struct SessionConfig { /// Authentication token @@ -203,15 +203,15 @@ struct SessionConfig { }; /** - * @brief Class representing the FoMaC library. + * @brief Class representing the Session library. * @details This class provides methods to query available devices and * manage the QDMI session. * @see QDMI_Session */ -class FoMaC { +class Session { /** * @brief Private token class. - * @details Only the FoMaC class can create instances of this class. + * @details Only the Session class can create instances of this class. */ class Token { public: @@ -633,7 +633,7 @@ class FoMaC { * @brief Constructs a Device object from a QDMI_Device handle. * @param device The QDMI_Device handle to wrap. */ - Device(FoMaC::Token /* unused */, QDMI_Device device) : device_(device) {} + Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {} /// @returns the underlying QDMI_Device object. [[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; } // NOLINTNEXTLINE(google-explicit-constructor) @@ -699,12 +699,6 @@ class FoMaC { private: QDMI_Session session_ = nullptr; - mutable std::mutex mutex_; - bool initialized_ = false; - std::unordered_map pendingParameters_; - - /// @brief Ensures the session is initialized, applying pending parameters - auto ensureInitialized() -> void; template [[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const @@ -723,26 +717,25 @@ class FoMaC { public: /** - * @brief Constructs a new FoMaC session with optional authentication. + * @brief Constructs a new QDMI Session with optional authentication. * @param config Optional session configuration containing authentication * parameters. If not provided, uses default (no authentication). - * @details Creates and allocates a new QDMI session. The session is not - * initialized until the first call to getDevices(). + * @details Creates, allocates, and initializes a new QDMI session. */ - explicit FoMaC(const SessionConfig& config = {}); + explicit Session(const SessionConfig& config = {}); /** * @brief Destructor that releases the QDMI session. */ - virtual ~FoMaC(); + virtual ~Session(); // Delete copy constructors and assignment operators - FoMaC(const FoMaC&) = delete; - FoMaC& operator=(const FoMaC&) = delete; + Session(const Session&) = delete; + Session& operator=(const Session&) = delete; // Allow move semantics - FoMaC(FoMaC&&) noexcept; - FoMaC& operator=(FoMaC&&) noexcept; + Session(Session&&) noexcept; + Session& operator=(Session&&) noexcept; /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] auto getDevices() -> std::vector; diff --git a/include/mqt-core/na/fomac/Device.hpp b/include/mqt-core/na/fomac/Device.hpp index 5d5626cf55..01d495bf06 100644 --- a/include/mqt-core/na/fomac/Device.hpp +++ b/include/mqt-core/na/fomac/Device.hpp @@ -20,18 +20,18 @@ namespace na { /** - * @brief Class representing the FoMaC library with neutral atom extensions. - * @see fomac::FoMaC + * @brief Class representing the Session library with neutral atom extensions. + * @see fomac::Session */ -class FoMaC : public fomac::FoMaC { +class Session : public fomac::Session { public: /** * @brief Class representing a quantum device with neutral atom extensions. - * @see fomac::FoMaC::Device + * @see fomac::Session::Device * @note Since it inherits from @ref na::Device, Device objects can be * converted to `nlohmann::json` objects. */ - class Device : public fomac::FoMaC::Device, na::Device { + class Device : public fomac::Session::Device, na::Device { /** * @brief Initializes the name from the underlying QDMI device. @@ -79,13 +79,13 @@ class FoMaC : public fomac::FoMaC { auto initOperationsFromDevice() -> bool; /** - * @brief Constructs a Device object from a fomac::FoMaC::Device object. - * @param device The fomac::FoMaC::Device object to wrap. + * @brief Constructs a Device object from a fomac::Session::Device object. + * @param device The fomac::Session::Device object to wrap. * @note The constructor does not initialize the additional fields of this * class. For their initialization, the corresponding `init*FromDevice` * methods must be called, see @ref tryCreateFromDevice. */ - explicit Device(const fomac::FoMaC::Device& device); + explicit Device(const fomac::Session::Device& device); public: /// @returns the length unit of the device. @@ -109,17 +109,18 @@ class FoMaC : public fomac::FoMaC { } /** - * @brief Try to create a Device object from a fomac::FoMaC::Device object. + * @brief Try to create a Device object from a fomac::Session::Device + * object. * @details This method attempts to create a Device object by initializing - * all necessary fields from the provided fomac::FoMaC::Device object. If + * all necessary fields from the provided fomac::Session::Device object. If * any required information is missing or invalid, the method returns * `std::nullopt`. - * @param device is the fomac::FoMaC::Device object to wrap. + * @param device is the fomac::Session::Device object to wrap. * @return An optional containing the instantiated device if compatible, * std::nullopt otherwise. */ [[nodiscard]] static auto - tryCreateFromDevice(const fomac::FoMaC::Device& device) + tryCreateFromDevice(const fomac::Session::Device& device) -> std::optional { Device d(device); // The sequence of the following method calls does not matter. @@ -160,7 +161,7 @@ class FoMaC : public fomac::FoMaC { }; /// @brief Deleted default constructor to prevent instantiation. - FoMaC() = delete; + Session() = delete; /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] static auto getDevices() -> std::vector; diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index c75d685ab4..2f3383af09 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -245,101 +245,102 @@ auto throwError(const int result, const std::string& msg) -> void { toString(static_cast(result)) + "."); } } -auto FoMaC::Device::Site::getIndex() const -> size_t { +auto Session::Device::Site::getIndex() const -> size_t { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } -auto FoMaC::Device::Site::getT1() const -> std::optional { +auto Session::Device::Site::getT1() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_T1); } -auto FoMaC::Device::Site::getT2() const -> std::optional { +auto Session::Device::Site::getT2() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_T2); } -auto FoMaC::Device::Site::getName() const -> std::optional { +auto Session::Device::Site::getName() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_NAME); } -auto FoMaC::Device::Site::getXCoordinate() const -> std::optional { +auto Session::Device::Site::getXCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_XCOORDINATE); } -auto FoMaC::Device::Site::getYCoordinate() const -> std::optional { +auto Session::Device::Site::getYCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_YCOORDINATE); } -auto FoMaC::Device::Site::getZCoordinate() const -> std::optional { +auto Session::Device::Site::getZCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_ZCOORDINATE); } -auto FoMaC::Device::Site::isZone() const -> bool { +auto Session::Device::Site::isZone() const -> bool { return queryProperty>(QDMI_SITE_PROPERTY_ISZONE) .value_or(false); } -auto FoMaC::Device::Site::getXExtent() const -> std::optional { +auto Session::Device::Site::getXExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_XEXTENT); } -auto FoMaC::Device::Site::getYExtent() const -> std::optional { +auto Session::Device::Site::getYExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_YEXTENT); } -auto FoMaC::Device::Site::getZExtent() const -> std::optional { +auto Session::Device::Site::getZExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_ZEXTENT); } -auto FoMaC::Device::Site::getModuleIndex() const -> std::optional { +auto Session::Device::Site::getModuleIndex() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_MODULEINDEX); } -auto FoMaC::Device::Site::getSubmoduleIndex() const -> std::optional { +auto Session::Device::Site::getSubmoduleIndex() const + -> std::optional { return queryProperty>( QDMI_SITE_PROPERTY_SUBMODULEINDEX); } -auto FoMaC::Device::Operation::getName(const std::vector& sites, - const std::vector& params) const +auto Session::Device::Operation::getName( + const std::vector& sites, const std::vector& params) const -> std::string { return queryProperty(QDMI_OPERATION_PROPERTY_NAME, sites, params); } -auto FoMaC::Device::Operation::getQubitsNum( +auto Session::Device::Operation::getQubitsNum( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>(QDMI_OPERATION_PROPERTY_QUBITSNUM, sites, params); } -auto FoMaC::Device::Operation::getParametersNum( +auto Session::Device::Operation::getParametersNum( const std::vector& sites, const std::vector& params) const -> size_t { return queryProperty(QDMI_OPERATION_PROPERTY_PARAMETERSNUM, sites, params); } -auto FoMaC::Device::Operation::getDuration( +auto Session::Device::Operation::getDuration( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_DURATION, sites, params); } -auto FoMaC::Device::Operation::getFidelity( +auto Session::Device::Operation::getFidelity( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>(QDMI_OPERATION_PROPERTY_FIDELITY, sites, params); } -auto FoMaC::Device::Operation::getInteractionRadius( +auto Session::Device::Operation::getInteractionRadius( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS, sites, params); } -auto FoMaC::Device::Operation::getBlockingRadius( +auto Session::Device::Operation::getBlockingRadius( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS, sites, params); } -auto FoMaC::Device::Operation::getIdlingFidelity( +auto Session::Device::Operation::getIdlingFidelity( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_IDLINGFIDELITY, sites, params); } -auto FoMaC::Device::Operation::isZoned() const -> bool { +auto Session::Device::Operation::isZoned() const -> bool { return queryProperty>(QDMI_OPERATION_PROPERTY_ISZONED, {}, {}) .value_or(false); } -auto FoMaC::Device::Operation::getSites() const +auto Session::Device::Operation::getSites() const -> std::optional> { const auto& qdmiSites = queryProperty>>( QDMI_OPERATION_PROPERTY_SITES, {}, {}); @@ -354,7 +355,7 @@ auto FoMaC::Device::Operation::getSites() const }); return returnedSites; } -auto FoMaC::Device::Operation::getSitePairs() const +auto Session::Device::Operation::getSitePairs() const -> std::optional>> { if (const auto qubitsNum = getQubitsNum({}, {}); !qubitsNum.has_value() || *qubitsNum != 2 || isZoned()) { @@ -380,28 +381,28 @@ auto FoMaC::Device::Operation::getSitePairs() const return pairs; } -auto FoMaC::Device::Operation::getMeanShuttlingSpeed( +auto Session::Device::Operation::getMeanShuttlingSpeed( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED, sites, params); } -auto FoMaC::Device::getName() const -> std::string { +auto Session::Device::getName() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_NAME); } -auto FoMaC::Device::getVersion() const -> std::string { +auto Session::Device::getVersion() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_VERSION); } -auto FoMaC::Device::getStatus() const -> QDMI_Device_Status { +auto Session::Device::getStatus() const -> QDMI_Device_Status { return queryProperty(QDMI_DEVICE_PROPERTY_STATUS); } -auto FoMaC::Device::getLibraryVersion() const -> std::string { +auto Session::Device::getLibraryVersion() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_LIBRARYVERSION); } -auto FoMaC::Device::getQubitsNum() const -> size_t { +auto Session::Device::getQubitsNum() const -> size_t { return queryProperty(QDMI_DEVICE_PROPERTY_QUBITSNUM); } -auto FoMaC::Device::getSites() const -> std::vector { +auto Session::Device::getSites() const -> std::vector { const auto& qdmiSites = queryProperty>(QDMI_DEVICE_PROPERTY_SITES); std::vector sites; @@ -412,14 +413,14 @@ auto FoMaC::Device::getSites() const -> std::vector { }); return sites; } -auto FoMaC::Device::getRegularSites() const -> std::vector { +auto Session::Device::getRegularSites() const -> std::vector { auto allSites = getSites(); const auto newEnd = std::ranges::remove_if( allSites, [](const Site& s) { return s.isZone(); }); allSites.erase(newEnd.begin(), newEnd.end()); return allSites; } -auto FoMaC::Device::getZones() const -> std::vector { +auto Session::Device::getZones() const -> std::vector { const auto& allSites = getSites(); std::vector zones; zones.reserve(3); // Reserve space for a typical max number of zones @@ -427,7 +428,7 @@ auto FoMaC::Device::getZones() const -> std::vector { [](const Site& s) { return s.isZone(); }); return zones; } -auto FoMaC::Device::getOperations() const -> std::vector { +auto Session::Device::getOperations() const -> std::vector { const auto& qdmiOperations = queryProperty>( QDMI_DEVICE_PROPERTY_OPERATIONS); std::vector operations; @@ -439,7 +440,7 @@ auto FoMaC::Device::getOperations() const -> std::vector { }); return operations; } -auto FoMaC::Device::getCouplingMap() const +auto Session::Device::getCouplingMap() const -> std::optional>> { const auto& qdmiCouplingMap = queryProperty< std::optional>>>( @@ -457,40 +458,40 @@ auto FoMaC::Device::getCouplingMap() const }); return couplingMap; } -auto FoMaC::Device::getNeedsCalibration() const -> std::optional { +auto Session::Device::getNeedsCalibration() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION); } -auto FoMaC::Device::getLengthUnit() const -> std::optional { +auto Session::Device::getLengthUnit() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHUNIT); } -auto FoMaC::Device::getLengthScaleFactor() const -> std::optional { +auto Session::Device::getLengthScaleFactor() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR); } -auto FoMaC::Device::getDurationUnit() const -> std::optional { +auto Session::Device::getDurationUnit() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONUNIT); } -auto FoMaC::Device::getDurationScaleFactor() const -> std::optional { +auto Session::Device::getDurationScaleFactor() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR); } -auto FoMaC::Device::getMinAtomDistance() const -> std::optional { +auto Session::Device::getMinAtomDistance() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_MINATOMDISTANCE); } -auto FoMaC::Device::getSupportedProgramFormats() const +auto Session::Device::getSupportedProgramFormats() const -> std::vector { return queryProperty>( QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS); } -auto FoMaC::Device::submitJob(const std::string& program, - const QDMI_Program_Format format, - const size_t numShots) const -> Job { +auto Session::Device::submitJob(const std::string& program, + const QDMI_Program_Format format, + const size_t numShots) const -> Job { QDMI_Job job = nullptr; throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions @@ -516,13 +517,13 @@ auto FoMaC::Device::submitJob(const std::string& program, return jobWrapper; } -auto FoMaC::Job::check() const -> QDMI_Job_Status { +auto Session::Job::check() const -> QDMI_Job_Status { QDMI_Job_Status status{}; throwIfError(QDMI_job_check(job_, &status), "Checking job status"); return status; } -auto FoMaC::Job::wait(const size_t timeout) const -> bool { +auto Session::Job::wait(const size_t timeout) const -> bool { const auto ret = QDMI_job_wait(job_, timeout); if (ret == QDMI_SUCCESS) { return true; @@ -534,11 +535,11 @@ auto FoMaC::Job::wait(const size_t timeout) const -> bool { unreachable(); } -auto FoMaC::Job::cancel() const -> void { +auto Session::Job::cancel() const -> void { throwIfError(QDMI_job_cancel(job_), "Cancelling job"); } -auto FoMaC::Job::getId() const -> std::string { +auto Session::Job::getId() const -> std::string { size_t size = 0; throwIfError( QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, 0, nullptr, &size), @@ -550,7 +551,7 @@ auto FoMaC::Job::getId() const -> std::string { return id; } -auto FoMaC::Job::getProgramFormat() const -> QDMI_Program_Format { +auto Session::Job::getProgramFormat() const -> QDMI_Program_Format { QDMI_Program_Format format{}; throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAMFORMAT, sizeof(format), &format, nullptr), @@ -558,7 +559,7 @@ auto FoMaC::Job::getProgramFormat() const -> QDMI_Program_Format { return format; } -auto FoMaC::Job::getProgram() const -> std::string { +auto Session::Job::getProgram() const -> std::string { size_t size = 0; throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, nullptr, &size), @@ -571,7 +572,7 @@ auto FoMaC::Job::getProgram() const -> std::string { return program; } -auto FoMaC::Job::getNumShots() const -> size_t { +auto Session::Job::getNumShots() const -> size_t { size_t numShots = 0; throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, sizeof(numShots), &numShots, nullptr), @@ -579,7 +580,7 @@ auto FoMaC::Job::getNumShots() const -> size_t { return numShots; } -auto FoMaC::Job::getShots() const -> std::vector { +auto Session::Job::getShots() const -> std::vector { size_t shotsSize = 0; throwIfError( QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, 0, nullptr, &shotsSize), @@ -610,7 +611,7 @@ auto FoMaC::Job::getShots() const -> std::vector { return shotsVec; } -auto FoMaC::Job::getCounts() const -> std::map { +auto Session::Job::getCounts() const -> std::map { // Get the histogram keys size_t keysSize = 0; throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, nullptr, @@ -661,7 +662,7 @@ auto FoMaC::Job::getCounts() const -> std::map { return counts; } -auto FoMaC::Job::getDenseStateVector() const +auto Session::Job::getDenseStateVector() const -> std::vector> { size_t size = 0; throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, @@ -681,7 +682,7 @@ auto FoMaC::Job::getDenseStateVector() const return stateVector; } -auto FoMaC::Job::getDenseProbabilities() const -> std::vector { +auto Session::Job::getDenseProbabilities() const -> std::vector { size_t size = 0; throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_DENSE, 0, nullptr, &size), @@ -699,7 +700,7 @@ auto FoMaC::Job::getDenseProbabilities() const -> std::vector { return probabilities; } -auto FoMaC::Job::getSparseStateVector() const +auto Session::Job::getSparseStateVector() const -> std::map> { size_t keysSize = 0; throwIfError(QDMI_job_get_results(job_, @@ -755,7 +756,7 @@ auto FoMaC::Job::getSparseStateVector() const return stateVector; } -auto FoMaC::Job::getSparseProbabilities() const +auto Session::Job::getSparseProbabilities() const -> std::map { size_t keysSize = 0; throwIfError(QDMI_job_get_results(job_, @@ -808,12 +809,18 @@ auto FoMaC::Job::getSparseProbabilities() const return probabilities; } -FoMaC::FoMaC(const SessionConfig& config) { +Session::Session(const SessionConfig& config) { QDMI_session_alloc(&session_); - // Populate pending parameters from config + // Set and validate parameters from config if (config.token) { - pendingParameters_[QDMI_SESSION_PARAMETER_TOKEN] = *config.token; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_TOKEN); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.token->size() + 1, + config.token->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_TOKEN)); } if (config.authFile) { // Validate file existence @@ -821,7 +828,13 @@ FoMaC::FoMaC(const SessionConfig& config) { throw std::runtime_error("Authentication file does not exist: " + *config.authFile); } - pendingParameters_[QDMI_SESSION_PARAMETER_AUTHFILE] = *config.authFile; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_AUTHFILE); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.authFile->size() + 1, + config.authFile->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_AUTHFILE)); } if (config.authUrl) { // Validate URL format @@ -830,69 +843,68 @@ FoMaC::FoMaC(const SessionConfig& config) { if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } - pendingParameters_[QDMI_SESSION_PARAMETER_AUTHURL] = *config.authUrl; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_AUTHURL); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.authUrl->size() + 1, + config.authUrl->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_AUTHURL)); } if (config.username) { - pendingParameters_[QDMI_SESSION_PARAMETER_USERNAME] = *config.username; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_USERNAME); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.username->size() + 1, + config.username->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_USERNAME)); } if (config.password) { - pendingParameters_[QDMI_SESSION_PARAMETER_PASSWORD] = *config.password; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_PASSWORD); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.password->size() + 1, + config.password->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_PASSWORD)); } if (config.projectId) { - pendingParameters_[QDMI_SESSION_PARAMETER_PROJECTID] = *config.projectId; + const auto qdmiParam = + static_cast(QDMI_SESSION_PARAMETER_PROJECTID); + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.projectId->size() + 1, + config.projectId->c_str()), + "Setting session parameter " + + toString(QDMI_SESSION_PARAMETER_PROJECTID)); } - // Initialization is deferred to ensureInitialized() + // Initialize the session + throwIfError(QDMI_session_init(session_), "Initializing session"); } -FoMaC::~FoMaC() { +Session::~Session() { if (session_ != nullptr) { QDMI_session_free(session_); } } -FoMaC::FoMaC(FoMaC&& other) noexcept - : session_(other.session_), initialized_(other.initialized_), - pendingParameters_(std::move(other.pendingParameters_)) { +Session::Session(Session&& other) noexcept : session_(other.session_) { other.session_ = nullptr; - other.initialized_ = false; } -FoMaC& FoMaC::operator=(FoMaC&& other) noexcept { +Session& Session::operator=(Session&& other) noexcept { if (this != &other) { if (session_ != nullptr) { QDMI_session_free(session_); } session_ = other.session_; - initialized_ = other.initialized_; - pendingParameters_ = std::move(other.pendingParameters_); other.session_ = nullptr; - other.initialized_ = false; } return *this; } -auto FoMaC::ensureInitialized() -> void { - const std::scoped_lock lock(mutex_); - if (initialized_) { - return; - } - - // Apply all pending parameters - for (const auto& [param, value] : pendingParameters_) { - const auto qdmiParam = static_cast(param); - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - value.size() + 1, value.c_str()), - "Setting session parameter " + toString(param)); - } - - // Initialize the session - throwIfError(QDMI_session_init(session_), "Initializing session"); - initialized_ = true; -} - -auto FoMaC::getDevices() -> std::vector { - ensureInitialized(); +auto Session::getDevices() -> std::vector { const auto& qdmiDevices = queryProperty>(QDMI_SESSION_PROPERTY_DEVICES); diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index 6bbe250b07..9214745f6c 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -38,12 +38,12 @@ namespace na { namespace { /** - * @brief Calculate the rectangular extent covering all given FoMaC sites. - * @param sites is a vector of FoMaC sites + * @brief Calculate the rectangular extent covering all given Session sites. + * @param sites is a vector of Session sites * @return the extent covering all given sites */ auto calculateExtentFromSites( - const std::vector& sites) -> Device::Region { + const std::vector& sites) -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); auto minY = std::numeric_limits::max(); @@ -61,13 +61,14 @@ auto calculateExtentFromSites( .height = static_cast(maxY - minY)}}; } /** - * @brief Calculate the rectangular extent covering all given FoMaC site pairs. - * @param sitePairs is a vector of FoMaC site pairs + * @brief Calculate the rectangular extent covering all given Session site + * pairs. + * @param sitePairs is a vector of Session site pairs * @return the extent covering all sites in the pairs */ auto calculateExtentFromSites( - const std::vector>& sitePairs) + const std::vector>& sitePairs) -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); @@ -109,8 +110,8 @@ class MinHeap } }; } // namespace -auto FoMaC::Device::initNameFromDevice() -> void { name = getName(); } -auto FoMaC::Device::initMinAtomDistanceFromDevice() -> bool { +auto Session::Device::initNameFromDevice() -> void { name = getName(); } +auto Session::Device::initMinAtomDistanceFromDevice() -> bool { const auto& d = getMinAtomDistance(); if (!d.has_value()) { SPDLOG_INFO("Minimal atom distance not set"); @@ -119,11 +120,11 @@ auto FoMaC::Device::initMinAtomDistanceFromDevice() -> bool { minAtomDistance = *d; return true; } -auto FoMaC::Device::initQubitsNumFromDevice() -> void { +auto Session::Device::initQubitsNumFromDevice() -> void { numQubits = getQubitsNum(); } -auto FoMaC::Device::initLengthUnitFromDevice() -> bool { - const auto& u = fomac::FoMaC::Device::getLengthUnit(); +auto Session::Device::initLengthUnitFromDevice() -> bool { + const auto& u = fomac::Session::Device::getLengthUnit(); if (!u.has_value()) { SPDLOG_INFO("Length unit not set"); return false; @@ -132,8 +133,8 @@ auto FoMaC::Device::initLengthUnitFromDevice() -> bool { lengthUnit.scaleFactor = getLengthScaleFactor().value_or(1.0); return true; } -auto FoMaC::Device::initDurationUnitFromDevice() -> bool { - const auto& u = fomac::FoMaC::Device::getDurationUnit(); +auto Session::Device::initDurationUnitFromDevice() -> bool { + const auto& u = fomac::Session::Device::getDurationUnit(); if (!u.has_value()) { SPDLOG_INFO("Duration unit not set"); return false; @@ -142,7 +143,7 @@ auto FoMaC::Device::initDurationUnitFromDevice() -> bool { durationUnit.scaleFactor = getDurationScaleFactor().value_or(1.0); return true; } -auto FoMaC::Device::initDecoherenceTimesFromDevice() -> bool { +auto Session::Device::initDecoherenceTimesFromDevice() -> bool { const auto regularSites = getRegularSites(); if (regularSites.empty()) { SPDLOG_INFO("Device has no regular sites with decoherence data"); @@ -169,7 +170,7 @@ auto FoMaC::Device::initDecoherenceTimesFromDevice() -> bool { decoherenceTimes.t2 = sumT2 / count; return true; } -auto FoMaC::Device::initTrapsfromDevice() -> bool { +auto Session::Device::initTrapsfromDevice() -> bool { traps.clear(); const auto regularSites = getRegularSites(); if (regularSites.empty()) { @@ -293,10 +294,10 @@ auto FoMaC::Device::initTrapsfromDevice() -> bool { } return true; } -auto FoMaC::Device::initOperationsFromDevice() -> bool { +auto Session::Device::initOperationsFromDevice() -> bool { std::map>> shuttlingUnitsPerId; - for (const fomac::FoMaC::Device::Operation& op : getOperations()) { + for (const fomac::Session::Device::Operation& op : getOperations()) { const auto zoned = op.isZoned(); const auto& nq = op.getQubitsNum(); const auto& name = op.getName(); @@ -307,7 +308,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } if (zoned) { if (std::ranges::any_of( - *sitesOpt, [](const fomac::FoMaC::Device::Site& site) -> bool { + *sitesOpt, [](const fomac::Session::Device::Site& site) -> bool { return !site.isZone(); })) { SPDLOG_INFO("Operation marked as zoned but has non-zone sites"); @@ -573,11 +574,11 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } return true; } -FoMaC::Device::Device(const fomac::FoMaC::Device& device) - : fomac::FoMaC::Device(device) {} -auto FoMaC::getDevices() -> std::vector { +Session::Device::Device(const fomac::Session::Device& device) + : fomac::Session::Device(device) {} +auto Session::getDevices() -> std::vector { std::vector devices; - fomac::FoMaC session; + fomac::Session session; for (const auto& d : session.getDevices()) { if (auto r = Device::tryCreateFromDevice(d); r.has_value()) { devices.emplace_back(r.value()); diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index a6ecb11ee0..a1e55e8637 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -28,36 +28,37 @@ #include namespace fomac { -class DeviceTest : public testing::TestWithParam { +class DeviceTest : public testing::TestWithParam { protected: - FoMaC::Device device; + Session::Device device; DeviceTest() : device(GetParam()) {} }; class SiteTest : public DeviceTest { protected: - FoMaC::Device::Site site; + Session::Device::Site site; SiteTest() : site(device.getSites().front()) {} }; class OperationTest : public DeviceTest { protected: - FoMaC::Device::Operation operation; + Session::Device::Operation operation; OperationTest() : operation(device.getOperations().front()) {} }; class DDSimulatorDeviceTest : public testing::Test { protected: - FoMaC::Device device; + Session::Device device; DDSimulatorDeviceTest() : device(getDDSimulatorDevice()) {} private: - static auto getDDSimulatorDevice() -> FoMaC::Device { - for (const auto& dev : fomac::getDevices()) { + static auto getDDSimulatorDevice() -> Session::Device { + Session session; + for (const auto& dev : session.getDevices()) { if (dev.getName() == "MQT Core DDSIM QDMI Device") { return dev; } @@ -68,11 +69,11 @@ class DDSimulatorDeviceTest : public testing::Test { class JobTest : public DDSimulatorDeviceTest { protected: - FoMaC::Job job; + Session::Job job; JobTest() : job(createTestJob()) {} - [[nodiscard]] FoMaC::Job createTestJob() const { + [[nodiscard]] Session::Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[1] q; @@ -86,11 +87,11 @@ c[0] = measure q[0]; class SimulatorJobTest : public DDSimulatorDeviceTest { protected: - FoMaC::Job job; + Session::Job job; SimulatorJobTest() : job(createTestJob()) {} - [[nodiscard]] FoMaC::Job createTestJob() const { + [[nodiscard]] Session::Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[2] q; @@ -614,107 +615,15 @@ TEST(AuthenticationTest, SessionParameterToString) { EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } -TEST(AuthenticationTest, ValidURLAccepted) { - FoMaC session; - EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "https://example.com")); - EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "http://auth.server.com:8080/api")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, - "https://auth.example.com/token?param=value")); -} - -TEST(AuthenticationTest, InvalidURLRejected) { - FoMaC session; - EXPECT_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "not-a-url"), - std::runtime_error); - EXPECT_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "ftp://invalid.com"), - std::runtime_error); - EXPECT_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_AUTHURL, "example.com"), - std::runtime_error); -} - -TEST(AuthenticationTest, NonexistentFileRejected) { - FoMaC session; - EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, - "/nonexistent/path/to/file.txt"), - std::runtime_error); - EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, - "/tmp/this_file_does_not_exist_12345.txt"), - std::runtime_error); -} - -TEST(AuthenticationTest, ExistingFileAccepted) { - FoMaC session; - // Create a temporary file for testing - const auto tmpPath = std::filesystem::temp_directory_path() / - ("test_fomac_auth_" + - std::to_string(std::hash{}( - std::this_thread::get_id())) + - ".txt"); - { - std::ofstream ofs(tmpPath); - ofs << "test_token_content"; - } - - // Existing file should be accepted - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_AUTHFILE, tmpPath.string())); - - // Clean up - std::filesystem::remove(tmpPath); -} - -TEST(AuthenticationTest, TokenParameterAccepted) { - FoMaC session; - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "my_token_123")); - EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "")); - EXPECT_NO_THROW(session.setParameter( - QDMI_SESSION_PARAMETER_TOKEN, - "very_long_token_with_special_characters_!@#$%^&*()")); -} - -TEST(AuthenticationTest, UsernameAndPasswordParametersAccepted) { - FoMaC session; - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_USERNAME, "user123")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_PASSWORD, "secure_password")); -} - -TEST(AuthenticationTest, ProjectIDParameterAccepted) { - FoMaC session; - EXPECT_NO_THROW(session.setParameter(QDMI_SESSION_PARAMETER_PROJECTID, - "project-123-abc")); -} +// Note: Authentication parameter tests removed because the underlying QDMI +// library does not currently support these parameters. When support is added, +// tests should validate that parameters are properly set through SessionConfig +// in the Session constructor. -TEST(AuthenticationTest, CustomParametersAccepted) { - FoMaC session; - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM1, "custom_value_1")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM2, "custom_value_2")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM3, "custom_value_3")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM4, "custom_value_4")); - EXPECT_NO_THROW( - session.setParameter(QDMI_SESSION_PARAMETER_CUSTOM5, "custom_value_5")); -} - -TEST(AuthenticationTest, CannotSetParameterAfterInitialization) { - FoMaC session; - auto devices = session.getDevices(); - EXPECT_FALSE(devices.empty()); - - // Try to set a parameter - should fail - EXPECT_THROW(session.setParameter(QDMI_SESSION_PARAMETER_TOKEN, "token"), - std::runtime_error); +// Helper function to get all devices for parameterized tests +inline auto getDevices() -> std::vector { + Session session; + return session.getDevices(); } INSTANTIATE_TEST_SUITE_P( @@ -723,8 +632,8 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name DeviceTest, // Parameters to test with - testing::ValuesIn(fomac::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -737,8 +646,8 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name SiteTest, // Parameters to test with - testing::ValuesIn(fomac::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -751,8 +660,8 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name OperationTest, // Parameters to test with - testing::ValuesIn(fomac::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); diff --git a/test/na/fomac/test_fomac.cpp b/test/na/fomac/test_fomac.cpp index 8d1e022d27..246c2ca80c 100644 --- a/test/na/fomac/test_fomac.cpp +++ b/test/na/fomac/test_fomac.cpp @@ -42,7 +42,7 @@ TEST(TestNAFoMaC, TrapsJSONRoundTrip) { } catch (const nlohmann::json::parse_error& e) { GTEST_FAIL() << "JSON parsing error: " << e.what(); } - nlohmann::json qdmiDevice = FoMaC::getDevices().front(); + nlohmann::json qdmiDevice = Session::getDevices().front(); canonicallyOrderLatticeVectors(fomacDevice); canonicallyOrderLatticeVectors(qdmiDevice); EXPECT_EQ(fomacDevice["traps"], qdmiDevice["traps"]); @@ -58,7 +58,7 @@ TEST(TestNAFoMaC, FullJSONRoundTrip) { } catch (const nlohmann::json::parse_error& e) { GTEST_FAIL() << "JSON parsing error: " << e.what(); } - nlohmann::json fomacDevice = FoMaC::getDevices().front(); + nlohmann::json fomacDevice = Session::getDevices().front(); canonicallyOrderLatticeVectors(jsonDevice); canonicallyOrderLatticeVectors(fomacDevice); EXPECT_EQ(jsonDevice, fomacDevice); From 6a4dc71d3fbd4ed38f944ce077feea1514213591 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 16:49:35 +0100 Subject: [PATCH 10/72] :recycle: Adjust Qiskit-QDMI Python interface to adhere to new API --- python/mqt/core/plugins/qiskit/provider.py | 3 ++- test/python/fomac/test_fomac.py | 20 +++++++++++++++----- test/python/plugins/qiskit/conftest.py | 3 ++- test/python/plugins/qiskit/test_backend.py | 12 ++++++------ test/python/plugins/qiskit/test_provider.py | 4 ++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index e3a5147928..b26195c774 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -46,8 +46,9 @@ class QDMIProvider: def __init__(self) -> None: """Initialize the QDMI provider.""" + session = fomac.Session() self._backends = [ - QDMIBackend(device=d, provider=self) for d in fomac.devices() if QDMIBackend.is_convertible(d) + QDMIBackend(device=d, provider=self) for d in session.get_devices() if QDMIBackend.is_convertible(d) ] def backends(self, name: str | None = None) -> list[QDMIBackend]: diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 6a54119d0a..ae6ae91fbe 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -14,10 +14,20 @@ import pytest -from mqt.core.fomac import Device, Job, ProgramFormat, devices +from mqt.core.fomac import Device, Job, ProgramFormat, Session -@pytest.fixture(params=devices()) +def _get_devices() -> list[Device]: + """Get all available devices from a Session. + + Returns: + List of all available QDMI devices. + """ + session = Session() + return session.get_devices() + + +@pytest.fixture(params=_get_devices()) def device(request: pytest.FixtureRequest) -> Device: """Fixture to provide a device for testing. @@ -27,7 +37,7 @@ def device(request: pytest.FixtureRequest) -> Device: return cast("Device", request.param) -@pytest.fixture(params=devices()) +@pytest.fixture(params=_get_devices()) def device_and_site(request: pytest.FixtureRequest) -> tuple[Device, Device.Site]: """Fixture to provide a device for testing. @@ -39,7 +49,7 @@ def device_and_site(request: pytest.FixtureRequest) -> tuple[Device, Device.Site return dev, site -@pytest.fixture(params=devices()) +@pytest.fixture(params=_get_devices()) def device_and_operation(request: pytest.FixtureRequest) -> tuple[Device, Device.Operation]: """Fixture to provide a device for testing. @@ -58,7 +68,7 @@ def ddsim_device() -> Device: Returns: The MQT Core DDSIM QDMI Device if it can be found. """ - for dev in devices(): + for dev in _get_devices(): if dev.name() == "MQT Core DDSIM QDMI Device": return dev pytest.skip("DDSIM device not found - job submission tests require DDSIM device") diff --git a/test/python/plugins/qiskit/conftest.py b/test/python/plugins/qiskit/conftest.py index 6c979b9c7a..ae745923e3 100644 --- a/test/python/plugins/qiskit/conftest.py +++ b/test/python/plugins/qiskit/conftest.py @@ -451,7 +451,8 @@ def real_qdmi_devices() -> list[fomac.Device]: Returns: List of all QDMI devices from FoMaC. """ - return fomac.devices() + session = fomac.Session() + return session.get_devices() @pytest.fixture(scope="module") diff --git a/test/python/plugins/qiskit/test_backend.py b/test/python/plugins/qiskit/test_backend.py index 18eb21a0ae..078299ec89 100644 --- a/test/python/plugins/qiskit/test_backend.py +++ b/test/python/plugins/qiskit/test_backend.py @@ -265,11 +265,11 @@ def test_backend_warns_on_unmappable_operation( operations=["cz", "custom_unmappable_gate", "measure"], ) - # Monkeypatch fomac.devices to return mock device - def mock_devices() -> list[MockQDMIDevice]: + # Monkeypatch Session.get_devices to return mock device + def mock_get_devices(_self: object) -> list[MockQDMIDevice]: return [mock_device] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) # Creating backend should trigger warning about unmappable operation with warnings.catch_warnings(record=True) as w: @@ -297,11 +297,11 @@ def test_backend_warns_on_missing_measurement_operation( operations=["cz"], # No measure operation ) - # Monkeypatch fomac.devices to return mock device - def mock_devices() -> list[MockQDMIDevice]: + # Monkeypatch Session.get_devices to return mock device + def mock_get_devices(_self: object) -> list[MockQDMIDevice]: return [mock_device] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) # Creating backend should trigger warning about missing measurement operation with warnings.catch_warnings(record=True) as w: diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index 6ae4442a0b..3d1d7a538a 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -80,10 +80,10 @@ def test_provider_get_backend_no_devices(monkeypatch: pytest.MonkeyPatch) -> Non """Provider raises ValueError when no devices available.""" # Monkeypatch to return empty device list - def mock_devices() -> list[object]: + def mock_get_devices(_self: object) -> list[object]: return [] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) provider = QDMIProvider() with pytest.raises(ValueError, match="No backend found with name"): From 8380a2213bf6dfb1f9210c50749fcad032f055a4 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 20:46:33 +0100 Subject: [PATCH 11/72] :rotating_light: Fix `clang-tidy` warnings --- bindings/fomac/fomac.cpp | 1 + src/fomac/FoMaC.cpp | 3 ++- test/fomac/test_fomac.cpp | 10 ++-------- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 4f875481f9..ef148e1d2c 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -10,6 +10,7 @@ #include "fomac/FoMaC.hpp" +#include #include #include // NOLINT(misc-include-cleaner) #include diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 2f3383af09..a5f4cbcfa7 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -91,6 +90,8 @@ auto toString(const QDMI_SESSION_PARAMETER_T param) -> std::string { return "PASSWORD"; case QDMI_SESSION_PARAMETER_PROJECTID: return "PROJECTID"; + case QDMI_SESSION_PARAMETER_MAX: + return "MAX"; case QDMI_SESSION_PARAMETER_CUSTOM1: return "CUSTOM1"; case QDMI_SESSION_PARAMETER_CUSTOM2: diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index a1e55e8637..a3aa9b96d5 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -13,8 +13,6 @@ #include #include #include -#include -#include #include #include #include @@ -23,7 +21,6 @@ #include #include #include -#include #include #include @@ -615,16 +612,13 @@ TEST(AuthenticationTest, SessionParameterToString) { EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } -// Note: Authentication parameter tests removed because the underlying QDMI -// library does not currently support these parameters. When support is added, -// tests should validate that parameters are properly set through SessionConfig -// in the Session constructor. - +namespace { // Helper function to get all devices for parameterized tests inline auto getDevices() -> std::vector { Session session; return session.getDevices(); } +} // namespace INSTANTIATE_TEST_SUITE_P( // Custom instantiation name From 594b3b3892148d0a03c8924b3526193aa001adeb Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 20:57:10 +0100 Subject: [PATCH 12/72] :white_check_mark: Add placeholder tests for authentication --- test/python/fomac/test_fomac.py | 199 ++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index ae6ae91fbe..c1a24fd565 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -10,6 +10,8 @@ from __future__ import annotations +import tempfile +from pathlib import Path from typing import cast import pytest @@ -588,3 +590,200 @@ def test_simulator_job_get_sparse_probabilities_returns_valid_probabilities(simu assert "11" in sparse_probabilities assert sparse_probabilities["11"] == pytest.approx(0.5) + + +def test_session_construction_with_token() -> None: + """Test Session construction with a token parameter. + + Note: The underlying QDMI library may not support authentication parameters yet, + so this test verifies that the parameter can be passed without causing errors + during construction, even if it's not actually used. + """ + # Empty token should be accepted + try: + session = Session(token="") + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + # Non-empty token should be accepted + try: + session = Session(token="test_token_123") # noqa: S106 + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + # Token with special characters should be accepted + try: + session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # noqa: S106 + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_session_construction_with_auth_url() -> None: + """Test Session construction with auth URL parameter. + + Note: The currently available QDMI devices don't support authentication. + Valid URLs should either be accepted or rejected with "Not supported" error. + Invalid URLs should be rejected with validation errors. + """ + # Valid HTTPS URL + try: + session = Session(auth_url="https://example.com") + assert session is not None + except RuntimeError: + # Either not supported or validation failed - both acceptable + pass + # Valid HTTP URL with port and path + try: + session = Session(auth_url="http://auth.server.com:8080/api") + assert session is not None + except RuntimeError: + # Either not supported or validation failed - both acceptable + pass + # Valid HTTPS URL with query parameters + try: + session = Session(auth_url="https://auth.example.com/token?param=value") + assert session is not None + except RuntimeError: + # Either not supported or validation failed - both acceptable + pass + # Invalid URL - not a URL at all + with pytest.raises(RuntimeError): + Session(auth_url="not-a-url") + # Invalid URL - unsupported protocol + with pytest.raises(RuntimeError): + Session(auth_url="ftp://invalid.com") + + # Invalid URL - missing protocol + with pytest.raises(RuntimeError): + Session(auth_url="example.com") + + +def test_session_construction_with_auth_file() -> None: + """Test Session construction with auth file parameter. + + Note: The currently available QDMI devices don't support authentication. + Existing files should either be accepted or rejected with "Not supported" error. + Non-existent files should be rejected with validation errors. + """ + # Test with non-existent file - should raise error + with pytest.raises(RuntimeError): + Session(auth_file="/nonexistent/path/to/file.txt") + + # Test with another non-existent file + with pytest.raises(RuntimeError): + Session(auth_file="/tmp/this_file_does_not_exist_12345.txt") # noqa: S108 + + # Test with existing file + with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", delete=False, suffix=".txt") as tmp_file: + tmp_file.write("test_token_content") + tmp_path = tmp_file.name + + try: + # Existing file should be accepted or rejected with "Not supported" + try: + session = Session(auth_file=tmp_path) + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + finally: + # Clean up + Path(tmp_path).unlink(missing_ok=True) + + +def test_session_construction_with_username_password() -> None: + """Test Session construction with username and password parameters. + + Note: The currently available QDMI devices don't support authentication. + """ + # Username only + try: + session = Session(username="user123") + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + # Password only + try: + session = Session(password="secure_password") # noqa: S106 + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + # Both username and password + try: + session = Session(username="user123", password="secure_password") # noqa: S106 + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_session_construction_with_project_id() -> None: + """Test Session construction with project ID parameter. + + Note: The currently available QDMI devices don't support authentication. + """ + try: + session = Session(project_id="project-123-abc") + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_session_construction_with_multiple_parameters() -> None: + """Test Session construction with multiple authentication parameters. + + Note: The currently available QDMI devices don't support authentication. + """ + try: + session = Session( + token="test_token", # noqa: S106 + username="test_user", + password="test_pass", # noqa: S106 + project_id="test_project", + ) + assert session is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_session_get_devices_returns_list() -> None: + """Test that get_devices() returns a list of Device objects.""" + session = Session() + devices = session.get_devices() + + assert isinstance(devices, list) + assert len(devices) > 0 + + # All elements should be Device instances + for device in devices: + assert isinstance(device, Device) + # Device should have a name + assert len(device.name()) > 0 + + +def test_session_multiple_instances() -> None: + """Test that multiple Session instances can be created independently.""" + session1 = Session() + session2 = Session() + + devices1 = session1.get_devices() + devices2 = session2.get_devices() + + # Both should return devices + assert len(devices1) > 0 + assert len(devices2) > 0 + + # Should return the same number of devices + assert len(devices1) == len(devices2) From 578b5f217e12a0757e00277ea4ce80cd1b6b8ab2 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:08:39 +0100 Subject: [PATCH 13/72] :bug: Guard session allocation --- src/fomac/FoMaC.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index a5f4cbcfa7..72e21eead8 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -811,7 +811,8 @@ auto Session::Job::getSparseProbabilities() const } Session::Session(const SessionConfig& config) { - QDMI_session_alloc(&session_); + const auto result = QDMI_session_alloc(&session_); + throwIfError(result, "Allocating QDMI session"); // Set and validate parameters from config if (config.token) { From b6ffb8af262674165627344c04974a40331dcedf Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:13:55 +0100 Subject: [PATCH 14/72] :recycle: Improve URL validation regex --- src/fomac/FoMaC.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 72e21eead8..d6265a6b53 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -839,9 +839,9 @@ Session::Session(const SessionConfig& config) { toString(QDMI_SESSION_PARAMETER_AUTHFILE)); } if (config.authUrl) { - // Validate URL format + // Validate URL format according to: https://uibakery.io/regex-library/url static const std::regex URL_PATTERN( - R"(^https?://[a-zA-Z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$)"); + R"(/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/)"); if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } From 1db21eb6c9cfe6cf9286357935f2d1b9a9aced51 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:16:25 +0100 Subject: [PATCH 15/72] :art: Address CodeRabbit's suggestions --- include/mqt-core/fomac/FoMaC.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 4e72080522..a3d3e803a9 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -143,19 +143,19 @@ concept maybe_optional_value_or_string_or_vector = value_or_string_or_vector>; /// @returns the string representation of the given QDMI_STATUS. -auto toString(const QDMI_STATUS result) -> std::string; +auto toString(QDMI_STATUS result) -> std::string; /// @returns the string representation of the given QDMI_Site_Property. -auto toString(const QDMI_Site_Property prop) -> std::string; +auto toString(QDMI_Site_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Operation_Property. -auto toString(const QDMI_Operation_Property prop) -> std::string; +auto toString(QDMI_Operation_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Device_Property. -auto toString(const QDMI_Device_Property prop) -> std::string; +auto toString(QDMI_Device_Property prop) -> std::string; /// @returns the string representation of the given QDMI_Session_Property. -constexpr auto toString(const QDMI_Session_Property prop) -> std::string { +constexpr auto toString(QDMI_Session_Property prop) -> std::string { if (prop == QDMI_SESSION_PROPERTY_DEVICES) { return "QDMI_SESSION_PROPERTY_DEVICES"; } @@ -163,13 +163,13 @@ constexpr auto toString(const QDMI_Session_Property prop) -> std::string { } /// @returns the string representation of the given QDMI_SESSION_PARAMETER_T. -auto toString(const QDMI_SESSION_PARAMETER_T param) -> std::string; +auto toString(QDMI_SESSION_PARAMETER_T param) -> std::string; /// Throws an exception corresponding to the given QDMI_STATUS code. -[[noreturn]] auto throwError(const int result, const std::string& msg) -> void; +[[noreturn]] auto throwError(int result, const std::string& msg) -> void; /// Throws an exception if the result indicates an error. -inline auto throwIfError(const int result, const std::string& msg) -> void { +inline auto throwIfError(int result, const std::string& msg) -> void { switch (result) { case QDMI_SUCCESS: break; From ed6d6b4c58cf81a28ffb5d891e5e8716a3ba767c Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:22:21 +0100 Subject: [PATCH 16/72] :memo: Update documentation to reflect code changes --- docs/qdmi/driver.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/qdmi/driver.md b/docs/qdmi/driver.md index 3085fbce38..ed836dae88 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -19,20 +19,23 @@ Other devices can be loaded dynamically at runtime via {cpp:func}`qdmi::Driver:: The QDMI Driver is implemented in C++ and exposed to Python via [pybind11](https://pybind11.readthedocs.io). Direct binding of the QDMI Client interface functions is not feasible due to technical limitations. -Instead, a FoMaC (Figure of Merits and Constraints) library defines wrapper classes ({cpp:class}`~fomac::FoMaC::Device`, {cpp:class}`~fomac::FoMaC::Device::Site`, {cpp:class}`~fomac::FoMaC::Device::Operation`) for the QDMI entities. -These classes together with their methods are then exposed to Python, see {py:class}`~mqt.core.fomac.Device`, {py:class}`~mqt.core.fomac.Device.Site`, {py:class}`~mqt.core.fomac.Device.Operation`. +Instead, a FoMaC (Figure of Merits and Constraints) library defines wrapper classes ({cpp:class}`~fomac::Session`, {cpp:class}`~fomac::Session::Device`, {cpp:class}`~fomac::Session::Device::Site`, {cpp:class}`~fomac::Session::Device::Operation`, {cpp:class}`~fomac::Session::Job`) for the QDMI entities. +These classes together with their methods are then exposed to Python, see {py:class}`~mqt.core.fomac.Session`, {py:class}`~mqt.core.fomac.Device`, {py:class}`~mqt.core.fomac.Device.Site`, {py:class}`~mqt.core.fomac.Device.Operation`, {py:class}`~mqt.core.fomac.Job`. ## Usage -The following example shows how to get a device from the QDMI driver and access its name. +The following example shows how to create a session and get devices from the QDMI driver. ```{code-cell} ipython3 -from mqt.core.fomac import devices +from mqt.core.fomac import Session -# get a list of all available devices -available_devices = devices() +# Create a session to interact with QDMI devices +session = Session() -# print the name of every device (by default there is only the NA device) +# Get a list of all available devices +available_devices = session.get_devices() + +# Print the name of every device for device in available_devices: print(device.name()) ``` From 321ee044f8d15973fad7f34780372d3542fc0c68 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:35:35 +0100 Subject: [PATCH 17/72] :sparkles: Expose session authentication to QDMIProvider initialization for passthrough --- python/mqt/core/plugins/qiskit/provider.py | 36 ++++++- test/python/plugins/qiskit/test_provider.py | 109 ++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index b26195c774..014de28310 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -42,11 +42,41 @@ class QDMIProvider: Get a specific backend by name: >>> backend = provider.get_backend("MQT Core DDSIM QDMI Device") + + Create a provider with authentication: + + >>> provider = QDMIProvider(token="my_token") + >>> # or with username and password + >>> provider = QDMIProvider(username="user", password="pass") """ - def __init__(self) -> None: - """Initialize the QDMI provider.""" - session = fomac.Session() + def __init__( + self, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + project_id: str | None = None, + ) -> None: + """Initialize the QDMI provider. + + Args: + token: Authentication token for the session. + auth_file: Path to file containing authentication information. + auth_url: URL to authentication server. + username: Username for authentication. + password: Password for authentication. + project_id: Project ID for the session. + """ + session = fomac.Session( + token=token, + auth_file=auth_file, + auth_url=auth_url, + username=username, + password=password, + project_id=project_id, + ) self._backends = [ QDMIBackend(device=d, provider=self) for d in session.get_devices() if QDMIBackend.is_convertible(d) ] diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index 3d1d7a538a..e55ebbad3b 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -10,6 +10,9 @@ from __future__ import annotations +import tempfile +from pathlib import Path + import pytest from mqt.core import fomac @@ -104,3 +107,109 @@ def test_backend_has_provider_reference() -> None: backend = provider.get_backend("MQT Core DDSIM QDMI Device") assert backend.provider is provider + + +def test_provider_with_token_parameter() -> None: + """Provider accepts token parameter.""" + # Should not raise an error when creating provider with token + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(token="test_token") # noqa: S106 + # If device supports token, verify provider was created + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_auth_file_parameter() -> None: + """Provider accepts auth_file parameter.""" + # Create a temporary file for testing + with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8") as tmp_file: + tmp_file.write("test_auth_content") + tmp_path = tmp_file.name + + try: + # Should not raise an error when creating provider with auth_file + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(auth_file=tmp_path) + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + finally: + # Clean up + Path(tmp_path).unlink(missing_ok=True) + + +def test_provider_with_auth_url_parameter() -> None: + """Provider accepts auth_url parameter.""" + # Should not raise an error when creating provider with auth_url + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(auth_url="https://auth.example.com") + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_username_password_parameters() -> None: + """Provider accepts username and password parameters.""" + # Should not raise an error when creating provider with username and password + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(username="test_user", password="test_pass") # noqa: S106 + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_project_id_parameter() -> None: + """Provider accepts project_id parameter.""" + # Should not raise an error when creating provider with project_id + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(project_id="test_project") + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_multiple_auth_parameters() -> None: + """Provider accepts multiple authentication parameters.""" + # Should not raise an error when creating provider with multiple auth parameters + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider( # noqa: S106 + token="test_token", + username="test_user", + password="test_pass", + project_id="test_project", + ) + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_default_constructor_unchanged() -> None: + """Provider default constructor behavior is unchanged (backward compatibility).""" + # Create provider without any parameters + provider = QDMIProvider() + + # Should have at least one backend (the DDSIM device) + backends = provider.backends() + assert len(backends) > 0 + + # Should be able to get backends + all_backends = provider.backends() + assert isinstance(all_backends, list) + + # Should be able to get a specific backend + backend = provider.get_backend("MQT Core DDSIM QDMI Device") + assert backend.name == "MQT Core DDSIM QDMI Device" + From 7f5cbe9419739c28faedf90bb1b39608333d7f6c Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:43:05 +0100 Subject: [PATCH 18/72] :bug: Fixed regex format --- src/fomac/FoMaC.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index d6265a6b53..d5fefa2795 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -841,7 +841,7 @@ Session::Session(const SessionConfig& config) { if (config.authUrl) { // Validate URL format according to: https://uibakery.io/regex-library/url static const std::regex URL_PATTERN( - R"(/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/)"); + R"(^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } From 36aaeaeb53a27dea65fbfed16f6ae3d242e6b039 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:49:11 +0100 Subject: [PATCH 19/72] :white_check_mark: Add authentication FoMaC C++ tests --- test/fomac/test_fomac.cpp | 211 ++++++++++++++++++++ test/python/plugins/qiskit/test_provider.py | 7 +- 2 files changed, 214 insertions(+), 4 deletions(-) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index a3aa9b96d5..5dfdec9b9d 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -22,6 +24,7 @@ #include #include #include +#include #include namespace fomac { @@ -612,6 +615,214 @@ TEST(AuthenticationTest, SessionParameterToString) { EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } +TEST(AuthenticationTest, SessionConstructionWithToken) { + // Empty token should be accepted + SessionConfig config1; + config1.token = ""; + try { + Session session(config1); + SUCCEED(); // If we get here, the session was created successfully + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } + + // Non-empty token should be accepted + SessionConfig config2; + config2.token = "test_token_123"; + try { + Session session(config2); + SUCCEED(); + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } + + // Token with special characters should be accepted + SessionConfig config3; + config3.token = "very_long_token_with_special_characters_!@#$%^&*()"; + try { + Session session(config3); + SUCCEED(); + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } +} + +TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { + // Valid HTTPS URL + SessionConfig config1; + config1.authUrl = "https://example.com"; + try { + Session session(config1); + SUCCEED(); + } catch (const std::runtime_error&) { + // Either not supported or validation failed - both acceptable + SUCCEED(); + } + + // Valid HTTP URL with port and path + SessionConfig config2; + config2.authUrl = "http://auth.server.com:8080/api"; + try { + Session session(config2); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Valid HTTPS URL with query parameters + SessionConfig config3; + config3.authUrl = "https://auth.example.com/token?param=value"; + try { + Session session(config3); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Invalid URL - not a URL at all + SessionConfig config4; + config4.authUrl = "not-a-url"; + EXPECT_THROW({ Session session(config4); }, std::runtime_error); + + // Invalid URL - unsupported protocol + SessionConfig config5; + config5.authUrl = "ftp://invalid.com"; + EXPECT_THROW({ Session session(config5); }, std::runtime_error); + + // Invalid URL - missing protocol + SessionConfig config6; + config6.authUrl = "example.com"; + EXPECT_THROW({ Session session(config6); }, std::runtime_error); +} + +TEST(AuthenticationTest, SessionConstructionWithAuthFile) { + // Test with non-existent file - should raise error + SessionConfig config1; + config1.authFile = "/nonexistent/path/to/file.txt"; + EXPECT_THROW({ Session session(config1); }, std::runtime_error); + + // Test with another non-existent file + SessionConfig config2; + config2.authFile = "/tmp/this_file_does_not_exist_12345.txt"; + EXPECT_THROW({ Session session(config2); }, std::runtime_error); + + // Test with existing file + // Create a temporary file + char tmpFilename[] = "/tmp/fomac_test_XXXXXX"; + const int fd = mkstemp(tmpFilename); + ASSERT_NE(fd, -1) << "Failed to create temporary file"; + + // Write some content to the file + const char* content = "test_token_content"; + write(fd, content, strlen(content)); + close(fd); + + // Try to create session with existing file + SessionConfig config3; + config3.authFile = tmpFilename; + try { + Session session(config3); + SUCCEED(); + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } + + // Clean up + remove(tmpFilename); +} + +TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { + // Username only + SessionConfig config1; + config1.username = "user123"; + try { + Session session(config1); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Password only + SessionConfig config2; + config2.password = "secure_password"; + try { + Session session(config2); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Both username and password + SessionConfig config3; + config3.username = "user123"; + config3.password = "secure_password"; + try { + Session session(config3); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } +} + +TEST(AuthenticationTest, SessionConstructionWithProjectId) { + SessionConfig config; + config.projectId = "project-123-abc"; + try { + Session session(config); + SUCCEED(); + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } +} + +TEST(AuthenticationTest, SessionConstructionWithMultipleParameters) { + SessionConfig config; + config.token = "test_token"; + config.username = "test_user"; + config.password = "test_pass"; + config.projectId = "test_project"; + try { + Session session(config); + SUCCEED(); + } catch (const std::runtime_error&) { + // If not supported, that's okay for now + SUCCEED(); + } +} + +TEST(AuthenticationTest, SessionGetDevicesReturnsList) { + Session session; + auto devices = session.getDevices(); + + EXPECT_FALSE(devices.empty()); + + // All elements should be Device instances + for (const auto& device : devices) { + // Device should have a name + EXPECT_FALSE(device.getName().empty()); + } +} + +TEST(AuthenticationTest, SessionMultipleInstances) { + Session session1; + Session session2; + + auto devices1 = session1.getDevices(); + auto devices2 = session2.getDevices(); + + // Both should return devices + EXPECT_FALSE(devices1.empty()); + EXPECT_FALSE(devices2.empty()); + + // Should return the same number of devices + EXPECT_EQ(devices1.size(), devices2.size()); +} + namespace { // Helper function to get all devices for parameterized tests inline auto getDevices() -> std::vector { diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index e55ebbad3b..24873987d3 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -184,10 +184,10 @@ def test_provider_with_multiple_auth_parameters() -> None: # Should not raise an error when creating provider with multiple auth parameters # Note: The currently available QDMI devices don't support authentication. try: - provider = QDMIProvider( # noqa: S106 - token="test_token", + provider = QDMIProvider( + token="test_token", # noqa: S106 username="test_user", - password="test_pass", + password="test_pass", # noqa: S106 project_id="test_project", ) assert provider is not None @@ -212,4 +212,3 @@ def test_provider_default_constructor_unchanged() -> None: # Should be able to get a specific backend backend = provider.get_backend("MQT Core DDSIM QDMI Device") assert backend.name == "MQT Core DDSIM QDMI Device" - From b4f495c56fc96d3b4c99544549793703a58d8de2 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 21:51:25 +0100 Subject: [PATCH 20/72] :rotating_light: Remove unused header --- include/mqt-core/fomac/FoMaC.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index a3d3e803a9..44cc6fc23c 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include From 25b13ab80c19aa482f49c9dc6073dba1af942f75 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 22:04:30 +0100 Subject: [PATCH 21/72] :memo: Documentation on session authentication --- docs/qdmi/qdmi_backend.md | 106 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/docs/qdmi/qdmi_backend.md b/docs/qdmi/qdmi_backend.md index 76b8e756d5..e6d0517e8c 100644 --- a/docs/qdmi/qdmi_backend.md +++ b/docs/qdmi/qdmi_backend.md @@ -102,6 +102,112 @@ filtered_ddsim = provider.backends(name="DDSIM") # Matches "MQT Core DDSIM QDMI exact = provider.backends(name="MQT Core DDSIM QDMI Device") ``` +## Authentication + +The {py:class}`~mqt.core.plugins.qiskit.QDMIProvider` supports authentication for accessing QDMI devices that require credentials. +Authentication parameters are passed to the provider constructor and forwarded to the underlying session. + +:::{note} +The default local devices (MQT Core DDSIM QDMI Device, MQT NA Default QDMI Device) do not require authentication. +Authentication is primarily used when connecting to remote quantum hardware. +::: + +### Supported Authentication Methods + +The provider supports multiple authentication methods: + +- **Token-based authentication**: Using an API token or access token +- **Username/password authentication**: Traditional credential-based authentication +- **File-based authentication**: Reading credentials from a file +- **URL-based authentication**: Connecting to an authentication server +- **Project-based authentication**: Associating sessions with specific projects, e.g., for accounting or quota management + +### Using Authentication Tokens + +The most common authentication method is using an API token: + +```python +from mqt.core.plugins.qiskit import QDMIProvider + +# Authenticate with a token +provider = QDMIProvider(token="your_api_token_here") + +# Get backends +backends = provider.backends() +for backend in backends: + print(f"{backend.name}: {backend.target.num_qubits} qubits") +``` + +### Username and Password Authentication + +For services that use traditional username/password authentication: + +```python +# Authenticate with username and password +provider = QDMIProvider(username="your_username", password="your_password") + +# Access backend +backend = provider.get_backend("RemoteQuantumDevice") +``` + +### File-Based Authentication + +Store credentials in a secure file for better security: + +```python +# Authenticate using a credentials file +# The file should contain authentication information in the format expected by the service +provider = QDMIProvider(auth_file="/path/to/credentials.txt") +``` + +### Authentication Server URL + +Connect to a custom authentication server: + +```python +# Use a custom authentication URL +provider = QDMIProvider(auth_url="https://auth.quantum-service.com/api/v1/auth") +``` + +### Project-Based Authentication + +Associate your session with a specific project or organization: + +```python +# Specify a project ID +provider = QDMIProvider( + token="your_api_token", project_id="quantum-research-project-2024" +) +``` + +### Combining Authentication Parameters + +Multiple authentication parameters can be combined for services that require multiple credentials: + +```python +# Use multiple authentication parameters +provider = QDMIProvider( + token="your_api_token", + username="your_username", + password="your_password", + project_id="your_project_id", + auth_url="https://custom-auth.example.com", +) +``` + +### Authentication Error Handling + +When authentication fails, the provider raises a `RuntimeError`: + +```python +try: + provider = QDMIProvider(token="invalid_token") + backends = provider.backends() +except RuntimeError as e: + print(f"Authentication failed: {e}") + # Handle authentication error (e.g., prompt for valid credentials) +``` + ## Device Capabilities and Target The backend automatically introspects the FoMaC (QDMI) device and constructs a Qiskit {py:class}`~qiskit.transpiler.Target` From 4dae4f1df73d926095b5a6c08c9be800f231bff0 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 22:04:38 +0100 Subject: [PATCH 22/72] :memo: CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 173a0c3d6b..5e7d51e9b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add authentication support for QDMI sessions with token, username/password, auth file, auth URL, and project ID parameters ([#1355]) ([**@marcelwa**]) - ✨ Add bi-directional iterator that traverses the def-use chain of a qubit value ([#1310]) ([**@MatthiasReumann**]) - ✨ Add `OptionalDependencyTester` to lazily handle optional Python dependencies like Qiskit ([#1243]) ([**@marcelwa**], [**@burgholzer**]) - ✨ Expose the QDMI job interface through FoMaC ([#1243]) ([**@marcelwa**], [**@burgholzer**]) @@ -20,6 +21,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**]) - 👷 Use `munich-quantum-software/setup-mlir` to set up MLIR ([#1294]) ([**@denialhaag**]) - ♻️ Preserve tuple structure and improve site type clarity of the MQT NA Default QDMI Device ([#1299]) ([**@marcelwa**]) - ♻️ Move DD package evaluation module to standalone script ([#1327]) ([**@burgholzer**]) @@ -263,6 +265,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1355]: https://github.com/munich-quantum-toolkit/core/pull/1355 [#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336 [#1327]: https://github.com/munich-quantum-toolkit/core/pull/1327 [#1310]: https://github.com/munich-quantum-toolkit/core/pull/1310 From 5655d593bfe93311b05a0f0ca98446cb9fa96644 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 22:13:12 +0100 Subject: [PATCH 23/72] :rotating_light: Fix `clang-tidy` warnings --- test/fomac/test_fomac.cpp | 61 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 5dfdec9b9d..48afa7ea10 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -13,8 +13,8 @@ #include #include #include -#include -#include +#include +#include #include #include #include @@ -24,7 +24,6 @@ #include #include #include -#include #include namespace fomac { @@ -620,7 +619,7 @@ TEST(AuthenticationTest, SessionConstructionWithToken) { SessionConfig config1; config1.token = ""; try { - Session session(config1); + const Session session(config1); SUCCEED(); // If we get here, the session was created successfully } catch (const std::runtime_error&) { // If not supported, that's okay for now @@ -631,7 +630,7 @@ TEST(AuthenticationTest, SessionConstructionWithToken) { SessionConfig config2; config2.token = "test_token_123"; try { - Session session(config2); + const Session session(config2); SUCCEED(); } catch (const std::runtime_error&) { // If not supported, that's okay for now @@ -642,7 +641,7 @@ TEST(AuthenticationTest, SessionConstructionWithToken) { SessionConfig config3; config3.token = "very_long_token_with_special_characters_!@#$%^&*()"; try { - Session session(config3); + const Session session(config3); SUCCEED(); } catch (const std::runtime_error&) { // If not supported, that's okay for now @@ -655,7 +654,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { SessionConfig config1; config1.authUrl = "https://example.com"; try { - Session session(config1); + const Session session(config1); SUCCEED(); } catch (const std::runtime_error&) { // Either not supported or validation failed - both acceptable @@ -666,7 +665,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { SessionConfig config2; config2.authUrl = "http://auth.server.com:8080/api"; try { - Session session(config2); + const Session session(config2); SUCCEED(); } catch (const std::runtime_error&) { SUCCEED(); @@ -676,7 +675,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { SessionConfig config3; config3.authUrl = "https://auth.example.com/token?param=value"; try { - Session session(config3); + const Session session(config3); SUCCEED(); } catch (const std::runtime_error&) { SUCCEED(); @@ -685,46 +684,46 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { // Invalid URL - not a URL at all SessionConfig config4; config4.authUrl = "not-a-url"; - EXPECT_THROW({ Session session(config4); }, std::runtime_error); + EXPECT_THROW({ const Session session(config4); }, std::runtime_error); // Invalid URL - unsupported protocol SessionConfig config5; config5.authUrl = "ftp://invalid.com"; - EXPECT_THROW({ Session session(config5); }, std::runtime_error); + EXPECT_THROW({ const Session session(config5); }, std::runtime_error); // Invalid URL - missing protocol SessionConfig config6; config6.authUrl = "example.com"; - EXPECT_THROW({ Session session(config6); }, std::runtime_error); + EXPECT_THROW({ const Session session(config6); }, std::runtime_error); } TEST(AuthenticationTest, SessionConstructionWithAuthFile) { // Test with non-existent file - should raise error SessionConfig config1; config1.authFile = "/nonexistent/path/to/file.txt"; - EXPECT_THROW({ Session session(config1); }, std::runtime_error); + EXPECT_THROW({ const Session session(config1); }, std::runtime_error); // Test with another non-existent file SessionConfig config2; config2.authFile = "/tmp/this_file_does_not_exist_12345.txt"; - EXPECT_THROW({ Session session(config2); }, std::runtime_error); + EXPECT_THROW({ const Session session(config2); }, std::runtime_error); // Test with existing file - // Create a temporary file - char tmpFilename[] = "/tmp/fomac_test_XXXXXX"; - const int fd = mkstemp(tmpFilename); - ASSERT_NE(fd, -1) << "Failed to create temporary file"; - - // Write some content to the file - const char* content = "test_token_content"; - write(fd, content, strlen(content)); - close(fd); + const auto tempDir = std::filesystem::temp_directory_path(); + auto tmpPath = tempDir / "fomac_test_auth_XXXXXX.txt"; + + // Create and write to the temporary file + { + std::ofstream tmpFile(tmpPath); + ASSERT_TRUE(tmpFile.is_open()) << "Failed to create temporary file"; + tmpFile << "test_token_content"; + } // Try to create session with existing file SessionConfig config3; - config3.authFile = tmpFilename; + config3.authFile = tmpPath.string(); try { - Session session(config3); + const Session session(config3); SUCCEED(); } catch (const std::runtime_error&) { // If not supported, that's okay for now @@ -732,7 +731,7 @@ TEST(AuthenticationTest, SessionConstructionWithAuthFile) { } // Clean up - remove(tmpFilename); + std::filesystem::remove(tmpPath); } TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { @@ -740,7 +739,7 @@ TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { SessionConfig config1; config1.username = "user123"; try { - Session session(config1); + const Session session(config1); SUCCEED(); } catch (const std::runtime_error&) { SUCCEED(); @@ -750,7 +749,7 @@ TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { SessionConfig config2; config2.password = "secure_password"; try { - Session session(config2); + const Session session(config2); SUCCEED(); } catch (const std::runtime_error&) { SUCCEED(); @@ -761,7 +760,7 @@ TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { config3.username = "user123"; config3.password = "secure_password"; try { - Session session(config3); + const Session session(config3); SUCCEED(); } catch (const std::runtime_error&) { SUCCEED(); @@ -772,7 +771,7 @@ TEST(AuthenticationTest, SessionConstructionWithProjectId) { SessionConfig config; config.projectId = "project-123-abc"; try { - Session session(config); + const Session session(config); SUCCEED(); } catch (const std::runtime_error&) { // If not supported, that's okay for now @@ -787,7 +786,7 @@ TEST(AuthenticationTest, SessionConstructionWithMultipleParameters) { config.password = "test_pass"; config.projectId = "test_project"; try { - Session session(config); + const Session session(config); SUCCEED(); } catch (const std::runtime_error&) { // If not supported, that's okay for now From bed169ba333172947060336bb23ed65f0921bd01 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Thu, 4 Dec 2025 22:16:57 +0100 Subject: [PATCH 24/72] :art: Light code cleanup --- src/fomac/FoMaC.cpp | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index d5fefa2795..7c403dd684 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -816,13 +816,11 @@ Session::Session(const SessionConfig& config) { // Set and validate parameters from config if (config.token) { - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_TOKEN); + const auto qdmiParam = QDMI_SESSION_PARAMETER_TOKEN; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.token->size() + 1, config.token->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_TOKEN)); + "Setting session parameter " + toString(qdmiParam)); } if (config.authFile) { // Validate file existence @@ -830,13 +828,11 @@ Session::Session(const SessionConfig& config) { throw std::runtime_error("Authentication file does not exist: " + *config.authFile); } - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_AUTHFILE); + const auto qdmiParam = QDMI_SESSION_PARAMETER_AUTHFILE; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.authFile->size() + 1, config.authFile->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_AUTHFILE)); + "Setting session parameter " + toString(qdmiParam)); } if (config.authUrl) { // Validate URL format according to: https://uibakery.io/regex-library/url @@ -845,40 +841,32 @@ Session::Session(const SessionConfig& config) { if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_AUTHURL); + const auto qdmiParam = QDMI_SESSION_PARAMETER_AUTHURL; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.authUrl->size() + 1, config.authUrl->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_AUTHURL)); + "Setting session parameter " + toString(qdmiParam)); } if (config.username) { - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_USERNAME); + const auto qdmiParam = QDMI_SESSION_PARAMETER_USERNAME; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.username->size() + 1, config.username->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_USERNAME)); + "Setting session parameter " + toString(qdmiParam)); } if (config.password) { - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_PASSWORD); + const auto qdmiParam = QDMI_SESSION_PARAMETER_PASSWORD; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.password->size() + 1, config.password->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_PASSWORD)); + "Setting session parameter " + toString(qdmiParam)); } if (config.projectId) { - const auto qdmiParam = - static_cast(QDMI_SESSION_PARAMETER_PROJECTID); + const auto qdmiParam = QDMI_SESSION_PARAMETER_PROJECTID; throwIfError(QDMI_session_set_parameter(session_, qdmiParam, config.projectId->size() + 1, config.projectId->c_str()), - "Setting session parameter " + - toString(QDMI_SESSION_PARAMETER_PROJECTID)); + "Setting session parameter " + toString(qdmiParam)); } // Initialize the session From 3206c7d6fa50ddcdd51347779f0702fab484470a Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 10:51:17 +0100 Subject: [PATCH 25/72] :sparkles: Expose CUSTOM `QDMI_Program_Format` parameters --- bindings/fomac/fomac.cpp | 46 ++++++++++++------- include/mqt-core/fomac/FoMaC.hpp | 10 +++++ python/mqt/core/fomac.pyi | 10 +++++ python/mqt/core/plugins/qiskit/provider.py | 15 +++++++ src/fomac/FoMaC.cpp | 35 +++++++++++++++ test/fomac/test_fomac.cpp | 49 +++++++++++++++++++++ test/python/fomac/test_fomac.py | 45 +++++++++++++++++++ test/python/plugins/qiskit/test_provider.py | 35 +++++++++++++++ 8 files changed, 228 insertions(+), 17 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index ef148e1d2c..412e40bb73 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -31,27 +31,39 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { const fomac::SessionConfig defaultConfig; session.def( - py::init( - [](const std::optional& token, - const std::optional& authFile, - const std::optional& authUrl, - const std::optional& username, - const std::optional& password, - const std::optional& projectId) -> fomac::Session { - fomac::SessionConfig config; - config.token = token; - config.authFile = authFile; - config.authUrl = authUrl; - config.username = username; - config.password = password; - config.projectId = projectId; - return fomac::Session{config}; - }), + py::init([](const std::optional& token, + const std::optional& authFile, + const std::optional& authUrl, + const std::optional& username, + const std::optional& password, + const std::optional& projectId, + const std::optional& custom1, + const std::optional& custom2, + const std::optional& custom3, + const std::optional& custom4, + const std::optional& custom5) -> fomac::Session { + fomac::SessionConfig config; + config.token = token; + config.authFile = authFile; + config.authUrl = authUrl; + config.username = username; + config.password = password; + config.projectId = projectId; + config.custom1 = custom1; + config.custom2 = custom2; + config.custom3 = custom3; + config.custom4 = custom4; + config.custom5 = custom5; + return fomac::Session{config}; + }), "token"_a = defaultConfig.token, "auth_file"_a = defaultConfig.authFile, "auth_url"_a = defaultConfig.authUrl, "username"_a = defaultConfig.username, "password"_a = defaultConfig.password, - "project_id"_a = defaultConfig.projectId); + "project_id"_a = defaultConfig.projectId, + "custom1"_a = defaultConfig.custom1, "custom2"_a = defaultConfig.custom2, + "custom3"_a = defaultConfig.custom3, "custom4"_a = defaultConfig.custom4, + "custom5"_a = defaultConfig.custom5); session.def("get_devices", &fomac::Session::getDevices); diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 44cc6fc23c..79e87392a4 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -199,6 +199,16 @@ struct SessionConfig { std::optional password; /// Project ID for session std::optional projectId; + /// Custom configuration parameter 1 + std::optional custom1; + /// Custom configuration parameter 2 + std::optional custom2; + /// Custom configuration parameter 3 + std::optional custom3; + /// Custom configuration parameter 4 + std::optional custom4; + /// Custom configuration parameter 5 + std::optional custom5; }; /** diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 16187e5a91..e0399b38cc 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -51,6 +51,11 @@ class Session: username: str | None = None, password: str | None = None, project_id: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, ) -> None: """Create a new FoMaC session with optional authentication. @@ -61,6 +66,11 @@ class Session: username: Username for authentication password: Password for authentication project_id: Project ID for session + custom1: Custom configuration parameter 1 + custom2: Custom configuration parameter 2 + custom3: Custom configuration parameter 3 + custom4: Custom configuration parameter 4 + custom5: Custom configuration parameter 5 Raises: RuntimeError: If auth_file does not exist diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index 014de28310..bf5a4b72ce 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -58,6 +58,11 @@ def __init__( username: str | None = None, password: str | None = None, project_id: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, ) -> None: """Initialize the QDMI provider. @@ -68,6 +73,11 @@ def __init__( username: Username for authentication. password: Password for authentication. project_id: Project ID for the session. + custom1: Custom configuration parameter 1. + custom2: Custom configuration parameter 2. + custom3: Custom configuration parameter 3. + custom4: Custom configuration parameter 4. + custom5: Custom configuration parameter 5. """ session = fomac.Session( token=token, @@ -76,6 +86,11 @@ def __init__( username=username, password=password, project_id=project_id, + custom1=custom1, + custom2=custom2, + custom3=custom3, + custom4=custom4, + custom5=custom5, ) self._backends = [ QDMIBackend(device=d, provider=self) for d in session.get_devices() if QDMIBackend.is_convertible(d) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 7c403dd684..593ec3850a 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -868,6 +868,41 @@ Session::Session(const SessionConfig& config) { config.projectId->c_str()), "Setting session parameter " + toString(qdmiParam)); } + if (config.custom1) { + const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM1; + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.custom1->size() + 1, + config.custom1->c_str()), + "Setting session parameter " + toString(qdmiParam)); + } + if (config.custom2) { + const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM2; + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.custom2->size() + 1, + config.custom2->c_str()), + "Setting session parameter " + toString(qdmiParam)); + } + if (config.custom3) { + const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM3; + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.custom3->size() + 1, + config.custom3->c_str()), + "Setting session parameter " + toString(qdmiParam)); + } + if (config.custom4) { + const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM4; + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.custom4->size() + 1, + config.custom4->c_str()), + "Setting session parameter " + toString(qdmiParam)); + } + if (config.custom5) { + const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM5; + throwIfError(QDMI_session_set_parameter(session_, qdmiParam, + config.custom5->size() + 1, + config.custom5->c_str()), + "Setting session parameter " + toString(qdmiParam)); + } // Initialize the session throwIfError(QDMI_session_init(session_), "Initializing session"); diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 48afa7ea10..990d2a55c7 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -822,6 +822,55 @@ TEST(AuthenticationTest, SessionMultipleInstances) { EXPECT_EQ(devices1.size(), devices2.size()); } +TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { + // Test custom1 + SessionConfig config1; + config1.custom1 = "custom_value_1"; + try { + const Session session(config1); + SUCCEED(); + } catch (const std::exception&) { + // If not supported, that's okay for now + SUCCEED(); + } + + // Test custom2 + SessionConfig config2; + config2.custom2 = "custom_value_2"; + try { + const Session session(config2); + SUCCEED(); + } catch (const std::exception&) { + SUCCEED(); + } + + // Test all custom parameters together + SessionConfig config3; + config3.custom1 = "value1"; + config3.custom2 = "value2"; + config3.custom3 = "value3"; + config3.custom4 = "value4"; + config3.custom5 = "value5"; + try { + const Session session(config3); + SUCCEED(); + } catch (const std::exception&) { + SUCCEED(); + } + + // Test mixing custom parameters with standard authentication + SessionConfig config4; + config4.token = "test_token"; + config4.custom1 = "custom_value"; + config4.projectId = "project_id"; + try { + const Session session(config4); + SUCCEED(); + } catch (const std::exception&) { + SUCCEED(); + } +} + namespace { // Helper function to get all devices for parameterized tests inline auto getDevices() -> std::vector { diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index c1a24fd565..14bbba5238 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -787,3 +787,48 @@ def test_session_multiple_instances() -> None: # Should return the same number of devices assert len(devices1) == len(devices2) + + +def test_session_construction_with_custom_parameters() -> None: + """Test Session construction with custom configuration parameters. + + Note: The currently available QDMI devices don't support custom parameters. + """ + # Test custom1 + try: + session = Session(custom1="custom_value_1") + assert session is not None + except (RuntimeError, ValueError): + # If not supported, that's okay for now + pass + + # Test custom2 + try: + session = Session(custom2="custom_value_2") + assert session is not None + except (RuntimeError, ValueError): + pass + + # Test all custom parameters together + try: + session = Session( + custom1="value1", + custom2="value2", + custom3="value3", + custom4="value4", + custom5="value5", + ) + assert session is not None + except (RuntimeError, ValueError): + pass + + # Test mixing custom parameters with standard authentication + try: + session = Session( + token="test_token", # noqa: S106 + custom1="custom_value", + project_id="project_id", + ) + assert session is not None + except (RuntimeError, ValueError): + pass diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index 24873987d3..7865fb26a5 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -212,3 +212,38 @@ def test_provider_default_constructor_unchanged() -> None: # Should be able to get a specific backend backend = provider.get_backend("MQT Core DDSIM QDMI Device") assert backend.name == "MQT Core DDSIM QDMI Device" + + +def test_provider_with_custom_parameters() -> None: + """Provider accepts custom configuration parameters.""" + # Test custom1 + try: + provider = QDMIProvider(custom1="custom_value_1") + assert provider is not None + except (RuntimeError, ValueError): + # If not supported, that's okay for now + pass + + # Test all custom parameters together + try: + provider = QDMIProvider( + custom1="value1", + custom2="value2", + custom3="value3", + custom4="value4", + custom5="value5", + ) + assert provider is not None + except (RuntimeError, ValueError): + pass + + # Test mixing custom with standard authentication + try: + provider = QDMIProvider( + token="test_token", # noqa: S106 + custom1="custom_value", + project_id="project_id", + ) + assert provider is not None + except (RuntimeError, ValueError): + pass From 77ed8856d09a7e1d129d6d99a3201884bce01e81 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 11:07:39 +0100 Subject: [PATCH 26/72] :art: Cleanup Session constructor --- src/fomac/FoMaC.cpp | 101 +++++++++++--------------------------------- 1 file changed, 25 insertions(+), 76 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 593ec3850a..78d81080cf 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -814,96 +814,45 @@ Session::Session(const SessionConfig& config) { const auto result = QDMI_session_alloc(&session_); throwIfError(result, "Allocating QDMI session"); - // Set and validate parameters from config - if (config.token) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_TOKEN; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.token->size() + 1, - config.token->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } + // Helper to set session parameters + const auto setParameter = [this](const std::optional& value, + QDMI_Session_Parameter param) { + if (value) { + throwIfError(QDMI_session_set_parameter( + session_, param, value->size() + 1, value->c_str()), + "Setting session parameter " + toString(param)); + } + }; + // Validate file existence for authFile if (config.authFile) { - // Validate file existence if (!std::filesystem::exists(*config.authFile)) { throw std::runtime_error("Authentication file does not exist: " + *config.authFile); } - const auto qdmiParam = QDMI_SESSION_PARAMETER_AUTHFILE; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.authFile->size() + 1, - config.authFile->c_str()), - "Setting session parameter " + toString(qdmiParam)); } + // Validate URL format for authUrl if (config.authUrl) { - // Validate URL format according to: https://uibakery.io/regex-library/url + // Adapted from: https://uibakery.io/regex-library/url static const std::regex URL_PATTERN( R"(^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } - const auto qdmiParam = QDMI_SESSION_PARAMETER_AUTHURL; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.authUrl->size() + 1, - config.authUrl->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.username) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_USERNAME; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.username->size() + 1, - config.username->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.password) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_PASSWORD; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.password->size() + 1, - config.password->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.projectId) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_PROJECTID; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.projectId->size() + 1, - config.projectId->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.custom1) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM1; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.custom1->size() + 1, - config.custom1->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.custom2) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM2; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.custom2->size() + 1, - config.custom2->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.custom3) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM3; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.custom3->size() + 1, - config.custom3->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.custom4) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM4; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.custom4->size() + 1, - config.custom4->c_str()), - "Setting session parameter " + toString(qdmiParam)); - } - if (config.custom5) { - const auto qdmiParam = QDMI_SESSION_PARAMETER_CUSTOM5; - throwIfError(QDMI_session_set_parameter(session_, qdmiParam, - config.custom5->size() + 1, - config.custom5->c_str()), - "Setting session parameter " + toString(qdmiParam)); } + // Set session parameters + setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); + setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); + setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); + // Initialize the session throwIfError(QDMI_session_init(session_), "Initializing session"); } From fe6b06778e763c9fa34b96be855adf5a9909ecce Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 11:33:39 +0100 Subject: [PATCH 27/72] :art: Cleanup Session constructor in bindings --- bindings/fomac/fomac.cpp | 61 +++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 412e40bb73..91028ed761 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -29,41 +29,38 @@ using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { auto session = py::class_(m, "Session"); - const fomac::SessionConfig defaultConfig; session.def( - py::init([](const std::optional& token, - const std::optional& authFile, - const std::optional& authUrl, - const std::optional& username, - const std::optional& password, - const std::optional& projectId, - const std::optional& custom1, - const std::optional& custom2, - const std::optional& custom3, - const std::optional& custom4, - const std::optional& custom5) -> fomac::Session { - fomac::SessionConfig config; - config.token = token; - config.authFile = authFile; - config.authUrl = authUrl; - config.username = username; - config.password = password; - config.projectId = projectId; - config.custom1 = custom1; - config.custom2 = custom2; - config.custom3 = custom3; - config.custom4 = custom4; - config.custom5 = custom5; + py::init([](const std::optional& token = std::nullopt, + const std::optional& authFile = std::nullopt, + const std::optional& authUrl = std::nullopt, + const std::optional& username = std::nullopt, + const std::optional& password = std::nullopt, + const std::optional& projectId = std::nullopt, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = + std::nullopt) -> fomac::Session { + fomac::SessionConfig config{.token = token, + .authFile = authFile, + .authUrl = authUrl, + .username = username, + .password = password, + .projectId = projectId, + .custom1 = custom1, + .custom2 = custom2, + .custom3 = custom3, + .custom4 = custom4, + .custom5 = custom5}; return fomac::Session{config}; }), - "token"_a = defaultConfig.token, "auth_file"_a = defaultConfig.authFile, - "auth_url"_a = defaultConfig.authUrl, - "username"_a = defaultConfig.username, - "password"_a = defaultConfig.password, - "project_id"_a = defaultConfig.projectId, - "custom1"_a = defaultConfig.custom1, "custom2"_a = defaultConfig.custom2, - "custom3"_a = defaultConfig.custom3, "custom4"_a = defaultConfig.custom4, - "custom5"_a = defaultConfig.custom5); + "token"_a = std::nullopt, "auth_file"_a = std::nullopt, + "auth_url"_a = std::nullopt, "username"_a = std::nullopt, + "password"_a = std::nullopt, "project_id"_a = std::nullopt, + "custom1"_a = std::nullopt, "custom2"_a = std::nullopt, + "custom3"_a = std::nullopt, "custom4"_a = std::nullopt, + "custom5"_a = std::nullopt); session.def("get_devices", &fomac::Session::getDevices); From 8f1b299828582a674bbe1953706e6a2c15e3a19e Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 12:22:25 +0100 Subject: [PATCH 28/72] :rotating_light: Fix `clang-tidy` warnings --- bindings/fomac/fomac.cpp | 22 +++++++++++----------- test/fomac/test_fomac.cpp | 1 + 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 91028ed761..6f4dd790ca 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -42,17 +42,17 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { const std::optional& custom4 = std::nullopt, const std::optional& custom5 = std::nullopt) -> fomac::Session { - fomac::SessionConfig config{.token = token, - .authFile = authFile, - .authUrl = authUrl, - .username = username, - .password = password, - .projectId = projectId, - .custom1 = custom1, - .custom2 = custom2, - .custom3 = custom3, - .custom4 = custom4, - .custom5 = custom5}; + const fomac::SessionConfig config{.token = token, + .authFile = authFile, + .authUrl = authUrl, + .username = username, + .password = password, + .projectId = projectId, + .custom1 = custom1, + .custom2 = custom2, + .custom3 = custom3, + .custom4 = custom4, + .custom5 = custom5}; return fomac::Session{config}; }), "token"_a = std::nullopt, "auth_file"_a = std::nullopt, diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 8b091d378e..d12311fc46 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include From 7c8047ab7166bfa9c404635ea70f8e3cc7d396cb Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 12:33:31 +0100 Subject: [PATCH 29/72] :safety_vest: Ensure partially instantiated sessions get cleaned up properly in case of errors --- src/fomac/FoMaC.cpp | 73 ++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 78d81080cf..17eabc3b43 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -814,47 +814,60 @@ Session::Session(const SessionConfig& config) { const auto result = QDMI_session_alloc(&session_); throwIfError(result, "Allocating QDMI session"); + // Helper to ensure session is freed if an exception is thrown during setup + const auto cleanup = [this]() -> void { + if (session_ != nullptr) { + QDMI_session_free(session_); + session_ = nullptr; + } + }; // Helper to set session parameters const auto setParameter = [this](const std::optional& value, - QDMI_Session_Parameter param) { + QDMI_Session_Parameter param) -> void { if (value) { throwIfError(QDMI_session_set_parameter( session_, param, value->size() + 1, value->c_str()), "Setting session parameter " + toString(param)); } }; - // Validate file existence for authFile - if (config.authFile) { - if (!std::filesystem::exists(*config.authFile)) { - throw std::runtime_error("Authentication file does not exist: " + - *config.authFile); + + try { + // Validate file existence for authFile + if (config.authFile) { + if (!std::filesystem::exists(*config.authFile)) { + throw std::runtime_error("Authentication file does not exist: " + + *config.authFile); + } } - } - // Validate URL format for authUrl - if (config.authUrl) { - // Adapted from: https://uibakery.io/regex-library/url - static const std::regex URL_PATTERN( - R"(^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); - if (!std::regex_match(*config.authUrl, URL_PATTERN)) { - throw std::runtime_error("Invalid URL format: " + *config.authUrl); + // Validate URL format for authUrl + if (config.authUrl) { + // Adapted from: https://uibakery.io/regex-library/url + static const std::regex URL_PATTERN( + R"(^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); + if (!std::regex_match(*config.authUrl, URL_PATTERN)) { + throw std::runtime_error("Invalid URL format: " + *config.authUrl); + } } - } - // Set session parameters - setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); - setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); - setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); - setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); - setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); - setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); - setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); - setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); - setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); - setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); - setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); - - // Initialize the session - throwIfError(QDMI_session_init(session_), "Initializing session"); + // Set session parameters + setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); + setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); + setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); + + // Initialize the session + throwIfError(QDMI_session_init(session_), "Initializing session"); + } catch (...) { + cleanup(); + throw; + } } Session::~Session() { From 5680e80279c826135c2d8a0b7c4eefbea7fdd3e6 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 12:45:53 +0100 Subject: [PATCH 30/72] :art: Implement CodeRabbit's suggestions --- include/mqt-core/fomac/FoMaC.hpp | 2 +- test/fomac/test_fomac.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 79e87392a4..53ae902511 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -736,7 +736,7 @@ class Session { /** * @brief Destructor that releases the QDMI session. */ - virtual ~Session(); + ~Session(); // Delete copy constructors and assignment operators Session(const Session&) = delete; diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index d12311fc46..f417a0b757 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -11,7 +11,6 @@ #include "fomac/FoMaC.hpp" #include -#include #include #include #include @@ -24,6 +23,7 @@ #include #include #include +#include #include #include @@ -732,6 +732,7 @@ TEST(AuthenticationTest, SessionParameterToString) { EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECTID"); + EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); @@ -835,7 +836,10 @@ TEST(AuthenticationTest, SessionConstructionWithAuthFile) { // Test with existing file const auto tempDir = std::filesystem::temp_directory_path(); - auto tmpPath = tempDir / "fomac_test_auth_XXXXXX.txt"; + auto tmpPath = tempDir / ("fomac_test_auth_" + + std::to_string(std::hash{}( + std::this_thread::get_id())) + + ".txt"); // Create and write to the temporary file { @@ -998,7 +1002,7 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { namespace { // Helper function to get all devices for parameterized tests -inline auto getDevices() -> std::vector { +auto getDevices() -> std::vector { Session session; return session.getDevices(); } From 3d8d4025d71182fad461b9baf47685143c39ca23 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 13:00:23 +0100 Subject: [PATCH 31/72] :recycle: Refactor URL validation to allow local URLs and IPs --- src/fomac/FoMaC.cpp | 2 +- test/fomac/test_fomac.cpp | 30 ++++++++++++++++++++++++++++++ test/python/fomac/test_fomac.py | 18 ++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 17eabc3b43..fcd58f6348 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -843,7 +843,7 @@ Session::Session(const SessionConfig& config) { if (config.authUrl) { // Adapted from: https://uibakery.io/regex-library/url static const std::regex URL_PATTERN( - R"(^https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); + R"(^https?://(?:localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index f417a0b757..a46c988a1d 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -807,6 +807,36 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { SUCCEED(); } + // Valid localhost URL + SessionConfig configLocalhost; + configLocalhost.authUrl = "http://localhost"; + try { + const Session session(configLocalhost); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Valid localhost URL with port + SessionConfig configLocalhostPort; + configLocalhostPort.authUrl = "http://localhost:8080"; + try { + const Session session(configLocalhostPort); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + + // Valid localhost URL with port and path + SessionConfig configLocalhostPath; + configLocalhostPath.authUrl = "https://localhost:3000/auth/api"; + try { + const Session session(configLocalhostPath); + SUCCEED(); + } catch (const std::runtime_error&) { + SUCCEED(); + } + // Invalid URL - not a URL at all SessionConfig config4; config4.authUrl = "not-a-url"; diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 9ca9e998c6..441a363cd1 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -657,6 +657,24 @@ def test_session_construction_with_auth_url() -> None: except RuntimeError: # Either not supported or validation failed - both acceptable pass + # Valid localhost URL + try: + session = Session(auth_url="http://localhost") + assert session is not None + except RuntimeError: + pass + # Valid localhost URL with port + try: + session = Session(auth_url="http://localhost:8080") + assert session is not None + except RuntimeError: + pass + # Valid localhost URL with port and path + try: + session = Session(auth_url="https://localhost:3000/auth/api") + assert session is not None + except RuntimeError: + pass # Invalid URL - not a URL at all with pytest.raises(RuntimeError): Session(auth_url="not-a-url") From 9843d31f956dc6d1a9ff0f65c55bd4a713629f9a Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 15:17:54 +0100 Subject: [PATCH 32/72] :art: Refactor QDMI Provider args to kwargs Co-authored-by: Lukas Burgholzer Signed-off-by: Marcel Walter --- python/mqt/core/plugins/qiskit/provider.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index bf5a4b72ce..73e4b88d83 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -52,6 +52,7 @@ class QDMIProvider: def __init__( self, + *, token: str | None = None, auth_file: str | None = None, auth_url: str | None = None, From f95d547a2aa84f673582dbf36d9cb153cf327a0e Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Fri, 5 Dec 2025 18:58:14 +0100 Subject: [PATCH 33/72] :recycle: Expose `QDMI_DEVICE_SESSION_PARAMETER`s via the `Driver` --- include/mqt-core/qdmi/Driver.hpp | 46 +++++++++++++++++++++++++++----- src/qdmi/Driver.cpp | 35 +++++++++++++++++++++--- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 9a8735a3ce..7e9700cac7 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,36 @@ namespace qdmi { +/** + * @brief Configuration for device session parameters. + * @details This struct holds optional parameters that can be set on a device + * session before initialization. All parameters are optional. + */ +struct DeviceSessionConfig { + /// Base URL for API endpoint + std::optional baseUrl; + /// Authentication token + std::optional token; + /// Path to file containing authentication information + std::optional authFile; + /// URL to authentication server + std::optional authUrl; + /// Username for authentication + std::optional username; + /// Password for authentication + std::optional password; + /// Custom configuration parameter 1 + std::optional custom1; + /// Custom configuration parameter 2 + std::optional custom2; + /// Custom configuration parameter 3 + std::optional custom3; + /// Custom configuration parameter 4 + std::optional custom4; + /// Custom configuration parameter 5 + std::optional custom5; +}; + /** * @brief Definition of the device library. * @details The device library contains function pointers to the QDMI @@ -182,8 +213,10 @@ struct QDMI_Device_impl_d { * the device session handle. * @param lib is a unique pointer to the device library that provides the * device interface functions. + * @param config is the configuration for device session parameters. */ - explicit QDMI_Device_impl_d(std::unique_ptr&& lib); + explicit QDMI_Device_impl_d(std::unique_ptr&& lib, + const qdmi::DeviceSessionConfig& config = {}); /** * @brief Destructor for the QDMI device. @@ -407,15 +440,12 @@ class Driver final { ~Driver(); #ifndef _WIN32 /** - * @brief Adds a dynamic device library to the driver. - * @details This function attempts to load a dynamic library containing - * QDMI device interface functions. If the library is already loaded, the - * function returns `false`. Otherwise, it loads the library, initializes - * the device, and adds it to the list of devices. + * @brief Loads a dynamic device library and adds it to the driver. * * @param libName The path to the dynamic library to load. * @param prefix The prefix used for the device interface functions in the * library. + * @param config Configuration for device session parameters. * @returns `true` if the library was successfully loaded, `false` if it was * already loaded. * @@ -426,7 +456,9 @@ class Driver final { * @throws std::bad_alloc If memory allocation fails during the process. */ auto addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix) -> bool; + const std::string& prefix, + const qdmi::DeviceSessionConfig& config = {}) + -> bool; #endif // _WIN32 /** * @brief Allocates a new session. diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 3438a48f17..01b41b6942 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -154,11 +154,38 @@ DynamicDeviceLibrary::~DynamicDeviceLibrary() { } // namespace qdmi QDMI_Device_impl_d::QDMI_Device_impl_d( - std::unique_ptr&& lib) + std::unique_ptr&& lib, + const qdmi::DeviceSessionConfig& config) : library_(std::move(lib)) { if (library_->device_session_alloc(&deviceSession_) != QDMI_SUCCESS) { throw std::runtime_error("Failed to allocate device session"); } + + // Set device session parameters from config + auto setParameter = [this](const std::optional& value, + QDMI_Device_Session_Parameter param) { + if (value && library_->device_session_set_parameter) { + const auto status = library_->device_session_set_parameter( + deviceSession_, param, value->size() + 1, value->c_str()); + if (status != QDMI_SUCCESS) { + library_->device_session_free(deviceSession_); + throw std::runtime_error("Failed to set device session parameter"); + } + } + }; + + setParameter(config.baseUrl, QDMI_DEVICE_SESSION_PARAMETER_BASEURL); + setParameter(config.token, QDMI_DEVICE_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_DEVICE_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_DEVICE_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_DEVICE_SESSION_PARAMETER_PASSWORD); + setParameter(config.custom1, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5); + if (library_->device_session_init(deviceSession_) != QDMI_SUCCESS) { library_->device_session_free(deviceSession_); throw std::runtime_error("Failed to initialize device session"); @@ -349,10 +376,12 @@ Driver::~Driver() { #ifndef _WIN32 auto Driver::addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix) -> bool { + const std::string& prefix, + const qdmi::DeviceSessionConfig& config) + -> bool { try { devices_.emplace_back(std::make_unique( - std::make_unique(libName, prefix))); + std::make_unique(libName, prefix), config)); } catch (const std::runtime_error& e) { if (std::string(e.what()).starts_with("Device library already loaded:")) { // The library is already loaded, so we can ignore this error but return From b0fed9a0e4c86a6cbd36cd52765768e87faecfd3 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 12:02:24 +0100 Subject: [PATCH 34/72] :rotating_light: Address `clang-tidy` warnings --- src/qdmi/Driver.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 01b41b6942..2c6cbdb560 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include From 69467b95b5a3c97fad4dad4d9ea60c304828b9df Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 12:40:21 +0100 Subject: [PATCH 35/72] :recycle: Refactor `QDMI_Device_impl_d` and `Session` initialization to no longer fail on unsupported parameter configurations --- include/mqt-core/fomac/FoMaC.hpp | 3 +- src/fomac/FoMaC.cpp | 21 ++++++- src/qdmi/CMakeLists.txt | 2 +- src/qdmi/Driver.cpp | 97 +++++++++++++++++++++++++++++++- 4 files changed, 115 insertions(+), 8 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 53ae902511..4ce282b207 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -173,7 +174,7 @@ inline auto throwIfError(int result, const std::string& msg) -> void { case QDMI_SUCCESS: break; case QDMI_WARN_GENERAL: - std::cerr << "Warning: " << msg << "\n"; + spdlog::warn("{}", msg); break; default: throwError(result, msg); diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index fcd58f6348..777e877718 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -825,9 +826,23 @@ Session::Session(const SessionConfig& config) { const auto setParameter = [this](const std::optional& value, QDMI_Session_Parameter param) -> void { if (value) { - throwIfError(QDMI_session_set_parameter( - session_, param, value->size() + 1, value->c_str()), - "Setting session parameter " + toString(param)); + const auto status = static_cast(QDMI_session_set_parameter( + session_, param, value->size() + 1, value->c_str())); + if (status == QDMI_ERROR_NOTSUPPORTED) { + // Optional parameter not supported by session - skip it + spdlog::debug("Session parameter {} not supported (skipped)", + toString(param)); + return; + } + if (status != QDMI_SUCCESS && status != QDMI_WARN_GENERAL) { + std::ostringstream ss; + ss << "Setting session parameter " << toString(param) << ": " + << toString(status) << " (status = " << status << ")"; + throwError(status, ss.str()); + } + if (status == QDMI_WARN_GENERAL) { + spdlog::warn("Setting session parameter {}", toString(param)); + } } }; diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index 2a74069adf..a3f291fbfc 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -27,7 +27,7 @@ if(NOT TARGET ${TARGET_NAME}) ${TARGET_NAME} PUBLIC qdmi::qdmi PRIVATE MQT::CoreQDMINaDevice MQT::CoreQDMIScDevice MQT::CoreQDMI_DDSIM_Device - qdmi::project_warnings) + qdmi::project_warnings spdlog::spdlog) # add to list of MQT core targets set(MQT_CORE_TARGETS diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 2c6cbdb560..e01b9a7cbc 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include @@ -33,6 +35,84 @@ #endif // _WIN32 namespace qdmi { +namespace { +/** + * @brief Function used to mark unreachable code + * @details Uses compiler specific extensions if possible. Even if no extension + * is used, undefined behavior is still raised by an empty function body and the + * noreturn attribute. + */ +[[noreturn]] inline void unreachable() { +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif +} +} // namespace + +auto toString(const QDMI_Device_Session_Parameter param) -> std::string { + switch (param) { + case QDMI_DEVICE_SESSION_PARAMETER_BASEURL: + return "BASEURL"; + case QDMI_DEVICE_SESSION_PARAMETER_TOKEN: + return "TOKEN"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE: + return "AUTHFILE"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHURL: + return "AUTHURL"; + case QDMI_DEVICE_SESSION_PARAMETER_USERNAME: + return "USERNAME"; + case QDMI_DEVICE_SESSION_PARAMETER_PASSWORD: + return "PASSWORD"; + case QDMI_DEVICE_SESSION_PARAMETER_MAX: + return "MAX"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1: + return "CUSTOM1"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2: + return "CUSTOM2"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3: + return "CUSTOM3"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4: + return "CUSTOM4"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +auto toString(const QDMI_STATUS result) -> std::string { + switch (result) { + case QDMI_WARN_GENERAL: + return "General warning"; + case QDMI_SUCCESS: + return "Success"; + case QDMI_ERROR_FATAL: + return "A fatal error"; + case QDMI_ERROR_OUTOFMEM: + return "Out of memory"; + case QDMI_ERROR_NOTIMPLEMENTED: + return "Not implemented"; + case QDMI_ERROR_LIBNOTFOUND: + return "Library not found"; + case QDMI_ERROR_NOTFOUND: + return "Element not found"; + case QDMI_ERROR_OUTOFRANGE: + return "Out of range"; + case QDMI_ERROR_INVALIDARGUMENT: + return "Invalid argument"; + case QDMI_ERROR_PERMISSIONDENIED: + return "Permission denied"; + case QDMI_ERROR_NOTSUPPORTED: + return "Not supported"; + case QDMI_ERROR_BADSTATE: + return "Bad state"; + case QDMI_ERROR_TIMEOUT: + return "Timeout"; + } + unreachable(); +} + // Macro to load a static symbol from a statically linked library. // @param prefix is the prefix used for the function names in the library. // @param symbol is the name of the symbol to load. @@ -166,11 +246,22 @@ QDMI_Device_impl_d::QDMI_Device_impl_d( auto setParameter = [this](const std::optional& value, QDMI_Device_Session_Parameter param) { if (value && library_->device_session_set_parameter) { - const auto status = library_->device_session_set_parameter( - deviceSession_, param, value->size() + 1, value->c_str()); + const auto status = + static_cast(library_->device_session_set_parameter( + deviceSession_, param, value->size() + 1, value->c_str())); + if (status == QDMI_ERROR_NOTSUPPORTED) { + // Optional parameter not supported by device - skip it + spdlog::debug( + "Device session parameter {} not supported by device (skipped)", + qdmi::toString(param)); + return; + } if (status != QDMI_SUCCESS) { library_->device_session_free(deviceSession_); - throw std::runtime_error("Failed to set device session parameter"); + std::ostringstream ss; + ss << "Failed to set device session parameter " << qdmi::toString(param) + << ": " << qdmi::toString(status); + throw std::runtime_error(ss.str()); } } }; From fbb78738eea81157959ffb73a4582bafcb6d5f21 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 13:06:42 +0100 Subject: [PATCH 36/72] :white_check_mark: Stricter Session tests to properly test updated parameter functionality --- test/fomac/test_fomac.cpp | 211 +++++++++-------------------- test/python/fomac/test_fomac.py | 233 ++++++++++++-------------------- 2 files changed, 156 insertions(+), 288 deletions(-) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index a46c988a1d..c1aa506966 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -744,100 +744,51 @@ TEST(AuthenticationTest, SessionConstructionWithToken) { // Empty token should be accepted SessionConfig config1; config1.token = ""; - try { - const Session session(config1); - SUCCEED(); // If we get here, the session was created successfully - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config1); }); // Non-empty token should be accepted SessionConfig config2; config2.token = "test_token_123"; - try { - const Session session(config2); - SUCCEED(); - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config2); }); // Token with special characters should be accepted SessionConfig config3; config3.token = "very_long_token_with_special_characters_!@#$%^&*()"; - try { - const Session session(config3); - SUCCEED(); - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config3); }); } TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { // Valid HTTPS URL SessionConfig config1; config1.authUrl = "https://example.com"; - try { - const Session session(config1); - SUCCEED(); - } catch (const std::runtime_error&) { - // Either not supported or validation failed - both acceptable - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config1); }); // Valid HTTP URL with port and path SessionConfig config2; config2.authUrl = "http://auth.server.com:8080/api"; - try { - const Session session(config2); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config2); }); // Valid HTTPS URL with query parameters SessionConfig config3; config3.authUrl = "https://auth.example.com/token?param=value"; - try { - const Session session(config3); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config3); }); // Valid localhost URL SessionConfig configLocalhost; configLocalhost.authUrl = "http://localhost"; - try { - const Session session(configLocalhost); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(configLocalhost); }); // Valid localhost URL with port SessionConfig configLocalhostPort; configLocalhostPort.authUrl = "http://localhost:8080"; - try { - const Session session(configLocalhostPort); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(configLocalhostPort); }); // Valid localhost URL with port and path SessionConfig configLocalhostPath; configLocalhostPath.authUrl = "https://localhost:3000/auth/api"; - try { - const Session session(configLocalhostPath); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(configLocalhostPath); }); - // Invalid URL - not a URL at all + // Invalid URL - not a URL at all (validation fails before setting parameter) SessionConfig config4; config4.authUrl = "not-a-url"; EXPECT_THROW({ const Session session(config4); }, std::runtime_error); @@ -854,40 +805,26 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { } TEST(AuthenticationTest, SessionConstructionWithAuthFile) { - // Test with non-existent file - should raise error + // Non-existent file (validation fails before setting parameter) SessionConfig config1; config1.authFile = "/nonexistent/path/to/file.txt"; EXPECT_THROW({ const Session session(config1); }, std::runtime_error); - // Test with another non-existent file - SessionConfig config2; - config2.authFile = "/tmp/this_file_does_not_exist_12345.txt"; - EXPECT_THROW({ const Session session(config2); }, std::runtime_error); - - // Test with existing file + // Existing file (should succeed even if parameter is unsupported) const auto tempDir = std::filesystem::temp_directory_path(); auto tmpPath = tempDir / ("fomac_test_auth_" + std::to_string(std::hash{}( std::this_thread::get_id())) + ".txt"); - - // Create and write to the temporary file { std::ofstream tmpFile(tmpPath); ASSERT_TRUE(tmpFile.is_open()) << "Failed to create temporary file"; tmpFile << "test_token_content"; } - // Try to create session with existing file - SessionConfig config3; - config3.authFile = tmpPath.string(); - try { - const Session session(config3); - SUCCEED(); - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } + SessionConfig config2; + config2.authFile = tmpPath.string(); + EXPECT_NO_THROW({ const Session session(config2); }); // Clean up std::filesystem::remove(tmpPath); @@ -897,45 +834,24 @@ TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { // Username only SessionConfig config1; config1.username = "user123"; - try { - const Session session(config1); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config1); }); // Password only SessionConfig config2; config2.password = "secure_password"; - try { - const Session session(config2); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config2); }); // Both username and password SessionConfig config3; config3.username = "user123"; config3.password = "secure_password"; - try { - const Session session(config3); - SUCCEED(); - } catch (const std::runtime_error&) { - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config3); }); } TEST(AuthenticationTest, SessionConstructionWithProjectId) { SessionConfig config; config.projectId = "project-123-abc"; - try { - const Session session(config); - SUCCEED(); - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } + EXPECT_NO_THROW({ const Session session(config); }); } TEST(AuthenticationTest, SessionConstructionWithMultipleParameters) { @@ -944,52 +860,25 @@ TEST(AuthenticationTest, SessionConstructionWithMultipleParameters) { config.username = "test_user"; config.password = "test_pass"; config.projectId = "test_project"; - try { - const Session session(config); - SUCCEED(); - } catch (const std::runtime_error&) { - // If not supported, that's okay for now - SUCCEED(); - } -} - -TEST(AuthenticationTest, SessionGetDevicesReturnsList) { - Session session; - auto devices = session.getDevices(); - - EXPECT_FALSE(devices.empty()); - - // All elements should be Device instances - for (const auto& device : devices) { - // Device should have a name - EXPECT_FALSE(device.getName().empty()); - } -} - -TEST(AuthenticationTest, SessionMultipleInstances) { - Session session1; - Session session2; - - auto devices1 = session1.getDevices(); - auto devices2 = session2.getDevices(); - - // Both should return devices - EXPECT_FALSE(devices1.empty()); - EXPECT_FALSE(devices2.empty()); - - // Should return the same number of devices - EXPECT_EQ(devices1.size(), devices2.size()); + EXPECT_NO_THROW({ const Session session(config); }); } TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { - // Test custom1 + // Custom parameters may not be supported by all devices, or may have specific + // validation requirements. This test verifies they can be passed to the + // Session constructor. Currently a smoke test. + + // Test custom1 - may succeed or fail with validation/unsupported errors SessionConfig config1; config1.custom1 = "custom_value_1"; try { const Session session(config1); SUCCEED(); - } catch (const std::exception&) { - // If not supported, that's okay for now + } catch (const std::invalid_argument&) { + // Validation error - parameter recognized but value invalid + SUCCEED(); + } catch (const std::runtime_error&) { + // Not supported or other error SUCCEED(); } @@ -999,7 +888,9 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { try { const Session session(config2); SUCCEED(); - } catch (const std::exception&) { + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { SUCCEED(); } @@ -1013,7 +904,9 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { try { const Session session(config3); SUCCEED(); - } catch (const std::exception&) { + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { SUCCEED(); } @@ -1025,11 +918,41 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { try { const Session session(config4); SUCCEED(); - } catch (const std::exception&) { + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { SUCCEED(); } } +TEST(AuthenticationTest, SessionGetDevicesReturnsList) { + Session session; + auto devices = session.getDevices(); + + EXPECT_FALSE(devices.empty()); + + // All elements should be Device instances + for (const auto& device : devices) { + // Device should have a name + EXPECT_FALSE(device.getName().empty()); + } +} + +TEST(AuthenticationTest, SessionMultipleInstances) { + Session session1; + Session session2; + + auto devices1 = session1.getDevices(); + auto devices2 = session2.getDevices(); + + // Both should return devices + EXPECT_FALSE(devices1.empty()); + EXPECT_FALSE(devices2.empty()); + + // Should return the same number of devices + EXPECT_EQ(devices1.size(), devices2.size()); +} + namespace { // Helper function to get all devices for parameterized tests auto getDevices() -> std::vector { diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 441a363cd1..8ac18d8609 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -600,84 +600,57 @@ def test_simulator_job_get_sparse_probabilities_returns_valid_probabilities(simu def test_session_construction_with_token() -> None: """Test Session construction with a token parameter. - Note: The underlying QDMI library may not support authentication parameters yet, - so this test verifies that the parameter can be passed without causing errors - during construction, even if it's not actually used. + Unsupported parameters are skipped during Session initialization, + so Session construction succeeds unless there's a critical error. """ # Empty token should be accepted - try: - session = Session(token="") - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(token="") + assert session is not None # Non-empty token should be accepted - try: - session = Session(token="test_token_123") # noqa: S106 - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(token="test_token_123") # noqa: S106 + assert session is not None # Token with special characters should be accepted - try: - session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # noqa: S106 - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # noqa: S106 + assert session is not None def test_session_construction_with_auth_url() -> None: """Test Session construction with auth URL parameter. - Note: The currently available QDMI devices don't support authentication. - Valid URLs should either be accepted or rejected with "Not supported" error. - Invalid URLs should be rejected with validation errors. + Valid URLs should pass validation and Session construction should succeed + (even if the parameter is unsupported and skipped). Invalid URLs should + fail validation before attempting to set the parameter. """ # Valid HTTPS URL - try: - session = Session(auth_url="https://example.com") - assert session is not None - except RuntimeError: - # Either not supported or validation failed - both acceptable - pass + session = Session(auth_url="https://example.com") + assert session is not None + # Valid HTTP URL with port and path - try: - session = Session(auth_url="http://auth.server.com:8080/api") - assert session is not None - except RuntimeError: - # Either not supported or validation failed - both acceptable - pass + session = Session(auth_url="http://auth.server.com:8080/api") + assert session is not None + # Valid HTTPS URL with query parameters - try: - session = Session(auth_url="https://auth.example.com/token?param=value") - assert session is not None - except RuntimeError: - # Either not supported or validation failed - both acceptable - pass + session = Session(auth_url="https://auth.example.com/token?param=value") + assert session is not None + # Valid localhost URL - try: - session = Session(auth_url="http://localhost") - assert session is not None - except RuntimeError: - pass + session = Session(auth_url="http://localhost") + assert session is not None + # Valid localhost URL with port - try: - session = Session(auth_url="http://localhost:8080") - assert session is not None - except RuntimeError: - pass + session = Session(auth_url="http://localhost:8080") + assert session is not None + # Valid localhost URL with port and path - try: - session = Session(auth_url="https://localhost:3000/auth/api") - assert session is not None - except RuntimeError: - pass + session = Session(auth_url="https://localhost:3000/auth/api") + assert session is not None + # Invalid URL - not a URL at all with pytest.raises(RuntimeError): Session(auth_url="not-a-url") + # Invalid URL - unsupported protocol with pytest.raises(RuntimeError): Session(auth_url="ftp://invalid.com") @@ -690,31 +663,22 @@ def test_session_construction_with_auth_url() -> None: def test_session_construction_with_auth_file() -> None: """Test Session construction with auth file parameter. - Note: The currently available QDMI devices don't support authentication. - Existing files should either be accepted or rejected with "Not supported" error. - Non-existent files should be rejected with validation errors. + Existing files should pass validation and Session construction should succeed. + Non-existent files should fail validation before attempting to set the parameter. """ - # Test with non-existent file - should raise error + # Test with non-existent file with pytest.raises(RuntimeError): Session(auth_file="/nonexistent/path/to/file.txt") - # Test with another non-existent file - with pytest.raises(RuntimeError): - Session(auth_file="/tmp/this_file_does_not_exist_12345.txt") # noqa: S108 - # Test with existing file with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", delete=False, suffix=".txt") as tmp_file: tmp_file.write("test_token_content") tmp_path = tmp_file.name try: - # Existing file should be accepted or rejected with "Not supported" - try: - session = Session(auth_file=tmp_path) - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + # Existing file should be accepted (validation passes, parameter may be skipped) + session = Session(auth_file=tmp_path) + assert session is not None finally: # Clean up Path(tmp_path).unlink(missing_ok=True) @@ -723,106 +687,56 @@ def test_session_construction_with_auth_file() -> None: def test_session_construction_with_username_password() -> None: """Test Session construction with username and password parameters. - Note: The currently available QDMI devices don't support authentication. + Unsupported parameters are skipped, so construction should succeed. """ # Username only - try: - session = Session(username="user123") - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(username="user123") + assert session is not None # Password only - try: - session = Session(password="secure_password") # noqa: S106 - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(password="secure_password") # noqa: S106 + assert session is not None # Both username and password - try: - session = Session(username="user123", password="secure_password") # noqa: S106 - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(username="user123", password="secure_password") # noqa: S106 + assert session is not None def test_session_construction_with_project_id() -> None: """Test Session construction with project ID parameter. - Note: The currently available QDMI devices don't support authentication. + Unsupported parameters are skipped, so construction should succeed. """ - try: - session = Session(project_id="project-123-abc") - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass + session = Session(project_id="project-123-abc") + assert session is not None def test_session_construction_with_multiple_parameters() -> None: """Test Session construction with multiple authentication parameters. - Note: The currently available QDMI devices don't support authentication. + Unsupported parameters are skipped, so construction should succeed. """ - try: - session = Session( - token="test_token", # noqa: S106 - username="test_user", - password="test_pass", # noqa: S106 - project_id="test_project", - ) - assert session is not None - except RuntimeError: - # If not supported, that's okay for now - pass - - -def test_session_get_devices_returns_list() -> None: - """Test that get_devices() returns a list of Device objects.""" - session = Session() - devices = session.get_devices() - - assert isinstance(devices, list) - assert len(devices) > 0 - - # All elements should be Device instances - for device in devices: - assert isinstance(device, Device) - # Device should have a name - assert len(device.name()) > 0 - - -def test_session_multiple_instances() -> None: - """Test that multiple Session instances can be created independently.""" - session1 = Session() - session2 = Session() - - devices1 = session1.get_devices() - devices2 = session2.get_devices() - - # Both should return devices - assert len(devices1) > 0 - assert len(devices2) > 0 - - # Should return the same number of devices - assert len(devices1) == len(devices2) + session = Session( + token="test_token", # noqa: S106 + username="test_user", + password="test_pass", # noqa: S106 + project_id="test_project", + ) + assert session is not None def test_session_construction_with_custom_parameters() -> None: """Test Session construction with custom configuration parameters. - Note: The currently available QDMI devices don't support custom parameters. + Custom parameters may not be supported by all devices, or may have specific + validation requirements. This test verifies they can be passed to the Session + constructor. Currently a smoke test.. """ - # Test custom1 + # Test custom1 - may succeed or fail with validation/unsupported errors try: session = Session(custom1="custom_value_1") assert session is not None except (RuntimeError, ValueError): - # If not supported, that's okay for now pass # Test custom2 @@ -855,3 +769,34 @@ def test_session_construction_with_custom_parameters() -> None: assert session is not None except (RuntimeError, ValueError): pass + + +def test_session_get_devices_returns_list() -> None: + """Test that get_devices() returns a list of Device objects.""" + session = Session() + devices = session.get_devices() + + assert isinstance(devices, list) + assert len(devices) > 0 + + # All elements should be Device instances + for device in devices: + assert isinstance(device, Device) + # Device should have a name + assert len(device.name()) > 0 + + +def test_session_multiple_instances() -> None: + """Test that multiple Session instances can be created independently.""" + session1 = Session() + session2 = Session() + + devices1 = session1.get_devices() + devices2 = session2.get_devices() + + # Both should return devices + assert len(devices1) > 0 + assert len(devices2) > 0 + + # Should return the same number of devices + assert len(devices1) == len(devices2) From 74cfd1db29f0f52b0f663c17a2e196495acc4376 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 13:39:43 +0100 Subject: [PATCH 37/72] :white_check_mark: Test `Driver::addDynamicDeviceLibrary` --- test/qdmi/test_driver.cpp | 213 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 206 insertions(+), 7 deletions(-) diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index 3473c05edd..c91171a285 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -49,9 +49,11 @@ class DriverTest : public testing::TestWithParam { QDMI_Device device = nullptr; #ifndef _WIN32 static void SetUpTestSuite() { + // Load dynamic libraries with default device session configuration + qdmi::DeviceSessionConfig config; ASSERT_NO_THROW({ for (const auto& [lib, prefix] : DYN_DEV_LIBS) { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix); + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); } }); } @@ -127,13 +129,25 @@ class DriverJobTest : public DriverTest { #ifndef _WIN32 TEST(DriverTest, LoadLibraryTwice) { - // Verify that attempting to load already-loaded libraries returns false. - // Note: SetUpTestSuite may have already loaded these libraries, so the first - // call here might also return false. This test validates that duplicate loads - // are safely handled and consistently return false (idempotent behavior). + // Test that loading the same library twice returns false (idempotent + // behavior). First load should succeed (return true), subsequent loads should + // return false. + qdmi::DeviceSessionConfig config; EXPECT_NO_THROW(for (const auto& [lib, prefix] : DYN_DEV_LIBS) { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix); - EXPECT_FALSE(qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix)); + // First attempt - should return true + const auto firstResult = + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + EXPECT_TRUE(firstResult) << "First load should return true"; + + // Second attempt - should return false (idempotent) + const auto secondResult = + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + EXPECT_FALSE(secondResult) << "Second load should return false"; + + // Third attempt - also should return false (idempotent) + const auto thirdResult = + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + EXPECT_FALSE(thirdResult) << "Third load should return false (idempotent)"; }); } #endif // _WIN32 @@ -539,6 +553,191 @@ INSTANTIATE_TEST_SUITE_P( std::erase(name, ')'); return name; }); + +#ifndef _WIN32 +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithBaseUrl) { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithCustomParameters) { + qdmi::DeviceSessionConfig config; + config.custom1 = "RESONANCE_COCOS_V1"; + config.custom2 = "test_value"; + config.baseUrl = "http://localhost:9090"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + // Custom parameters may fail with validation errors or succeed/return false + try { + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + SUCCEED() << "Library loaded or already loaded"; + } catch (const std::runtime_error& e) { + // Custom parameters may be rejected with INVALIDARGUMENT + std::string msg = e.what(); + if (msg.find("CUSTOM") != std::string::npos && + msg.find("Invalid argument") != std::string::npos) { + SUCCEED() << "Custom parameter validation error (expected): " << msg; + } else { + throw; // Re-throw unexpected errors + } + } + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthToken) { + qdmi::DeviceSessionConfig config; + config.token = "test_token_123"; + config.baseUrl = "https://api.example.com"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthFile) { + qdmi::DeviceSessionConfig config; + config.authFile = "/nonexistent/auth.json"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + // This should not throw even with non-existent file because + // if the auth file parameter is not supported, it's skipped + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithUsernamePassword) { + qdmi::DeviceSessionConfig config; + config.authUrl = "https://auth.example.com"; + config.username = "quantum_user"; + config.password = "secret_password"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + config.token = "test_token"; + config.authUrl = "https://auth.example.com"; + config.username = "user"; + config.password = "pass"; + config.custom1 = "value1"; + config.custom2 = "value2"; + config.custom3 = "value3"; + config.custom4 = "value4"; + config.custom5 = "value5"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + try { + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + SUCCEED() << "Library loaded or already loaded"; + } catch (const std::runtime_error& e) { + // Custom parameters may be rejected with INVALIDARGUMENT + std::string msg = e.what(); + if (msg.find("CUSTOM") != std::string::npos && + msg.find("Invalid argument") != std::string::npos) { + SUCCEED() << "Custom parameter validation error (expected): " << msg; + } else { + throw; // Re-throw unexpected errors + } + } + } +} + +TEST(DeviceSessionConfigTest, VerifyDynamicDevicesInSession) { + QDMI_Session session = nullptr; + ASSERT_EQ(QDMI_session_alloc(&session), QDMI_SUCCESS); + ASSERT_EQ(QDMI_session_init(session), QDMI_SUCCESS); + + size_t devicesSize = 0; + ASSERT_EQ(QDMI_session_query_session_property(session, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &devicesSize), + QDMI_SUCCESS); + + const size_t numDevices = devicesSize / sizeof(QDMI_Device); + + // Should have at least the static devices (NA, DDSIM, SC) + const size_t expectedMinDevices = 3; + EXPECT_GE(numDevices, expectedMinDevices) + << "Should have at least " << expectedMinDevices << " static devices"; + + // Verify we can query device names + std::vector devices(numDevices); + ASSERT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, devicesSize, + devices.data(), nullptr), + QDMI_SUCCESS); + + for (auto* device : devices) { + size_t nameSize = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &nameSize), + QDMI_SUCCESS); + + std::string name(nameSize - 1, '\0'); + ASSERT_EQ(QDMI_device_query_device_property(device, + QDMI_DEVICE_PROPERTY_NAME, + nameSize, name.data(), nullptr), + QDMI_SUCCESS); + + EXPECT_FALSE(name.empty()) << "Device should have a non-empty name"; + } + + QDMI_session_free(session); +} + +TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { + if (DYN_DEV_LIBS.size() > 0) { + // Make a copy to avoid dangling reference + const auto firstLib = DYN_DEV_LIBS[0]; + const auto& lib = firstLib.first; + const auto& prefix = firstLib.second; + + // Config 1: baseUrl + { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + EXPECT_NO_THROW({ + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + }); + } + + // Config 2: different baseUrl and custom parameters + { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:9090"; + config.custom1 = "API_V2"; + const auto result = + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + EXPECT_FALSE(result) + << "Library should already be loaded with first config"; + } + + // Config 3: authentication parameters + { + qdmi::DeviceSessionConfig config; + config.token = "new_token"; + config.authUrl = "https://auth.example.com"; + const auto result = + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + EXPECT_FALSE(result) + << "Library should already be loaded with first config"; + } + } +} +#endif // _WIN32 + INSTANTIATE_TEST_SUITE_P( // Custom instantiation name DefaultDevices, From 7882499752fa6f10fbf0241eed352ee9950e6af1 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 13:51:46 +0100 Subject: [PATCH 38/72] :green_heart: Fix RTD build --- include/mqt-core/fomac/FoMaC.hpp | 13 +------------ src/fomac/FoMaC.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 4ce282b207..e3d6ec22cf 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -169,17 +168,7 @@ auto toString(QDMI_SESSION_PARAMETER_T param) -> std::string; [[noreturn]] auto throwError(int result, const std::string& msg) -> void; /// Throws an exception if the result indicates an error. -inline auto throwIfError(int result, const std::string& msg) -> void { - switch (result) { - case QDMI_SUCCESS: - break; - case QDMI_WARN_GENERAL: - spdlog::warn("{}", msg); - break; - default: - throwError(result, msg); - } -} +auto throwIfError(int result, const std::string& msg) -> void; /** * @brief Configuration structure for session authentication parameters. diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 777e877718..d46dbf9a37 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -247,6 +247,17 @@ auto throwError(const int result, const std::string& msg) -> void { toString(static_cast(result)) + "."); } } +auto throwIfError(const int result, const std::string& msg) -> void { + switch (result) { + case QDMI_SUCCESS: + break; + case QDMI_WARN_GENERAL: + spdlog::warn("{}", msg); + break; + default: + throwError(result, msg); + } +} auto Session::Device::Site::getIndex() const -> size_t { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } From 106452c66be59af7fb5137cd21a0f59828869c1e Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 14:01:42 +0100 Subject: [PATCH 39/72] :art: Prefer SPDLOG macros over function calls --- src/fomac/FoMaC.cpp | 8 ++++---- src/qdmi/Driver.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index d46dbf9a37..2b430f41ce 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -252,7 +252,7 @@ auto throwIfError(const int result, const std::string& msg) -> void { case QDMI_SUCCESS: break; case QDMI_WARN_GENERAL: - spdlog::warn("{}", msg); + SPDLOG_WARN("{}", msg); break; default: throwError(result, msg); @@ -841,8 +841,8 @@ Session::Session(const SessionConfig& config) { session_, param, value->size() + 1, value->c_str())); if (status == QDMI_ERROR_NOTSUPPORTED) { // Optional parameter not supported by session - skip it - spdlog::debug("Session parameter {} not supported (skipped)", - toString(param)); + SPDLOG_INFO("Session parameter {} not supported (skipped)", + toString(param)); return; } if (status != QDMI_SUCCESS && status != QDMI_WARN_GENERAL) { @@ -852,7 +852,7 @@ Session::Session(const SessionConfig& config) { throwError(status, ss.str()); } if (status == QDMI_WARN_GENERAL) { - spdlog::warn("Setting session parameter {}", toString(param)); + SPDLOG_WARN("Setting session parameter {}", toString(param)); } } }; diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index e01b9a7cbc..800363f525 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -251,7 +251,7 @@ QDMI_Device_impl_d::QDMI_Device_impl_d( deviceSession_, param, value->size() + 1, value->c_str())); if (status == QDMI_ERROR_NOTSUPPORTED) { // Optional parameter not supported by device - skip it - spdlog::debug( + SPDLOG_INFO( "Device session parameter {} not supported by device (skipped)", qdmi::toString(param)); return; From d0d132c924649f4381d886f6eeeb76933f9803d0 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 14:30:48 +0100 Subject: [PATCH 40/72] :sparkles: Expose device library loading to Python --- bindings/fomac/fomac.cpp | 41 ++++++++++++++++++++ python/mqt/core/fomac.pyi | 67 +++++++++++++++++++++++++++++++++ test/python/fomac/test_fomac.py | 15 ++++++++ 3 files changed, 123 insertions(+) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 6f4dd790ca..fb3ebbfa0d 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -10,6 +10,8 @@ #include "fomac/FoMaC.hpp" +#include "qdmi/Driver.hpp" + #include #include #include // NOLINT(misc-include-cleaner) @@ -221,6 +223,45 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { }); device.def(py::self == py::self); // NOLINT(misc-redundant-expression) device.def(py::self != py::self); // NOLINT(misc-redundant-expression) + +#ifndef _WIN32 + // Module-level function to add dynamic device libraries on non-Windows + // systems + m.def( + "add_dynamic_device_library", + [](const std::string& libraryPath, const std::string& prefix, + const std::optional& baseUrl = std::nullopt, + const std::optional& token = std::nullopt, + const std::optional& authFile = std::nullopt, + const std::optional& authUrl = std::nullopt, + const std::optional& username = std::nullopt, + const std::optional& password = std::nullopt, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) -> bool { + const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, + .token = token, + .authFile = authFile, + .authUrl = authUrl, + .username = username, + .password = password, + .custom1 = custom1, + .custom2 = custom2, + .custom3 = custom3, + .custom4 = custom4, + .custom5 = custom5}; + return qdmi::Driver::get().addDynamicDeviceLibrary(libraryPath, prefix, + config); + }, + "library_path"_a, "prefix"_a, "base_url"_a = std::nullopt, + "token"_a = std::nullopt, "auth_file"_a = std::nullopt, + "auth_url"_a = std::nullopt, "username"_a = std::nullopt, + "password"_a = std::nullopt, "custom1"_a = std::nullopt, + "custom2"_a = std::nullopt, "custom3"_a = std::nullopt, + "custom4"_a = std::nullopt, "custom5"_a = std::nullopt); +#endif // _WIN32 } } // namespace mqt diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index e0399b38cc..282669d795 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -6,6 +6,7 @@ # # Licensed under the MIT License +import sys from collections.abc import Iterable from enum import Enum @@ -16,6 +17,10 @@ __all__ = [ "Session", ] +# add_dynamic_device_library is only available on non-Windows platforms +if sys.platform != "win32": + __all__ += ["add_dynamic_device_library"] + class ProgramFormat(Enum): """Enumeration of program formats.""" @@ -274,3 +279,65 @@ class Device: """Checks if two devices are equal.""" def __ne__(self, other: object) -> bool: """Checks if two devices are not equal.""" + +# Dynamic library loading is only available on non-Windows platforms +if sys.platform != "win32": + def add_dynamic_device_library( + library_path: str, + prefix: str, + *, + base_url: str | None = None, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, + ) -> bool: + """Load a dynamic device library into the QDMI driver. + + This function loads a shared library (.so, .dll, or .dylib) that implements + a QDMI device interface and makes it available for use in sessions. + + Note: This function is only available on non-Windows platforms. + + Args: + library_path: Path to the shared library file to load. + prefix: Function prefix used by the library (e.g., "MY_DEVICE"). + base_url: Optional base URL for the device API endpoint. + token: Optional authentication token. + auth_file: Optional path to authentication file. + auth_url: Optional authentication server URL. + username: Optional username for authentication. + password: Optional password for authentication. + custom1: Optional custom configuration parameter 1. + custom2: Optional custom configuration parameter 2. + custom3: Optional custom configuration parameter 3. + custom4: Optional custom configuration parameter 4. + custom5: Optional custom configuration parameter 5. + + Returns: + True if the library was successfully loaded, False if it was already loaded. + + Raises: + RuntimeError: If library loading fails or configuration is invalid. + + Examples: + Load a device library with configuration: + + >>> import mqt.core.fomac as fomac + >>> success = fomac.add_dynamic_device_library( + ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" + ... ) + >>> if success: + ... print("Library loaded successfully") + + Now the device is available in sessions: + + >>> session = fomac.Session() + >>> devices = session.get_devices() + """ diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 8ac18d8609..5d92ac84a7 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -10,6 +10,7 @@ from __future__ import annotations +import sys import tempfile from pathlib import Path from typing import cast @@ -800,3 +801,17 @@ def test_session_multiple_instances() -> None: # Should return the same number of devices assert len(devices1) == len(devices2) + + +if sys.platform != "win32": + from mqt.core import fomac + + def test_add_dynamic_device_library_exists() -> None: + """Test that add_dynamic_device_library function exists on non-Windows platforms.""" + assert hasattr(fomac, "add_dynamic_device_library") + assert callable(fomac.add_dynamic_device_library) + + def test_add_dynamic_device_library_nonexistent_library() -> None: + """Test that loading a non-existent library raises an error.""" + with pytest.raises(RuntimeError): + fomac.add_dynamic_device_library("/nonexistent/lib.so", "PREFIX") From f0b3a321100f9b3c0f90bb451a3641b34cebba18 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 15:00:36 +0100 Subject: [PATCH 41/72] :art: Implement CodeRabbit's suggestions --- test/qdmi/test_driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index c91171a285..c2a573734b 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -698,7 +698,7 @@ TEST(DeviceSessionConfigTest, VerifyDynamicDevicesInSession) { } TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { - if (DYN_DEV_LIBS.size() > 0) { + if (!DYN_DEV_LIBS.empty()) { // Make a copy to avoid dangling reference const auto firstLib = DYN_DEV_LIBS[0]; const auto& lib = firstLib.first; From 3fb8b1cf213afbffff48707b23484c853e9f2a58 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sat, 6 Dec 2025 17:38:23 +0100 Subject: [PATCH 42/72] :rotating_light: Fix `clang-tidy` warnings --- src/qdmi/Driver.cpp | 4 +--- test/fomac/test_fomac.cpp | 1 - test/qdmi/test_driver.cpp | 11 ++++++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 800363f525..fae46c79ca 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -49,8 +49,6 @@ namespace { __assume(false); #endif } -} // namespace - auto toString(const QDMI_Device_Session_Parameter param) -> std::string { switch (param) { case QDMI_DEVICE_SESSION_PARAMETER_BASEURL: @@ -80,7 +78,6 @@ auto toString(const QDMI_Device_Session_Parameter param) -> std::string { } unreachable(); } - auto toString(const QDMI_STATUS result) -> std::string { switch (result) { case QDMI_WARN_GENERAL: @@ -112,6 +109,7 @@ auto toString(const QDMI_STATUS result) -> std::string { } unreachable(); } +} // namespace // Macro to load a static symbol from a statically linked library. // @param prefix is the prefix used for the function names in the library. diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index c1aa506966..f68445cabc 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index c2a573734b..c1a9ed895e 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,7 @@ class DriverTest : public testing::TestWithParam { #ifndef _WIN32 static void SetUpTestSuite() { // Load dynamic libraries with default device session configuration - qdmi::DeviceSessionConfig config; + const qdmi::DeviceSessionConfig config; ASSERT_NO_THROW({ for (const auto& [lib, prefix] : DYN_DEV_LIBS) { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); @@ -132,7 +133,7 @@ TEST(DriverTest, LoadLibraryTwice) { // Test that loading the same library twice returns false (idempotent // behavior). First load should succeed (return true), subsequent loads should // return false. - qdmi::DeviceSessionConfig config; + const qdmi::DeviceSessionConfig config; EXPECT_NO_THROW(for (const auto& [lib, prefix] : DYN_DEV_LIBS) { // First attempt - should return true const auto firstResult = @@ -578,7 +579,7 @@ TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithCustomParameters) { SUCCEED() << "Library loaded or already loaded"; } catch (const std::runtime_error& e) { // Custom parameters may be rejected with INVALIDARGUMENT - std::string msg = e.what(); + const std::string msg = e.what(); if (msg.find("CUSTOM") != std::string::npos && msg.find("Invalid argument") != std::string::npos) { SUCCEED() << "Custom parameter validation error (expected): " << msg; @@ -643,7 +644,7 @@ TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { SUCCEED() << "Library loaded or already loaded"; } catch (const std::runtime_error& e) { // Custom parameters may be rejected with INVALIDARGUMENT - std::string msg = e.what(); + const std::string msg = e.what(); if (msg.find("CUSTOM") != std::string::npos && msg.find("Invalid argument") != std::string::npos) { SUCCEED() << "Custom parameter validation error (expected): " << msg; @@ -676,7 +677,7 @@ TEST(DeviceSessionConfigTest, VerifyDynamicDevicesInSession) { std::vector devices(numDevices); ASSERT_EQ(QDMI_session_query_session_property( session, QDMI_SESSION_PROPERTY_DEVICES, devicesSize, - devices.data(), nullptr), + static_cast(devices.data()), nullptr), QDMI_SUCCESS); for (auto* device : devices) { From 5cd5f00fb69549930f6c7bb9478405328ce45efb Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 6 Dec 2025 23:43:06 +0100 Subject: [PATCH 43/72] =?UTF-8?q?=F0=9F=A9=B9=20ensure=20session=20stays?= =?UTF-8?q?=20alive=20in=20provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- python/mqt/core/plugins/qiskit/provider.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index 73e4b88d83..26418c4c46 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -80,7 +80,7 @@ def __init__( custom4: Custom configuration parameter 4. custom5: Custom configuration parameter 5. """ - session = fomac.Session( + self._session = fomac.Session( token=token, auth_file=auth_file, auth_url=auth_url, @@ -94,7 +94,7 @@ def __init__( custom5=custom5, ) self._backends = [ - QDMIBackend(device=d, provider=self) for d in session.get_devices() if QDMIBackend.is_convertible(d) + QDMIBackend(device=d, provider=self) for d in self._session.get_devices() if QDMIBackend.is_convertible(d) ] def backends(self, name: str | None = None) -> list[QDMIBackend]: From 0a4f68f1076ba2f5d0eb09c98d94b298ad29e48d Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sat, 6 Dec 2025 23:53:33 +0100 Subject: [PATCH 44/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20provider?= =?UTF-8?q?=20initialization=20to=20use=20session=20kwargs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- python/mqt/core/plugins/qiskit/provider.py | 37 ++++++++-------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index 26418c4c46..bc673a9f62 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -59,11 +59,7 @@ def __init__( username: str | None = None, password: str | None = None, project_id: str | None = None, - custom1: str | None = None, - custom2: str | None = None, - custom3: str | None = None, - custom4: str | None = None, - custom5: str | None = None, + **session_kwargs: str, ) -> None: """Initialize the QDMI provider. @@ -74,25 +70,20 @@ def __init__( username: Username for authentication. password: Password for authentication. project_id: Project ID for the session. - custom1: Custom configuration parameter 1. - custom2: Custom configuration parameter 2. - custom3: Custom configuration parameter 3. - custom4: Custom configuration parameter 4. - custom5: Custom configuration parameter 5. + session_kwargs: Optional additional keyword arguments for Session initialization. """ - self._session = fomac.Session( - token=token, - auth_file=auth_file, - auth_url=auth_url, - username=username, - password=password, - project_id=project_id, - custom1=custom1, - custom2=custom2, - custom3=custom3, - custom4=custom4, - custom5=custom5, - ) + kwargs = { + "token": token, + "auth_file": auth_file, + "auth_url": auth_url, + "username": username, + "password": password, + "project_id": project_id, + } + if session_kwargs: + kwargs.update(session_kwargs) + + self._session = fomac.Session(**kwargs) self._backends = [ QDMIBackend(device=d, provider=self) for d in self._session.get_devices() if QDMIBackend.is_convertible(d) ] From 8e7938c5cefebdf6c6d1681813cc38176fb0e6f2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 00:00:28 +0100 Subject: [PATCH 45/72] =?UTF-8?q?=F0=9F=8E=A8=20remove=20redundant=20inlin?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/Driver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index fae46c79ca..7e9f9509bb 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -42,7 +42,7 @@ namespace { * is used, undefined behavior is still raised by an empty function body and the * noreturn attribute. */ -[[noreturn]] inline void unreachable() { +[[noreturn]] void unreachable() { #ifdef __GNUC__ // GCC, Clang, ICC __builtin_unreachable(); #elif defined(_MSC_VER) // MSVC From 5ea53a6844e5a06ffa686f085a7afe1e21b572e6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 00:21:17 +0100 Subject: [PATCH 46/72] =?UTF-8?q?=E2=9C=85=E2=99=BB=EF=B8=8F=20refine=20sm?= =?UTF-8?q?oke=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/fomac/test_fomac.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index f68445cabc..ea651127eb 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -871,26 +871,26 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { SessionConfig config1; config1.custom1 = "custom_value_1"; try { - const Session session(config1); - SUCCEED(); + Session session(config1); + EXPECT_NO_THROW(std::ignore = session.getDevices()); } catch (const std::invalid_argument&) { // Validation error - parameter recognized but value invalid SUCCEED(); } catch (const std::runtime_error&) { // Not supported or other error - SUCCEED(); + GTEST_SKIP() << "Custom parameter not supported by backend"; } // Test custom2 SessionConfig config2; config2.custom2 = "custom_value_2"; try { - const Session session(config2); - SUCCEED(); + Session session(config2); + EXPECT_NO_THROW(std::ignore = session.getDevices()); } catch (const std::invalid_argument&) { SUCCEED(); } catch (const std::runtime_error&) { - SUCCEED(); + GTEST_SKIP() << "Custom parameter not supported by backend"; } // Test all custom parameters together @@ -901,12 +901,12 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { config3.custom4 = "value4"; config3.custom5 = "value5"; try { - const Session session(config3); - SUCCEED(); + Session session(config3); + EXPECT_NO_THROW(std::ignore = session.getDevices()); } catch (const std::invalid_argument&) { SUCCEED(); } catch (const std::runtime_error&) { - SUCCEED(); + GTEST_SKIP() << "Custom parameter not supported by backend"; } // Test mixing custom parameters with standard authentication @@ -915,12 +915,12 @@ TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { config4.custom1 = "custom_value"; config4.projectId = "project_id"; try { - const Session session(config4); - SUCCEED(); + Session session(config4); + EXPECT_NO_THROW(std::ignore = session.getDevices()); } catch (const std::invalid_argument&) { SUCCEED(); } catch (const std::runtime_error&) { - SUCCEED(); + GTEST_SKIP() << "Custom parameter not supported by backend"; } } From 7b6fb8ff2ed90e26768bb745c3925a084a783394 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 01:14:10 +0100 Subject: [PATCH 47/72] =?UTF-8?q?=F0=9F=90=9B=20Fix=20custom=20QDMI=20prop?= =?UTF-8?q?erty=20and=20parameter=20handling=20in=20SC=20and=20NA=20device?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 1 + src/na/device/Device.cpp | 51 ++++++++++++++++++++++++++++++++++------ src/qdmi/sc/Device.cpp | 51 ++++++++++++++++++++++++++++++++++------ 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73214fb43f..49fc1de1a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Fixed +- 🐛 Fix custom QDMI property and parameter handling in SC and NA devices ([#1355]) ([**@burgholzer**]) - 🚨 Fix argument naming of `QuantumComputation` and `CompoundOperation` dunder methods for properly implementing the `MutableSequence` protocol ([#1338]) ([**@burgholzer**]) - 🐛 Fix memory management in dynamic QDMI device by making it explicit ([#1336]) ([**@ystade**]) diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp index 634f9487b7..92c357e5d8 100644 --- a/src/na/device/Device.cpp +++ b/src/na/device/Device.cpp @@ -158,7 +158,13 @@ auto Device::sessionFree(MQT_NA_QDMI_Device_Session session) -> void { } auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_DEVICE_PROPERTY_MAX && + prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -219,7 +225,12 @@ auto MQT_NA_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_SESSION_PARAMETER_MAX) { + (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -287,7 +298,12 @@ auto MQT_NA_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_JOB_PARAMETER_MAX) { + (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -297,7 +313,13 @@ auto MQT_NA_QDMI_Device_Job_impl_d::queryProperty( // NOLINTNEXTLINE(readability-non-const-parameter) const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_JOB_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -327,7 +349,11 @@ auto MQT_NA_QDMI_Device_Job_impl_d::getResults( QDMI_Job_Result result, // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { - if ((data != nullptr && size == 0) || result >= QDMI_JOB_RESULT_MAX) { + if ((data != nullptr && size == 0) || + (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && + result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && + result != QDMI_JOB_RESULT_CUSTOM4 && + result != QDMI_JOB_RESULT_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -367,7 +393,12 @@ auto MQT_NA_QDMI_Site_impl_d::makeUniqueZone(const uint64_t id, const int64_t x, auto MQT_NA_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_SITE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && + prop != QDMI_SITE_PROPERTY_CUSTOM2 && + prop != QDMI_SITE_PROPERTY_CUSTOM3 && + prop != QDMI_SITE_PROPERTY_CUSTOM4 && + prop != QDMI_SITE_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -535,7 +566,13 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( size_t* sizeRet) const -> int { if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || - (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX || + (value != nullptr && size == 0) || + (prop >= QDMI_OPERATION_PROPERTY_MAX && + prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM5) || (isZoned_ && numSites > 1) || (!isZoned_ && numSites > 0 && numQubits_ != numSites)) { return QDMI_ERROR_INVALIDARGUMENT; diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 3b248e2263..c994f69b12 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -157,7 +157,13 @@ auto Device::sessionFree(MQT_SC_QDMI_Device_Session session) -> void { } auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_DEVICE_PROPERTY_MAX && + prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && + prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -209,7 +215,12 @@ auto MQT_SC_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_SESSION_PARAMETER_MAX) { + (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && + param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -277,7 +288,12 @@ auto MQT_SC_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_JOB_PARAMETER_MAX) { + (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && + param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -287,7 +303,13 @@ auto MQT_SC_QDMI_Device_Job_impl_d::queryProperty( // NOLINTNEXTLINE(readability-non-const-parameter) const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_JOB_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && + prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -317,7 +339,11 @@ auto MQT_SC_QDMI_Device_Job_impl_d::getResults( QDMI_Job_Result result, // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { - if ((data != nullptr && size == 0) || result >= QDMI_JOB_RESULT_MAX) { + if ((data != nullptr && size == 0) || + (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && + result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && + result != QDMI_JOB_RESULT_CUSTOM4 && + result != QDMI_JOB_RESULT_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -330,7 +356,12 @@ auto MQT_SC_QDMI_Site_impl_d::makeUniqueSite(const uint64_t id) auto MQT_SC_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_SITE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && + prop != QDMI_SITE_PROPERTY_CUSTOM2 && + prop != QDMI_SITE_PROPERTY_CUSTOM3 && + prop != QDMI_SITE_PROPERTY_CUSTOM4 && + prop != QDMI_SITE_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -396,7 +427,13 @@ auto MQT_SC_QDMI_Operation_impl_d::queryProperty( size_t* sizeRet) const -> int { if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || - (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX) { + (value != nullptr && size == 0) || + (prop >= QDMI_OPERATION_PROPERTY_MAX && + prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && + prop != QDMI_OPERATION_PROPERTY_CUSTOM5)) { return QDMI_ERROR_INVALIDARGUMENT; } if (sites != nullptr) { From f8e719c9a853d7855fd56b0c68287d9e0deafafa Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 01:45:10 +0100 Subject: [PATCH 48/72] =?UTF-8?q?=F0=9F=90=9B=20Lock=20the=20right=20mutex?= =?UTF-8?q?=20in=20the=20DD=20QDMI=20device?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/dd/Device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index e5b93eb5d3..791e538444 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -314,7 +314,7 @@ auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, } auto Device::generateUniqueID() -> int { - const std::scoped_lock lock(sessionsMutex_); + const std::scoped_lock lock(rngMutex_); return dis_(rng_); } auto Device::setStatus(const QDMI_Device_Status status) -> void { From 6be3b684873a1b3b1ed08780032e1b0e6bd4b7e2 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 01:45:48 +0100 Subject: [PATCH 49/72] =?UTF-8?q?=F0=9F=8E=A8=20remove=20redundant=20initi?= =?UTF-8?q?alization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/qdmi/dd/Device.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index 0b1307ef97..cf85796d57 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -46,12 +46,12 @@ class Device final { std::unique_ptr> sessions_; /// Mutex protecting access to sessions_. - mutable std::mutex sessionsMutex_{}; + mutable std::mutex sessionsMutex_; /// RNG for generating unique IDs. std::mt19937_64 rng_{std::random_device{}()}; /// Mutex protecting RNG usage. - mutable std::mutex rngMutex_{}; + mutable std::mutex rngMutex_; /// Distribution for generating unique IDs. std::uniform_int_distribution<> dis_ = @@ -143,7 +143,7 @@ struct MQT_DDSIM_QDMI_Device_Session_impl_d { std::unique_ptr> jobs_; /// @brief Mutex protecting access to jobs_. - mutable std::mutex jobsMutex_{}; + mutable std::mutex jobsMutex_; public: /** From 5b7bd741f452906d9d7aa00e58ba39667f630251 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 01:59:55 +0100 Subject: [PATCH 50/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Enable=20thread-safe?= =?UTF-8?q?=20reference=20counting=20for=20QDMI=20devices=20singletons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 1 + include/mqt-core/na/device/Device.hpp | 4 +++- include/mqt-core/qdmi/dd/Device.hpp | 4 +++- include/mqt-core/qdmi/sc/Device.hpp | 4 +++- src/na/device/Device.cpp | 13 +++++++++---- src/qdmi/dd/Device.cpp | 13 +++++++++---- src/qdmi/sc/Device.cpp | 13 +++++++++---- 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49fc1de1a2..9e6941ad30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) - ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**]) - 👷 Stop testing on `ubuntu-22.04` and `ubuntu-22.04-arm` runners ([#1359]) ([**@denialhaag**], [**@burgholzer**]) - 👷 Stop testing with `clang-19` and start testing with `clang-21` ([#1359]) ([**@denialhaag**], [**@burgholzer**]) diff --git a/include/mqt-core/na/device/Device.hpp b/include/mqt-core/na/device/Device.hpp index 7f1ed958df..7aa6557cfd 100644 --- a/include/mqt-core/na/device/Device.hpp +++ b/include/mqt-core/na/device/Device.hpp @@ -70,7 +70,9 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance; + static std::atomic instance_; + /// @brief Reference count for the singleton instance. + static std::atomic refCount_; public: // Default move constructor and move assignment operator. diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index cf85796d57..8c7d0ea022 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -64,7 +64,9 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance; + static std::atomic instance_; + /// @brief Reference count for the singleton instance. + static std::atomic refCount_; public: // Default move constructor and move assignment operator. diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index 3376b6a06e..165add67fb 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -53,7 +53,9 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance; + static std::atomic instance_; + /// @brief Reference count for the singleton instance. + static std::atomic refCount_; public: // Delete move constructor and move assignment operator. diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp index 92c357e5d8..507f406c81 100644 --- a/src/na/device/Device.cpp +++ b/src/na/device/Device.cpp @@ -96,7 +96,8 @@ // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::na { -std::atomic Device::instance = nullptr; +std::atomic Device::instance_ = nullptr; +std::atomic Device::refCount_ = 0U; Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) @@ -116,23 +117,27 @@ void Device::initialize() { Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { + if (!instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } + refCount_.fetch_add(1); } void Device::finalize() { + if (const auto prev = refCount_.fetch_sub(1); prev > 1) { + return; + } // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); + const Device* oldInstance = instance_.exchange(nullptr); // Delete the old instance if it existed. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete oldInstance; } auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); + auto* loadedInstance = instance_.load(); assert(loadedInstance != nullptr && "Device not initialized. Call `initialize()` first."); return *loadedInstance; diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index 791e538444..974b243c8c 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -210,7 +210,8 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, namespace qdmi::dd { -std::atomic Device::instance = nullptr; +std::atomic Device::instance_ = nullptr; +std::atomic Device::refCount_ = 0U; Device::Device() : name_("MQT Core DDSIM QDMI Device"), @@ -221,23 +222,27 @@ void Device::initialize() { Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { + if (!instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } + refCount_.fetch_add(1); } void Device::finalize() { + if (const auto prev = refCount_.fetch_sub(1); prev > 1) { + return; + } // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); + const Device* oldInstance = instance_.exchange(nullptr); // Delete the old instance if it existed. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete oldInstance; } auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); + auto* loadedInstance = instance_.load(); assert(loadedInstance != nullptr && "Device not initialized. Call `initialize()` first."); return *loadedInstance; diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index c994f69b12..51b61e19f9 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -97,7 +97,8 @@ // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::sc { -std::atomic Device::instance = nullptr; +std::atomic Device::instance_ = nullptr; +std::atomic Device::refCount_ = 0U; Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) @@ -118,21 +119,25 @@ void Device::initialize() { Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { + if (!instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } + refCount_.fetch_add(1); } void Device::finalize() { + if (const auto prev = refCount_.fetch_sub(1); prev > 1) { + return; + } // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); + const Device* oldInstance = instance_.exchange(nullptr); // Delete the old instance if it existed. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete oldInstance; } auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); + auto* loadedInstance = instance_.load(); assert(loadedInstance != nullptr && "Device not initialized. Call `initialize()` first."); return *loadedInstance; From f9f0b6b4c7495c3d9173af2144a730dacacde763 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:04:33 +0100 Subject: [PATCH 51/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Allow=20repeated=20l?= =?UTF-8?q?oading=20of=20QDMI=20device=20library=20with=20potentially=20di?= =?UTF-8?q?fferent=20session=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 1 + bindings/fomac/fomac.cpp | 6 ++-- include/mqt-core/qdmi/Driver.hpp | 8 ++--- python/mqt/core/fomac.pyi | 9 ++---- src/qdmi/Driver.cpp | 25 +++------------ test/qdmi/test_driver.cpp | 52 ++++++-------------------------- 6 files changed, 22 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e6941ad30..8f93f4845c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**]) - ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) - ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**]) - 👷 Stop testing on `ubuntu-22.04` and `ubuntu-22.04-arm` runners ([#1359]) ([**@denialhaag**], [**@burgholzer**]) diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index fb3ebbfa0d..03de2ca837 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -240,7 +240,7 @@ 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) -> bool { + const std::optional& custom5 = std::nullopt) -> void { const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, .token = token, .authFile = authFile, @@ -252,8 +252,8 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .custom3 = custom3, .custom4 = custom4, .custom5 = custom5}; - return qdmi::Driver::get().addDynamicDeviceLibrary(libraryPath, prefix, - config); + qdmi::Driver::get().addDynamicDeviceLibrary(libraryPath, prefix, + config); }, "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/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 7e9700cac7..b169031355 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -446,19 +446,15 @@ class Driver final { * @param prefix The prefix used for the device interface functions in the * library. * @param config Configuration for device session parameters. - * @returns `true` if the library was successfully loaded, `false` if it was - * already loaded. * * @note This function is only available on non-Windows platforms. * - * @throws std::runtime_error If the library fails to load or the device - * cannot be initialized. + * @throws std::runtime_error If the device cannot be initialized. * @throws std::bad_alloc If memory allocation fails during the process. */ auto addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix, - const qdmi::DeviceSessionConfig& config = {}) - -> bool; + const DeviceSessionConfig& config = {}) -> void; #endif // _WIN32 /** * @brief Allocates a new session. diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index 282669d795..e2d6ad8d93 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, - ) -> bool: + ) -> None: """Load a dynamic device library into the QDMI driver. This function loads a shared library (.so, .dll, or .dylib) that implements @@ -320,9 +320,6 @@ if sys.platform != "win32": custom4: Optional custom configuration parameter 4. custom5: Optional custom configuration parameter 5. - Returns: - True if the library was successfully loaded, False if it was already loaded. - Raises: RuntimeError: If library loading fails or configuration is invalid. @@ -330,11 +327,9 @@ if sys.platform != "win32": Load a device library with configuration: >>> import mqt.core.fomac as fomac - >>> success = fomac.add_dynamic_device_library( + >>> fomac.add_dynamic_device_library( ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" ... ) - >>> if success: - ... print("Library loaded successfully") Now the device is available in sessions: diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 7e9f9509bb..590c38cd4a 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -165,12 +165,6 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, if (libHandle_ == nullptr) { throw std::runtime_error("Couldn't open the device library: " + libName); } - if (!openLibHandles().emplace(libHandle_).second) { - // dlopen uses reference counting, so we need to decrement the reference - // count that was increased by dlopen. - dlclose(libHandle_); - throw std::runtime_error("Device library already loaded: " + libName); - } //===----------------------------------------------------------------------===// // Macro for loading a symbol from the dynamic library. @@ -467,21 +461,10 @@ Driver::~Driver() { #ifndef _WIN32 auto Driver::addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix, - const qdmi::DeviceSessionConfig& config) - -> bool { - try { - devices_.emplace_back(std::make_unique( - std::make_unique(libName, prefix), config)); - } catch (const std::runtime_error& e) { - if (std::string(e.what()).starts_with("Device library already loaded:")) { - // The library is already loaded, so we can ignore this error but return - // false. - return false; - } - // Re-throw other exception - throw; - } - return true; + const DeviceSessionConfig& config) + -> void { + devices_.emplace_back(std::make_unique( + std::make_unique(libName, prefix), config)); } #endif diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index c1a9ed895e..1728f3b465 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -128,31 +128,6 @@ class DriverJobTest : public DriverTest { } }; -#ifndef _WIN32 -TEST(DriverTest, LoadLibraryTwice) { - // Test that loading the same library twice returns false (idempotent - // behavior). First load should succeed (return true), subsequent loads should - // return false. - const qdmi::DeviceSessionConfig config; - EXPECT_NO_THROW(for (const auto& [lib, prefix] : DYN_DEV_LIBS) { - // First attempt - should return true - const auto firstResult = - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - EXPECT_TRUE(firstResult) << "First load should return true"; - - // Second attempt - should return false (idempotent) - const auto secondResult = - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - EXPECT_FALSE(secondResult) << "Second load should return false"; - - // Third attempt - also should return false (idempotent) - const auto thirdResult = - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - EXPECT_FALSE(thirdResult) << "Third load should return false (idempotent)"; - }); -} -#endif // _WIN32 - TEST_P(DriverTest, SessionSetParameter) { const std::string authFile = "authfile.txt"; QDMI_Session uninitializedSession = nullptr; @@ -699,19 +674,18 @@ TEST(DeviceSessionConfigTest, VerifyDynamicDevicesInSession) { } TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { - if (!DYN_DEV_LIBS.empty()) { - // Make a copy to avoid dangling reference - const auto firstLib = DYN_DEV_LIBS[0]; - const auto& lib = firstLib.first; - const auto& prefix = firstLib.second; - + // This test is explicitly not part of the fixture because this would + // automatically load the default config and the respective libraries. + if constexpr (DYN_DEV_LIBS.empty()) { + GTEST_SKIP() << "No dynamic device libraries to test"; + } + auto& driver = qdmi::Driver::get(); + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { // Config 1: baseUrl { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:8080"; - EXPECT_NO_THROW({ - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - }); + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); } // Config 2: different baseUrl and custom parameters @@ -719,10 +693,7 @@ TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { qdmi::DeviceSessionConfig config; config.baseUrl = "http://localhost:9090"; config.custom1 = "API_V2"; - const auto result = - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - EXPECT_FALSE(result) - << "Library should already be loaded with first config"; + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); } // Config 3: authentication parameters @@ -730,10 +701,7 @@ TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { qdmi::DeviceSessionConfig config; config.token = "new_token"; config.authUrl = "https://auth.example.com"; - const auto result = - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); - EXPECT_FALSE(result) - << "Library should already be loaded with first config"; + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); } } } From 23b88465bc6d661bcd1591005470001f85454022 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:05:14 +0100 Subject: [PATCH 52/72] =?UTF-8?q?=F0=9F=8E=A8=20slightly=20adjust=20sessio?= =?UTF-8?q?n=20parameter=20setter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/Driver.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 590c38cd4a..afe0a29a4f 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -241,20 +241,21 @@ QDMI_Device_impl_d::QDMI_Device_impl_d( const auto status = static_cast(library_->device_session_set_parameter( deviceSession_, param, value->size() + 1, value->c_str())); + if (status == QDMI_SUCCESS) { + return; + } + if (status == QDMI_ERROR_NOTSUPPORTED) { - // Optional parameter not supported by device - skip it SPDLOG_INFO( "Device session parameter {} not supported by device (skipped)", qdmi::toString(param)); return; } - if (status != QDMI_SUCCESS) { - library_->device_session_free(deviceSession_); - std::ostringstream ss; - ss << "Failed to set device session parameter " << qdmi::toString(param) - << ": " << qdmi::toString(status); - throw std::runtime_error(ss.str()); - } + library_->device_session_free(deviceSession_); + std::ostringstream ss; + ss << "Failed to set device session parameter " << qdmi::toString(param) + << ": " << qdmi::toString(status); + throw std::runtime_error(ss.str()); } }; From 951035b8c7bffbb275d057bd10f48ea4d5f23edf Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:35:55 +0100 Subject: [PATCH 53/72] =?UTF-8?q?=F0=9F=8E=A8=20reduce=20redundancy=20via?= =?UTF-8?q?=20new=20macro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/na/device/Device.cpp | 51 +++++++++-------------------------- src/qdmi/dd/Device.cpp | 58 +++++++++------------------------------- src/qdmi/sc/Device.cpp | 51 +++++++++-------------------------- 3 files changed, 37 insertions(+), 123 deletions(-) diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp index 507f406c81..12978f80b2 100644 --- a/src/na/device/Device.cpp +++ b/src/na/device/Device.cpp @@ -93,6 +93,11 @@ return QDMI_SUCCESS; \ } \ } + +#define IS_INVALID_ARGUMENT(prop, prefix) \ + ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ + (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::na { @@ -164,12 +169,7 @@ auto Device::sessionFree(MQT_NA_QDMI_Device_Session session) -> void { auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_PROPERTY_MAX && - prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -230,12 +230,7 @@ auto MQT_NA_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -303,12 +298,7 @@ auto MQT_NA_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -319,12 +309,7 @@ auto MQT_NA_QDMI_Device_Job_impl_d::queryProperty( const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -355,10 +340,7 @@ auto MQT_NA_QDMI_Device_Job_impl_d::getResults( // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { if ((data != nullptr && size == 0) || - (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && - result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && - result != QDMI_JOB_RESULT_CUSTOM4 && - result != QDMI_JOB_RESULT_CUSTOM5)) { + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -399,11 +381,7 @@ auto MQT_NA_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && - prop != QDMI_SITE_PROPERTY_CUSTOM2 && - prop != QDMI_SITE_PROPERTY_CUSTOM3 && - prop != QDMI_SITE_PROPERTY_CUSTOM4 && - prop != QDMI_SITE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -572,12 +550,7 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || (value != nullptr && size == 0) || - (prop >= QDMI_OPERATION_PROPERTY_MAX && - prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM5) || + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY) || (isZoned_ && numSites > 1) || (!isZoned_ && numSites > 0 && numQubits_ != numSites)) { return QDMI_ERROR_INVALIDARGUMENT; diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index 974b243c8c..b664cd25ac 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -206,6 +206,11 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, return QDMI_SUCCESS; \ } \ } + +#define IS_INVALID_ARGUMENT(prop, prefix) \ + ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ + (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::dd { @@ -272,12 +277,7 @@ auto Device::sessionFree(MQT_DDSIM_QDMI_Device_Session session) -> void { auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_PROPERTY_MAX && - prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -349,12 +349,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::setParameter( const QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -398,11 +393,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::querySiteProperty( return QDMI_ERROR_BADSTATE; } if (site == nullptr || (value != nullptr && size == 0) || - (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && - prop != QDMI_SITE_PROPERTY_CUSTOM2 && - prop != QDMI_SITE_PROPERTY_CUSTOM3 && - prop != QDMI_SITE_PROPERTY_CUSTOM4 && - prop != QDMI_SITE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto id = @@ -422,12 +413,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::queryOperationProperty( if (operation == nullptr || (sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || (value != nullptr && size == 0) || - (prop >= QDMI_OPERATION_PROPERTY_MAX && - prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto& [name_, numSites_, numParams_, isVariadic] = @@ -458,12 +444,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_.load() != QDMI_JOB_STATUS_CREATED) { @@ -473,12 +454,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( case QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT: if (value != nullptr) { const auto format = *static_cast(value); - if (format >= QDMI_PROGRAM_FORMAT_MAX && - format != QDMI_PROGRAM_FORMAT_CUSTOM1 && - format != QDMI_PROGRAM_FORMAT_CUSTOM2 && - format != QDMI_PROGRAM_FORMAT_CUSTOM3 && - format != QDMI_PROGRAM_FORMAT_CUSTOM4 && - format != QDMI_PROGRAM_FORMAT_CUSTOM5) { + if (IS_INVALID_ARGUMENT(format, QDMI_PROGRAM_FORMAT)) { return QDMI_ERROR_INVALIDARGUMENT; } if (format != QDMI_PROGRAM_FORMAT_QASM2 && @@ -507,12 +483,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::queryProperty( const QDMI_Device_Job_Property prop, const size_t size, void* value, size_t* sizeRet) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto str = std::to_string(id_); @@ -779,10 +750,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getResults(const QDMI_Job_Result result, size_t* sizeRet) -> QDMI_STATUS { if ((data != nullptr && size == 0) || - (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && - result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && - result != QDMI_JOB_RESULT_CUSTOM4 && - result != QDMI_JOB_RESULT_CUSTOM5)) { + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_.load() != QDMI_JOB_STATUS_DONE) { diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 51b61e19f9..9b12b111b1 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -94,6 +94,11 @@ return QDMI_SUCCESS; \ } \ } + +#define IS_INVALID_ARGUMENT(prop, prefix) \ + ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ + (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::sc { @@ -163,12 +168,7 @@ auto Device::sessionFree(MQT_SC_QDMI_Device_Session session) -> void { auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_PROPERTY_MAX && - prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -220,12 +220,7 @@ auto MQT_SC_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -293,12 +288,7 @@ auto MQT_SC_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -309,12 +299,7 @@ auto MQT_SC_QDMI_Device_Job_impl_d::queryProperty( const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -345,10 +330,7 @@ auto MQT_SC_QDMI_Device_Job_impl_d::getResults( // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { if ((data != nullptr && size == 0) || - (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && - result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && - result != QDMI_JOB_RESULT_CUSTOM4 && - result != QDMI_JOB_RESULT_CUSTOM5)) { + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -362,11 +344,7 @@ auto MQT_SC_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { if ((value != nullptr && size == 0) || - (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && - prop != QDMI_SITE_PROPERTY_CUSTOM2 && - prop != QDMI_SITE_PROPERTY_CUSTOM3 && - prop != QDMI_SITE_PROPERTY_CUSTOM4 && - prop != QDMI_SITE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -433,12 +411,7 @@ auto MQT_SC_QDMI_Operation_impl_d::queryProperty( if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || (value != nullptr && size == 0) || - (prop >= QDMI_OPERATION_PROPERTY_MAX && - prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } if (sites != nullptr) { From 8e84370edf99ca128dc4ed7dcffc86c29c6db561 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:42:22 +0100 Subject: [PATCH 54/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20slightly=20refactor?= =?UTF-8?q?=20reference=20counting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/na/device/Device.cpp | 8 +++++--- src/qdmi/dd/Device.cpp | 8 +++++--- src/qdmi/sc/Device.cpp | 8 +++++--- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp index 12978f80b2..848000d38f 100644 --- a/src/na/device/Device.cpp +++ b/src/na/device/Device.cpp @@ -118,19 +118,21 @@ Device::Device() { } void Device::initialize() { + // Always increment the reference count when initialize is called. + refCount_.fetch_add(1); // NOLINTNEXTLINE(misc-const-correctness) Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance_.compare_exchange_strong(expected, newInstance)) { + if (auto* newInstance = new Device(); + !instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } - refCount_.fetch_add(1); } void Device::finalize() { + // Always decrement the reference count when finalize is called. if (const auto prev = refCount_.fetch_sub(1); prev > 1) { return; } diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index b664cd25ac..38bb21e0c1 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -223,19 +223,21 @@ Device::Device() qubitsNum_(std::numeric_limits<::dd::Qubit>::max()) {} void Device::initialize() { + // Always increment the reference count when initialize is called. + refCount_.fetch_add(1); // NOLINTNEXTLINE(misc-const-correctness) Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance_.compare_exchange_strong(expected, newInstance)) { + if (auto* newInstance = new Device(); + !instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } - refCount_.fetch_add(1); } void Device::finalize() { + // Always decrement the reference count when finalize is called. if (const auto prev = refCount_.fetch_sub(1); prev > 1) { return; } diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 9b12b111b1..41dc531738 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -120,18 +120,20 @@ Device::~Device() { sessions_.clear(); } void Device::initialize() { + // Always increment the reference count when initialize is called. + refCount_.fetch_add(1); // NOLINTNEXTLINE(misc-const-correctness) Device* expected = nullptr; // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance_.compare_exchange_strong(expected, newInstance)) { + if (auto* newInstance = new Device(); + !instance_.compare_exchange_strong(expected, newInstance)) { // Another thread won the race, so delete the instance we created. // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete newInstance; } - refCount_.fetch_add(1); } void Device::finalize() { + // Always decrement the reference count when finalize is called. if (const auto prev = refCount_.fetch_sub(1); prev > 1) { return; } From d617428baa40ac91e0a27b8a0179cd0186eb8675 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:43:28 +0100 Subject: [PATCH 55/72] =?UTF-8?q?=F0=9F=94=A5=20remove=20openLibHandles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/qdmi/Driver.hpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index b169031355..34fd17f124 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -128,16 +128,6 @@ class DynamicDeviceLibrary final : public DeviceLibrary { /// @brief Handle to the dynamic library returned by `dlopen`. void* libHandle_; - /** - * @brief Returns a reference to the set of open library handles. - * @details This set can be used to check whether a newly opened library - * is already open. - */ - static auto openLibHandles() -> std::unordered_set& { - static std::unordered_set libHandles; - return libHandles; - } - public: /** * @brief Constructs a DynamicDeviceLibrary object. From d6a546764a94b20c7cd818f22ebb5af1039767ab Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 02:47:47 +0100 Subject: [PATCH 56/72] =?UTF-8?q?=E2=9C=85=20add=20one=20more=20test=20cas?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- test/fomac/test_fomac.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index ea651127eb..064760b608 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -801,6 +801,11 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { SessionConfig config6; config6.authUrl = "example.com"; EXPECT_THROW({ const Session session(config6); }, std::runtime_error); + + // Invalid URL - empty + SessionConfig config7; + config7.authUrl = ""; + EXPECT_THROW({ const Session session(config7); }, std::runtime_error); } TEST(AuthenticationTest, SessionConstructionWithAuthFile) { From e432e5646d6a3b813c6d7e4f210409975235935d Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 03:06:57 +0100 Subject: [PATCH 57/72] =?UTF-8?q?=F0=9F=9A=9A=20move=20`NA`=20QDMI=20devic?= =?UTF-8?q?e=20in=20its=20right=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 1 + bindings/na/fomac/fomac.cpp | 2 +- include/mqt-core/na/fomac/Device.hpp | 2 +- include/mqt-core/{na/device => qdmi/na}/Device.hpp | 0 include/mqt-core/{na/device => qdmi/na}/Generator.hpp | 0 src/na/CMakeLists.txt | 2 -- src/na/fomac/Device.cpp | 2 +- src/qdmi/CMakeLists.txt | 1 + src/{na/device => qdmi/na}/App.cpp | 2 +- src/{na/device => qdmi/na}/CMakeLists.txt | 8 ++++---- src/{na/device => qdmi/na}/Device.cpp | 4 ++-- src/{na/device => qdmi/na}/DynDevice.cpp | 0 src/{na/device => qdmi/na}/Generator.cpp | 2 +- test/na/device/test_device.cpp | 2 +- test/na/device/test_generator.cpp | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) rename include/mqt-core/{na/device => qdmi/na}/Device.hpp (100%) rename include/mqt-core/{na/device => qdmi/na}/Generator.hpp (100%) rename src/{na/device => qdmi/na}/App.cpp (99%) rename src/{na/device => qdmi/na}/CMakeLists.txt (96%) rename src/{na/device => qdmi/na}/Device.cpp (99%) rename src/{na/device => qdmi/na}/DynDevice.cpp (100%) rename src/{na/device => qdmi/na}/Generator.cpp (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f93f4845c..a409070031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- 🚚 Move `NA` QDMI device in its right place next to other QDMI devices ([#1355]) ([**@burgholzer**]) - ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**]) - ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) - ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**]) diff --git a/bindings/na/fomac/fomac.cpp b/bindings/na/fomac/fomac.cpp index 2f65fae240..c292f1cf14 100644 --- a/bindings/na/fomac/fomac.cpp +++ b/bindings/na/fomac/fomac.cpp @@ -12,7 +12,7 @@ // clang-format off #include "fomac/FoMaC.hpp" #include "na/fomac/Device.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/include/mqt-core/na/fomac/Device.hpp b/include/mqt-core/na/fomac/Device.hpp index 01d495bf06..58049436c3 100644 --- a/include/mqt-core/na/fomac/Device.hpp +++ b/include/mqt-core/na/fomac/Device.hpp @@ -11,7 +11,7 @@ #pragma once #include "fomac/FoMaC.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" // NOLINTNEXTLINE(misc-include-cleaner) #include diff --git a/include/mqt-core/na/device/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp similarity index 100% rename from include/mqt-core/na/device/Device.hpp rename to include/mqt-core/qdmi/na/Device.hpp diff --git a/include/mqt-core/na/device/Generator.hpp b/include/mqt-core/qdmi/na/Generator.hpp similarity index 100% rename from include/mqt-core/na/device/Generator.hpp rename to include/mqt-core/qdmi/na/Generator.hpp diff --git a/src/na/CMakeLists.txt b/src/na/CMakeLists.txt index 024e4eb492..b75056ee2b 100644 --- a/src/na/CMakeLists.txt +++ b/src/na/CMakeLists.txt @@ -6,8 +6,6 @@ # # Licensed under the MIT License -# add subdirectories -add_subdirectory(device) add_subdirectory(fomac) if(NOT TARGET ${MQT_CORE_TARGET_NAME}-na) diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index 9214745f6c..c7d1f6c068 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -12,7 +12,7 @@ #include "fomac/FoMaC.hpp" #include "ir/Definitions.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index a3f291fbfc..455308d64d 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(dd) add_subdirectory(sc) +add_subdirectory(na) set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-driver) diff --git a/src/na/device/App.cpp b/src/qdmi/na/App.cpp similarity index 99% rename from src/na/device/App.cpp rename to src/qdmi/na/App.cpp index 9901218b2a..0d31c80f09 100644 --- a/src/na/device/App.cpp +++ b/src/qdmi/na/App.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/src/na/device/CMakeLists.txt b/src/qdmi/na/CMakeLists.txt similarity index 96% rename from src/na/device/CMakeLists.txt rename to src/qdmi/na/CMakeLists.txt index f6dcda1759..a075409ffd 100644 --- a/src/na/device/CMakeLists.txt +++ b/src/qdmi/na/CMakeLists.txt @@ -24,7 +24,7 @@ if(NOT TARGET ${TARGET_NAME}) # add headers using file sets target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} - FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Generator.hpp) + FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Generator.hpp) # Link nlohmann_json, spdlog target_link_libraries( @@ -91,7 +91,7 @@ if(NOT TARGET ${TARGET_NAME}) # Set paths set(JSON_FILE ${PROJECT_SOURCE_DIR}/json/na/device.json) - set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/na/device/DeviceMemberInitializers.hpp) + set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/na/DeviceMemberInitializers.hpp) # Create include directory file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/na/device) @@ -125,7 +125,7 @@ if(NOT TARGET ${TARGET_NAME}) ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Device.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp ${QDMI_HDRS}) # add link libraries @@ -185,7 +185,7 @@ if(NOT TARGET ${TARGET_NAME}) ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Device.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp ${QDMI_HDRS}) # add link libraries target_link_libraries( diff --git a/src/na/device/Device.cpp b/src/qdmi/na/Device.cpp similarity index 99% rename from src/na/device/Device.cpp rename to src/qdmi/na/Device.cpp index 848000d38f..3a64cb10ba 100644 --- a/src/na/device/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -12,10 +12,10 @@ * @brief The MQT QDMI device implementation for neutral atom devices. */ -#include "na/device/Device.hpp" +#include "qdmi/na/Device.hpp" #include "mqt_na_qdmi/device.h" -#include "na/device/DeviceMemberInitializers.hpp" +#include "qdmi/na/DeviceMemberInitializers.hpp" #include #include diff --git a/src/na/device/DynDevice.cpp b/src/qdmi/na/DynDevice.cpp similarity index 100% rename from src/na/device/DynDevice.cpp rename to src/qdmi/na/DynDevice.cpp diff --git a/src/na/device/Generator.cpp b/src/qdmi/na/Generator.cpp similarity index 99% rename from src/na/device/Generator.cpp rename to src/qdmi/na/Generator.cpp index 978b26e21b..90f64f1c21 100644 --- a/src/na/device/Generator.cpp +++ b/src/qdmi/na/Generator.cpp @@ -12,7 +12,7 @@ * @brief The MQT QDMI device generator for neutral atom devices. */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/test/na/device/test_device.cpp b/test/na/device/test_device.cpp index abc07fa292..d155f44982 100644 --- a/test/na/device/test_device.cpp +++ b/test/na/device/test_device.cpp @@ -9,7 +9,7 @@ */ #include "mqt_na_qdmi/device.h" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/test/na/device/test_generator.cpp b/test/na/device/test_generator.cpp index f66b5e9193..aa048c75c6 100644 --- a/test/na/device/test_generator.cpp +++ b/test/na/device/test_generator.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include From 2e20fab116cdc51b472b3fac34c30d353d0d2185 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 03:15:38 +0100 Subject: [PATCH 58/72] =?UTF-8?q?=F0=9F=A9=B9=20fix=20include=20dir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/na/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qdmi/na/CMakeLists.txt b/src/qdmi/na/CMakeLists.txt index a075409ffd..9a9bd3ee47 100644 --- a/src/qdmi/na/CMakeLists.txt +++ b/src/qdmi/na/CMakeLists.txt @@ -94,7 +94,7 @@ if(NOT TARGET ${TARGET_NAME}) set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/na/DeviceMemberInitializers.hpp) # Create include directory - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/na/device) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/na) # Generate definitions for device add_custom_command( From 80e88dc9dd5393aede8b2c93864cd3d3932ffd60 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 03:26:57 +0100 Subject: [PATCH 59/72] =?UTF-8?q?=F0=9F=93=9D=20better=20singleton=20manag?= =?UTF-8?q?ement=20docstrings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/qdmi/dd/Device.hpp | 14 ++++++++++---- include/mqt-core/qdmi/na/Device.hpp | 14 ++++++++++---- include/mqt-core/qdmi/sc/Device.hpp | 14 ++++++++++---- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index 8c7d0ea022..9e4cfc60a4 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -81,14 +81,20 @@ class Device final { /** * @brief Initializes the singleton instance. - * @details Must be called before `get()`. + * @details Must be called before using `get()`. + * Each call to `initialize()` must be paired with exactly one call to + * `finalize()`. Multiple threads may call `initialize()` and `finalize()` + * independently, with the instance being destroyed only when the last thread + * calls `finalize()`. */ static void initialize(); /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. + * @brief Decrements the reference count and destroys the singleton instance + * if this is the last active reference. + * @details This must be called exactly once per call to `initialize()`. + * After calling `finalize()`, `get()` must not be called until `initialize()` + * is called again. */ static void finalize(); diff --git a/include/mqt-core/qdmi/na/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp index 7aa6557cfd..7d35d5a081 100644 --- a/include/mqt-core/qdmi/na/Device.hpp +++ b/include/mqt-core/qdmi/na/Device.hpp @@ -87,14 +87,20 @@ class Device final { /** * @brief Initializes the singleton instance. - * @details Must be called before `get()`. + * @details Must be called before using `get()`. + * Each call to `initialize()` must be paired with exactly one call to + * `finalize()`. Multiple threads may call `initialize()` and `finalize()` + * independently, with the instance being destroyed only when the last thread + * calls `finalize()`. */ static void initialize(); /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. + * @brief Decrements the reference count and destroys the singleton instance + * if this is the last active reference. + * @details This must be called exactly once per call to `initialize()`. + * After calling `finalize()`, `get()` must not be called until `initialize()` + * is called again. */ static void finalize(); diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index 165add67fb..57ae82be4a 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -70,14 +70,20 @@ class Device final { /** * @brief Initializes the singleton instance. - * @details Must be called before `get()`. + * @details Must be called before using `get()`. + * Each call to `initialize()` must be paired with exactly one call to + * `finalize()`. Multiple threads may call `initialize()` and `finalize()` + * independently, with the instance being destroyed only when the last thread + * calls `finalize()`. */ static void initialize(); /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. + * @brief Decrements the reference count and destroys the singleton instance + * if this is the last active reference. + * @details This must be called exactly once per call to `initialize()`. + * After calling `finalize()`, `get()` must not be called until `initialize()` + * is called again. */ static void finalize(); From 086f7b20cc2565b5dff80f795a326fc4c2b62e77 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 08:03:20 +0100 Subject: [PATCH 60/72] =?UTF-8?q?=F0=9F=A9=B9=20fix=20invalid=20argument?= =?UTF-8?q?=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/dd/Device.cpp | 2 +- src/qdmi/na/Device.cpp | 2 +- src/qdmi/sc/Device.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index 38bb21e0c1..bfb25db23e 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -208,7 +208,7 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, } #define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) diff --git a/src/qdmi/na/Device.cpp b/src/qdmi/na/Device.cpp index 3a64cb10ba..c2af0a6883 100644 --- a/src/qdmi/na/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -95,7 +95,7 @@ } #define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 41dc531738..7e15978b03 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -96,7 +96,7 @@ } #define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) > prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) // NOLINTEND(bugprone-macro-parentheses) From 0b744455a8fc3f452ae865ff29b1aa63e2d6e934 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 10:07:01 +0100 Subject: [PATCH 61/72] =?UTF-8?q?=F0=9F=9A=A8=20additional=20clang-tidy=20?= =?UTF-8?q?naming=20convention=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .clang-tidy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index 515cf5881a..f8a072a8da 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -37,6 +37,10 @@ CheckOptions: value: CamelCase - key: readability-identifier-naming.ClassIgnoredRegexp value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" + - key: readability-identifier-naming.ClassMemberCase + value: camelBack + - key: readability-identifier-naming.ClassMemberIgnoredRegexp + value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*|.*_" - key: readability-identifier-naming.ConstantParameterCase value: camelBack - key: readability-identifier-naming.EnumCase From 6d3dc520d3dc5e7609be888378684979a690ec90 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 10:08:13 +0100 Subject: [PATCH 62/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20rework=20thread-safe?= =?UTF-8?q?=20singleton=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/qdmi/dd/Device.hpp | 7 +++-- include/mqt-core/qdmi/na/Device.hpp | 9 ++++-- include/mqt-core/qdmi/sc/Device.hpp | 10 ++++--- src/qdmi/dd/Device.cpp | 45 +++++++++++----------------- src/qdmi/na/Device.cpp | 46 +++++++++++------------------ src/qdmi/sc/Device.cpp | 41 +++++++++++-------------- 6 files changed, 70 insertions(+), 88 deletions(-) diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index 9e4cfc60a4..45299b46b0 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -64,9 +64,12 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance_; + inline static Device* instance_ = nullptr; /// @brief Reference count for the singleton instance. - static std::atomic refCount_; + inline static size_t refCount_ = 0U; + /// @brief Mutex for synchronizing access to the singleton instance. + inline static auto* // NOLINT(cppcoreguidelines-owning-memory) + instanceMutex_ = new std::mutex; public: // Default move constructor and move assignment operator. diff --git a/include/mqt-core/qdmi/na/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp index 7d35d5a081..0f439cc456 100644 --- a/include/mqt-core/qdmi/na/Device.hpp +++ b/include/mqt-core/qdmi/na/Device.hpp @@ -16,10 +16,10 @@ #include "mqt_na_qdmi/device.h" -#include #include #include #include +#include #include #include #include @@ -70,9 +70,12 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance_; + inline static Device* instance_ = nullptr; /// @brief Reference count for the singleton instance. - static std::atomic refCount_; + inline static size_t refCount_ = 0U; + /// @brief Mutex for synchronizing access to the singleton instance. + inline static auto* // NOLINT(cppcoreguidelines-owning-memory) + instanceMutex_ = new std::mutex; public: // Default move constructor and move assignment operator. diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index 57ae82be4a..bcfc7235f6 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -16,11 +16,10 @@ #include "mqt_sc_qdmi/device.h" -#include #include #include #include -#include +#include #include #include #include @@ -53,9 +52,12 @@ class Device final { Device(); /// @brief The singleton instance. - static std::atomic instance_; + inline static Device* instance_ = nullptr; /// @brief Reference count for the singleton instance. - static std::atomic refCount_; + inline static size_t refCount_ = 0U; + /// @brief Mutex for synchronizing access to the singleton instance. + inline static auto* // NOLINT(cppcoreguidelines-owning-memory) + instanceMutex_ = new std::mutex; public: // Delete move constructor and move assignment operator. diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index bfb25db23e..de8d8b7fc4 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -214,47 +214,36 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::dd { - -std::atomic Device::instance_ = nullptr; -std::atomic Device::refCount_ = 0U; - Device::Device() : name_("MQT Core DDSIM QDMI Device"), qubitsNum_(std::numeric_limits<::dd::Qubit>::max()) {} - void Device::initialize() { + const std::scoped_lock lock(*instanceMutex_); // Always increment the reference count when initialize is called. - refCount_.fetch_add(1); - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - if (auto* newInstance = new Device(); - !instance_.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. + ++refCount_; + if (instance_ == nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; + instance_ = new Device(); } } - void Device::finalize() { - // Always decrement the reference count when finalize is called. - if (const auto prev = refCount_.fetch_sub(1); prev > 1) { - return; + const std::scoped_lock lock(*instanceMutex_); + --refCount_; + if (refCount_ == 0) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete instance_; + instance_ = nullptr; } - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance_.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; + // Do NOT delete the static mutex. The mutex is intentionally leaked to + // avoid static deinitialization issues (cf. static deinitialization order + // fiasco) } - auto Device::get() -> Device& { - auto* loadedInstance = instance_.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // May only be called after `initialize()` has been called. + // Thus, `instance_` should not be null. + assert(instance_ != nullptr && "Device not initialized."); + return *instance_; } - auto Device::sessionAlloc(MQT_DDSIM_QDMI_Device_Session* session) -> QDMI_STATUS { if (session == nullptr) { diff --git a/src/qdmi/na/Device.cpp b/src/qdmi/na/Device.cpp index c2af0a6883..41d352eeef 100644 --- a/src/qdmi/na/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -18,13 +18,13 @@ #include "qdmi/na/DeviceMemberInitializers.hpp" #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -101,9 +101,6 @@ // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::na { -std::atomic Device::instance_ = nullptr; -std::atomic Device::refCount_ = 0U; - Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) INITIALIZE_NAME(name_); @@ -116,40 +113,33 @@ Device::Device() { INITIALIZE_SITES(sites_); INITIALIZE_OPERATIONS(operations_); } - void Device::initialize() { + const std::scoped_lock lock(*instanceMutex_); // Always increment the reference count when initialize is called. - refCount_.fetch_add(1); - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - if (auto* newInstance = new Device(); - !instance_.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. + ++refCount_; + if (instance_ == nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; + instance_ = new Device(); } } - void Device::finalize() { - // Always decrement the reference count when finalize is called. - if (const auto prev = refCount_.fetch_sub(1); prev > 1) { - return; + const std::scoped_lock lock(*instanceMutex_); + --refCount_; + if (refCount_ == 0) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete instance_; + instance_ = nullptr; } - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance_.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; + // Do NOT delete the static mutex. The mutex is intentionally leaked to + // avoid static deinitialization issues (cf. static deinitialization order + // fiasco) } - auto Device::get() -> Device& { - auto* loadedInstance = instance_.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // May only be called after `initialize()` has been called. + // Thus, `instance_` should not be null. + assert(instance_ != nullptr && "Device not initialized."); + return *instance_; } - auto Device::sessionAlloc(MQT_NA_QDMI_Device_Session* session) -> int { if (session == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 7e15978b03..d2ac9b053f 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -18,13 +18,13 @@ #include "qdmi/sc/DeviceMemberInitializers.hpp" #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -102,8 +102,6 @@ // NOLINTEND(bugprone-macro-parentheses) namespace qdmi::sc { -std::atomic Device::instance_ = nullptr; -std::atomic Device::refCount_ = 0U; Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) @@ -120,34 +118,31 @@ Device::~Device() { sessions_.clear(); } void Device::initialize() { + const std::scoped_lock lock(*instanceMutex_); // Always increment the reference count when initialize is called. - refCount_.fetch_add(1); - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - if (auto* newInstance = new Device(); - !instance_.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. + ++refCount_; + if (instance_ == nullptr) { // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; + instance_ = new Device(); } } void Device::finalize() { - // Always decrement the reference count when finalize is called. - if (const auto prev = refCount_.fetch_sub(1); prev > 1) { - return; + const std::scoped_lock lock(*instanceMutex_); + --refCount_; + if (refCount_ == 0) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete instance_; + instance_ = nullptr; } - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance_.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; + // Do NOT delete the static mutex. The mutex is intentionally leaked to + // avoid static deinitialization issues (cf. static deinitialization order + // fiasco) } auto Device::get() -> Device& { - auto* loadedInstance = instance_.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // May only be called after `initialize()` has been called. + // Thus, `instance_` should not be null. + assert(instance_ != nullptr && "Device not initialized."); + return *instance_; } auto Device::sessionAlloc(MQT_SC_QDMI_Device_Session* session) -> int { if (session == nullptr) { From cb6f1c273102c69a89f1f30eb1c970b9c8f34afc Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 11:12:14 +0100 Subject: [PATCH 63/72] =?UTF-8?q?Revert=20"=F0=9F=9A=A8=20additional=20cla?= =?UTF-8?q?ng-tidy=20naming=20convention=20rule"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0b744455a8fc3f452ae865ff29b1aa63e2d6e934. --- .clang-tidy | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index f8a072a8da..515cf5881a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -37,10 +37,6 @@ CheckOptions: value: CamelCase - key: readability-identifier-naming.ClassIgnoredRegexp value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" - - key: readability-identifier-naming.ClassMemberCase - value: camelBack - - key: readability-identifier-naming.ClassMemberIgnoredRegexp - value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*|.*_" - key: readability-identifier-naming.ConstantParameterCase value: camelBack - key: readability-identifier-naming.EnumCase From 676a8a1986788c5d41506e6c2bbc872c8eaca33c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 11:13:42 +0100 Subject: [PATCH 64/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20simplify=20handling?= =?UTF-8?q?=20of=20static=20deinitialization=20order=20fiasco?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/qdmi/dd/Device.hpp | 27 -------------------- include/mqt-core/qdmi/na/Device.hpp | 27 -------------------- include/mqt-core/qdmi/sc/Device.hpp | 27 -------------------- src/qdmi/dd/Device.cpp | 38 ++++++----------------------- src/qdmi/na/Device.cpp | 38 ++++++----------------------- src/qdmi/sc/Device.cpp | 38 ++++++----------------------- 6 files changed, 24 insertions(+), 171 deletions(-) diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index 45299b46b0..4247306e27 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -63,14 +63,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - inline static Device* instance_ = nullptr; - /// @brief Reference count for the singleton instance. - inline static size_t refCount_ = 0U; - /// @brief Mutex for synchronizing access to the singleton instance. - inline static auto* // NOLINT(cppcoreguidelines-owning-memory) - instanceMutex_ = new std::mutex; - public: // Default move constructor and move assignment operator. Device(Device&&) = delete; @@ -82,25 +74,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device() = default; - /** - * @brief Initializes the singleton instance. - * @details Must be called before using `get()`. - * Each call to `initialize()` must be paired with exactly one call to - * `finalize()`. Multiple threads may call `initialize()` and `finalize()` - * independently, with the instance being destroyed only when the last thread - * calls `finalize()`. - */ - static void initialize(); - - /** - * @brief Decrements the reference count and destroys the singleton instance - * if this is the last active reference. - * @details This must be called exactly once per call to `initialize()`. - * After calling `finalize()`, `get()` must not be called until `initialize()` - * is called again. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; diff --git a/include/mqt-core/qdmi/na/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp index 0f439cc456..b847d7fb59 100644 --- a/include/mqt-core/qdmi/na/Device.hpp +++ b/include/mqt-core/qdmi/na/Device.hpp @@ -69,14 +69,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - inline static Device* instance_ = nullptr; - /// @brief Reference count for the singleton instance. - inline static size_t refCount_ = 0U; - /// @brief Mutex for synchronizing access to the singleton instance. - inline static auto* // NOLINT(cppcoreguidelines-owning-memory) - instanceMutex_ = new std::mutex; - public: // Default move constructor and move assignment operator. Device(Device&&) = default; @@ -88,25 +80,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device() = default; - /** - * @brief Initializes the singleton instance. - * @details Must be called before using `get()`. - * Each call to `initialize()` must be paired with exactly one call to - * `finalize()`. Multiple threads may call `initialize()` and `finalize()` - * independently, with the instance being destroyed only when the last thread - * calls `finalize()`. - */ - static void initialize(); - - /** - * @brief Decrements the reference count and destroys the singleton instance - * if this is the last active reference. - * @details This must be called exactly once per call to `initialize()`. - * After calling `finalize()`, `get()` must not be called until `initialize()` - * is called again. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index bcfc7235f6..4c6eeec4d9 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -51,14 +51,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - inline static Device* instance_ = nullptr; - /// @brief Reference count for the singleton instance. - inline static size_t refCount_ = 0U; - /// @brief Mutex for synchronizing access to the singleton instance. - inline static auto* // NOLINT(cppcoreguidelines-owning-memory) - instanceMutex_ = new std::mutex; - public: // Delete move constructor and move assignment operator. Device(Device&&) = delete; @@ -70,25 +62,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device(); - /** - * @brief Initializes the singleton instance. - * @details Must be called before using `get()`. - * Each call to `initialize()` must be paired with exactly one call to - * `finalize()`. Multiple threads may call `initialize()` and `finalize()` - * independently, with the instance being destroyed only when the last thread - * calls `finalize()`. - */ - static void initialize(); - - /** - * @brief Decrements the reference count and destroys the singleton instance - * if this is the last active reference. - * @details This must be called exactly once per call to `initialize()`. - * After calling `finalize()`, `get()` must not be called until `initialize()` - * is called again. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index de8d8b7fc4..5611b7a334 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -217,32 +217,12 @@ namespace qdmi::dd { Device::Device() : name_("MQT Core DDSIM QDMI Device"), qubitsNum_(std::numeric_limits<::dd::Qubit>::max()) {} -void Device::initialize() { - const std::scoped_lock lock(*instanceMutex_); - // Always increment the reference count when initialize is called. - ++refCount_; - if (instance_ == nullptr) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - instance_ = new Device(); - } -} -void Device::finalize() { - const std::scoped_lock lock(*instanceMutex_); - --refCount_; - if (refCount_ == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete instance_; - instance_ = nullptr; - } - // Do NOT delete the static mutex. The mutex is intentionally leaked to - // avoid static deinitialization issues (cf. static deinitialization order - // fiasco) -} auto Device::get() -> Device& { - // May only be called after `initialize()` has been called. - // Thus, `instance_` should not be null. - assert(instance_ != nullptr && "Device not initialized."); - return *instance_; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } auto Device::sessionAlloc(MQT_DDSIM_QDMI_Device_Session* session) -> QDMI_STATUS { @@ -780,14 +760,12 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getResults(const QDMI_Job_Result result, // QDMI uses a different naming convention for its C interface functions // NOLINTBEGIN(readability-identifier-naming) int MQT_DDSIM_QDMI_device_initialize() { - qdmi::dd::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::dd::Device::get(); return QDMI_SUCCESS; } -int MQT_DDSIM_QDMI_device_finalize() { - qdmi::dd::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_DDSIM_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_DDSIM_QDMI_device_session_alloc( MQT_DDSIM_QDMI_Device_Session* session) { diff --git a/src/qdmi/na/Device.cpp b/src/qdmi/na/Device.cpp index 41d352eeef..89256f258b 100644 --- a/src/qdmi/na/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -113,32 +113,12 @@ Device::Device() { INITIALIZE_SITES(sites_); INITIALIZE_OPERATIONS(operations_); } -void Device::initialize() { - const std::scoped_lock lock(*instanceMutex_); - // Always increment the reference count when initialize is called. - ++refCount_; - if (instance_ == nullptr) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - instance_ = new Device(); - } -} -void Device::finalize() { - const std::scoped_lock lock(*instanceMutex_); - --refCount_; - if (refCount_ == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete instance_; - instance_ = nullptr; - } - // Do NOT delete the static mutex. The mutex is intentionally leaked to - // avoid static deinitialization issues (cf. static deinitialization order - // fiasco) -} auto Device::get() -> Device& { - // May only be called after `initialize()` has been called. - // Thus, `instance_` should not be null. - assert(instance_ != nullptr && "Device not initialized."); - return *instance_; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } auto Device::sessionAlloc(MQT_NA_QDMI_Device_Session* session) -> int { if (session == nullptr) { @@ -661,14 +641,12 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( } int MQT_NA_QDMI_device_initialize() { - qdmi::na::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::na::Device::get(); return QDMI_SUCCESS; } -int MQT_NA_QDMI_device_finalize() { - qdmi::na::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_NA_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_NA_QDMI_device_session_alloc(MQT_NA_QDMI_Device_Session* session) { return qdmi::na::Device::get().sessionAlloc(session); diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index d2ac9b053f..b38aaf93a2 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -117,32 +117,12 @@ Device::~Device() { // Explicitly clear sessions before destruction to avoid spurious segfaults sessions_.clear(); } -void Device::initialize() { - const std::scoped_lock lock(*instanceMutex_); - // Always increment the reference count when initialize is called. - ++refCount_; - if (instance_ == nullptr) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - instance_ = new Device(); - } -} -void Device::finalize() { - const std::scoped_lock lock(*instanceMutex_); - --refCount_; - if (refCount_ == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete instance_; - instance_ = nullptr; - } - // Do NOT delete the static mutex. The mutex is intentionally leaked to - // avoid static deinitialization issues (cf. static deinitialization order - // fiasco) -} auto Device::get() -> Device& { - // May only be called after `initialize()` has been called. - // Thus, `instance_` should not be null. - assert(instance_ != nullptr && "Device not initialized."); - return *instance_; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } auto Device::sessionAlloc(MQT_SC_QDMI_Device_Session* session) -> int { if (session == nullptr) { @@ -494,14 +474,12 @@ auto MQT_SC_QDMI_Operation_impl_d::queryProperty( } int MQT_SC_QDMI_device_initialize() { - qdmi::sc::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::sc::Device::get(); return QDMI_SUCCESS; } -int MQT_SC_QDMI_device_finalize() { - qdmi::sc::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_SC_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_SC_QDMI_device_session_alloc(MQT_SC_QDMI_Device_Session* session) { return qdmi::sc::Device::get().sessionAlloc(session); From 090ee4876900b9a5fbcd6dacb243d2c90a890d1e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 11:23:21 +0100 Subject: [PATCH 65/72] =?UTF-8?q?=F0=9F=9A=A8=20clang-tidy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/qdmi/na/Device.cpp | 1 - src/qdmi/sc/Device.cpp | 1 - 2 files changed, 2 deletions(-) diff --git a/src/qdmi/na/Device.cpp b/src/qdmi/na/Device.cpp index 89256f258b..02a8d82410 100644 --- a/src/qdmi/na/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index b38aaf93a2..0fc4b595bd 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include From 724d2b866ea9e0c1ecab332d414803c309790463 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 14:46:57 +0100 Subject: [PATCH 66/72] =?UTF-8?q?=F0=9F=9A=A8=20fix=20compiler=20and=20lin?= =?UTF-8?q?ter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- include/mqt-core/fomac/FoMaC.hpp | 6 +++--- include/mqt-core/qdmi/na/Device.hpp | 1 - include/mqt-core/qdmi/sc/Device.hpp | 1 - src/na/fomac/Device.cpp | 18 ++++++++++-------- test/qdmi/dd/results_sampling_test.cpp | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index e3d6ec22cf..8b01266162 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -391,7 +391,7 @@ class Session { : device_(device), site_(site) {} /// @returns the underlying QDMI_Site object. [[nodiscard]] auto getQDMISite() const -> QDMI_Site { return site_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Site() const { return site_; } auto operator<=>(const Site&) const = default; /// @see QDMI_SITE_PROPERTY_INDEX @@ -510,7 +510,7 @@ class Session { [[nodiscard]] auto getQDMIOperation() const -> QDMI_Operation { return operation_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Operation() const { return operation_; } auto operator<=>(const Operation&) const = default; /// @see QDMI_OPERATION_PROPERTY_NAME @@ -635,7 +635,7 @@ class Session { Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {} /// @returns the underlying QDMI_Device object. [[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Device() const { return device_; } auto operator<=>(const Device&) const = default; /// @see QDMI_DEVICE_PROPERTY_NAME diff --git a/include/mqt-core/qdmi/na/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp index b847d7fb59..71990cd35f 100644 --- a/include/mqt-core/qdmi/na/Device.hpp +++ b/include/mqt-core/qdmi/na/Device.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index 4c6eeec4d9..937168538a 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index c7d1f6c068..a0d025afc0 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -300,7 +300,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { for (const fomac::Session::Device::Operation& op : getOperations()) { const auto zoned = op.isZoned(); const auto& nq = op.getQubitsNum(); - const auto& name = op.getName(); + const auto& opName = op.getName(); const auto& sitesOpt = op.getSites(); if (!sitesOpt.has_value() || sitesOpt->empty()) { SPDLOG_INFO("Operation missing sites"); @@ -344,7 +344,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { if (!nq.has_value()) { // shuttling operations std::smatch match; - if (std::regex_match(name, match, std::regex("load<(\\d+)>"))) { + if (std::regex_match(opName, match, std::regex("load<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& d = op.getDuration(); if (!d.has_value()) { @@ -381,7 +381,8 @@ auto Session::Device::initOperationsFromDevice() -> bool { } unit.loadDuration = *d; unit.loadFidelity = *f; - } else if (std::regex_match(name, match, std::regex("move<(\\d+)>"))) { + } else if (std::regex_match(opName, match, + std::regex("move<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& speed = op.getMeanShuttlingSpeed(); if (!speed.has_value()) { @@ -412,7 +413,8 @@ auto Session::Device::initOperationsFromDevice() -> bool { } } unit.meanShuttlingSpeed = *speed; - } else if (std::regex_match(name, match, std::regex("store<(\\d+)>"))) { + } else if (std::regex_match(opName, match, + std::regex("store<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& d = op.getDuration(); if (!d.has_value()) { @@ -467,7 +469,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { if (*nq == 1) { // zoned single-qubit operations globalSingleQubitOperations.emplace_back(GlobalSingleQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -490,7 +492,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { return false; } globalMultiQubitOperations.emplace_back(GlobalMultiQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -521,7 +523,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { const auto region = calculateExtentFromSites(*sitesOpt); if (*nq == 1) { localSingleQubitOperations.emplace_back(LocalSingleQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -546,7 +548,7 @@ auto Session::Device::initOperationsFromDevice() -> bool { return false; } localMultiQubitOperations.emplace_back( - LocalMultiQubitOperation{{.name = name, + LocalMultiQubitOperation{{.name = opName, .region = pairRegion, .duration = *d, .fidelity = *f, diff --git a/test/qdmi/dd/results_sampling_test.cpp b/test/qdmi/dd/results_sampling_test.cpp index e6707be467..b60abff206 100644 --- a/test/qdmi/dd/results_sampling_test.cpp +++ b/test/qdmi/dd/results_sampling_test.cpp @@ -32,7 +32,7 @@ TEST(ResultsSampling, HistogramKeysAndValuesSumToShots) { auto [keys, vals] = qdmi_test::getHistogram(j.job); ASSERT_EQ(keys.size(), vals.size()); - auto sum = 0U; + size_t sum = 0U; for (const auto& v : vals) { sum += v; } From a3023d5323d1576f95e64424c82eca36ca145b64 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Sun, 7 Dec 2025 14:54:04 +0100 Subject: [PATCH 67/72] =?UTF-8?q?=E2=9C=A8=20add=20common=20definitions=20?= =?UTF-8?q?and=20utilities=20for=20QDMI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- CHANGELOG.md | 1 + include/mqt-core/fomac/FoMaC.hpp | 123 ++++----- include/mqt-core/qdmi/Common.hpp | 371 ++++++++++++++++++++++++++ src/fomac/CMakeLists.txt | 2 +- src/fomac/FoMaC.cpp | 444 ++++++++----------------------- src/qdmi/CMakeLists.txt | 33 ++- src/qdmi/Common.cpp | 57 ++++ src/qdmi/Driver.cpp | 77 +----- src/qdmi/dd/CMakeLists.txt | 3 +- src/qdmi/dd/Device.cpp | 70 +---- src/qdmi/na/CMakeLists.txt | 15 +- src/qdmi/na/Device.cpp | 70 +---- src/qdmi/sc/CMakeLists.txt | 15 +- src/qdmi/sc/Device.cpp | 71 +---- test/fomac/test_fomac.cpp | 214 ++++++++------- 15 files changed, 749 insertions(+), 817 deletions(-) create mode 100644 include/mqt-core/qdmi/Common.hpp create mode 100644 src/qdmi/Common.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a409070031..e9b38b3b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ✨ Add common definitions and utilities for QDMI ([**@burgholzer**]) - 🚚 Move `NA` QDMI device in its right place next to other QDMI devices ([#1355]) ([**@burgholzer**]) - ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**]) - ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 8b01266162..2a99cf0f11 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -10,6 +10,8 @@ #pragma once +#include "qdmi/Common.hpp" + #include #include #include @@ -141,35 +143,6 @@ template concept maybe_optional_value_or_string_or_vector = value_or_string_or_vector>; -/// @returns the string representation of the given QDMI_STATUS. -auto toString(QDMI_STATUS result) -> std::string; - -/// @returns the string representation of the given QDMI_Site_Property. -auto toString(QDMI_Site_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Operation_Property. -auto toString(QDMI_Operation_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Device_Property. -auto toString(QDMI_Device_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Session_Property. -constexpr auto toString(QDMI_Session_Property prop) -> std::string { - if (prop == QDMI_SESSION_PROPERTY_DEVICES) { - return "QDMI_SESSION_PROPERTY_DEVICES"; - } - return "QDMI_SESSION_PROPERTY_UNKNOWN"; -} - -/// @returns the string representation of the given QDMI_SESSION_PARAMETER_T. -auto toString(QDMI_SESSION_PARAMETER_T param) -> std::string; - -/// Throws an exception corresponding to the given QDMI_STATUS code. -[[noreturn]] auto throwError(int result, const std::string& msg) -> void; - -/// Throws an exception if the result indicates an error. -auto throwIfError(int result, const std::string& msg) -> void; - /** * @brief Configuration structure for session authentication parameters. * @details All parameters are optional. Only set the parameters needed for @@ -350,21 +323,24 @@ class Session { QDMI_Site site_; template - [[nodiscard]] auto queryProperty(QDMI_Site_Property prop) const -> T { + [[nodiscard]] auto queryProperty(const QDMI_Site_Property prop) const + -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = QDMI_device_query_site_property( - device_, site_, prop, 0, nullptr, &size); + auto result = QDMI_device_query_site_property(device_, site_, prop, 0, + nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_site_property( - device_, site_, prop, size, value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_site_property(device_, site_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -376,7 +352,7 @@ class Session { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -434,10 +410,12 @@ class Session { QDMI_Operation operation_; template - [[nodiscard]] auto queryProperty(QDMI_Operation_Property prop, + [[nodiscard]] auto queryProperty(const QDMI_Operation_Property prop, const std::vector& sites, const std::vector& params) const -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); std::vector qdmiSites; qdmiSites.reserve(sites.size()); std::ranges::transform( @@ -445,7 +423,7 @@ class Session { [](const Site& site) -> QDMI_Site { return site; }); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = QDMI_device_query_operation_property( + auto result = QDMI_device_query_operation_property( device_, operation_, sites.size(), qdmiSites.data(), params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { @@ -453,18 +431,17 @@ class Session { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_operation_property( + device_, operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; - const auto result = QDMI_device_query_operation_property( + auto result = QDMI_device_query_operation_property( device_, operation_, sites.size(), qdmiSites.data(), params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { @@ -472,14 +449,13 @@ class Session { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_operation_property( + device_, operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -492,7 +468,7 @@ class Session { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -580,38 +556,41 @@ class Session { QDMI_Device device_; template - [[nodiscard]] auto queryProperty(QDMI_Device_Property prop) const -> T { + [[nodiscard]] auto queryProperty(const QDMI_Device_Property prop) const + -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = + auto result = QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; - const auto result = + auto result = QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -622,7 +601,7 @@ class Session { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -702,15 +681,17 @@ class Session { template [[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); size_t size = 0; - throwIfError( - QDMI_session_query_session_property(session_, prop, 0, nullptr, &size), - "Querying " + toString(prop)); + auto result = + QDMI_session_query_session_property(session_, prop, 0, nullptr, &size); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_session_query_session_property(session_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_session_query_session_property(session_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } diff --git a/include/mqt-core/qdmi/Common.hpp b/include/mqt-core/qdmi/Common.hpp new file mode 100644 index 0000000000..31260547ad --- /dev/null +++ b/include/mqt-core/qdmi/Common.hpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +/// @file Common.hpp +/// @brief Common definitions and utilities for working with QDMI in C++. +/// @note This header will be upstreamed to the QDMI core library in the future. + +#pragma once + +#include +#include +#include + +namespace qdmi { +/** + * @brief Function used to mark unreachable code + * @details Uses compiler specific extensions if possible. Even if no extension + * is used, undefined behavior is still raised by an empty function body and the + * noreturn attribute. + */ +[[noreturn]] inline void unreachable() { +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif +} + +// NOLINTBEGIN(bugprone-macro-parentheses) +#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ + size, value, size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < sizeof(prop_type)) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + *static_cast(value) = prop_value; \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = sizeof(prop_type); \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#ifdef _WIN32 +#define STRNCPY(dest, src, size) \ + strncpy_s(static_cast(dest), size, src, size); +#else +#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); +#endif + +#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ + size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < strlen(prop_value) + 1) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + STRNCPY(value, prop_value, size); \ + /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ + static_cast(value)[size - 1] = '\0'; \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = strlen(prop_value) + 1; \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ + value, size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < (prop_values).size() * sizeof(prop_type)) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + memcpy(static_cast(value), \ + static_cast((prop_values).data()), \ + (prop_values).size() * sizeof(prop_type)); \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = (prop_values).size() * sizeof(prop_type); \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#define IS_INVALID_ARGUMENT(prop, prefix) \ + ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ + (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) +// NOLINTEND(bugprone-macro-parentheses) + +/// Returns the string representation of the given status code @p result. +constexpr auto toString(const QDMI_STATUS result) -> const char* { + switch (result) { + case QDMI_WARN_GENERAL: + return "General warning"; + case QDMI_SUCCESS: + return "Success"; + case QDMI_ERROR_FATAL: + return "A fatal error"; + case QDMI_ERROR_OUTOFMEM: + return "Out of memory"; + case QDMI_ERROR_NOTIMPLEMENTED: + return "Not implemented"; + case QDMI_ERROR_LIBNOTFOUND: + return "Library not found"; + case QDMI_ERROR_NOTFOUND: + return "Element not found"; + case QDMI_ERROR_OUTOFRANGE: + return "Out of range"; + case QDMI_ERROR_INVALIDARGUMENT: + return "Invalid argument"; + case QDMI_ERROR_PERMISSIONDENIED: + return "Permission denied"; + case QDMI_ERROR_NOTSUPPORTED: + return "Not supported"; + case QDMI_ERROR_BADSTATE: + return "Bad state"; + case QDMI_ERROR_TIMEOUT: + return "Timeout"; + } + unreachable(); +} + +/** + * @brief Throws an exception if the result indicates an error. + * @param result The result of a QDMI operation + * @param msg The error message to include in the exception + * @throws std::bad_alloc if the result is QDMI_ERROR_OUTOFMEM + * @throws std::out_of_range if the result is QDMI_ERROR_OUTOFRANGE + * @throws std::invalid_argument if the result is QDMI_ERROR_INVALIDARGUMENT + * @throws std::runtime_error for all other error results + */ +auto throwIfError(int result, const std::string& msg) -> void; + +/// Returns the string representation of the given session parameter @p param. +constexpr auto toString(const QDMI_Session_Parameter param) -> const char* { + switch (param) { + case QDMI_SESSION_PARAMETER_TOKEN: + return "TOKEN"; + case QDMI_SESSION_PARAMETER_AUTHFILE: + return "AUTH FILE"; + case QDMI_SESSION_PARAMETER_AUTHURL: + return "AUTH URL"; + case QDMI_SESSION_PARAMETER_USERNAME: + return "USERNAME"; + case QDMI_SESSION_PARAMETER_PASSWORD: + return "PASSWORD"; + case QDMI_SESSION_PARAMETER_PROJECTID: + return "PROJECT ID"; + case QDMI_SESSION_PARAMETER_MAX: + return "MAX"; + case QDMI_SESSION_PARAMETER_CUSTOM1: + return "CUSTOM1"; + case QDMI_SESSION_PARAMETER_CUSTOM2: + return "CUSTOM2"; + case QDMI_SESSION_PARAMETER_CUSTOM3: + return "CUSTOM3"; + case QDMI_SESSION_PARAMETER_CUSTOM4: + return "CUSTOM4"; + case QDMI_SESSION_PARAMETER_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given session property @p prop. +constexpr auto toString(const QDMI_Session_Property prop) -> const char* { + switch (prop) { + case QDMI_SESSION_PROPERTY_DEVICES: + return "DEVICES"; + case QDMI_SESSION_PROPERTY_MAX: + return "MAX"; + case QDMI_SESSION_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_SESSION_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_SESSION_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_SESSION_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_SESSION_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given device session parameter +/// @p param. +constexpr auto toString(const QDMI_Device_Session_Parameter param) -> const + char* { + switch (param) { + case QDMI_DEVICE_SESSION_PARAMETER_BASEURL: + return "BASE URL"; + case QDMI_DEVICE_SESSION_PARAMETER_TOKEN: + return "TOKEN"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE: + return "AUTH FILE"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHURL: + return "AUTH URL"; + case QDMI_DEVICE_SESSION_PARAMETER_USERNAME: + return "USERNAME"; + case QDMI_DEVICE_SESSION_PARAMETER_PASSWORD: + return "PASSWORD"; + case QDMI_DEVICE_SESSION_PARAMETER_MAX: + return "MAX"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1: + return "CUSTOM1"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2: + return "CUSTOM2"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3: + return "CUSTOM3"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4: + return "CUSTOM4"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given site property @p prop. +constexpr auto toString(const QDMI_Site_Property prop) -> const char* { + switch (prop) { + case QDMI_SITE_PROPERTY_INDEX: + return "INDEX"; + case QDMI_SITE_PROPERTY_T1: + return "T1"; + case QDMI_SITE_PROPERTY_T2: + return "T2"; + case QDMI_SITE_PROPERTY_NAME: + return "NAME"; + case QDMI_SITE_PROPERTY_XCOORDINATE: + return "X COORDINATE"; + case QDMI_SITE_PROPERTY_YCOORDINATE: + return "Y COORDINATE"; + case QDMI_SITE_PROPERTY_ZCOORDINATE: + return "Z COORDINATE"; + case QDMI_SITE_PROPERTY_ISZONE: + return "IS ZONE"; + case QDMI_SITE_PROPERTY_XEXTENT: + return "X EXTENT"; + case QDMI_SITE_PROPERTY_YEXTENT: + return "Y EXTENT"; + case QDMI_SITE_PROPERTY_ZEXTENT: + return "Z EXTENT"; + case QDMI_SITE_PROPERTY_MODULEINDEX: + return "MODULE INDEX"; + case QDMI_SITE_PROPERTY_SUBMODULEINDEX: + return "SUBMODULE INDEX"; + case QDMI_SITE_PROPERTY_MAX: + return "MAX"; + case QDMI_SITE_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_SITE_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_SITE_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_SITE_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_SITE_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given operation property @p prop. +constexpr auto toString(const QDMI_Operation_Property prop) -> const char* { + switch (prop) { + case QDMI_OPERATION_PROPERTY_NAME: + return "NAME"; + case QDMI_OPERATION_PROPERTY_QUBITSNUM: + return "QUBITS NUM"; + case QDMI_OPERATION_PROPERTY_PARAMETERSNUM: + return "PARAMETERS NUM"; + case QDMI_OPERATION_PROPERTY_DURATION: + return "DURATION"; + case QDMI_OPERATION_PROPERTY_FIDELITY: + return "FIDELITY"; + case QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS: + return "INTERACTION RADIUS"; + case QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS: + return "BLOCKING RADIUS"; + case QDMI_OPERATION_PROPERTY_IDLINGFIDELITY: + return "IDLING FIDELITY"; + case QDMI_OPERATION_PROPERTY_ISZONED: + return "IS ZONED"; + case QDMI_OPERATION_PROPERTY_SITES: + return "SITES"; + case QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED: + return "MEAN SHUTTLING SPEED"; + case QDMI_OPERATION_PROPERTY_MAX: + return "MAX"; + case QDMI_OPERATION_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_OPERATION_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_OPERATION_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_OPERATION_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_OPERATION_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given device property @p prop. +constexpr auto toString(const QDMI_Device_Property prop) -> const char* { + switch (prop) { + case QDMI_DEVICE_PROPERTY_NAME: + return "NAME"; + case QDMI_DEVICE_PROPERTY_VERSION: + return "VERSION"; + case QDMI_DEVICE_PROPERTY_STATUS: + return "STATUS"; + case QDMI_DEVICE_PROPERTY_LIBRARYVERSION: + return "LIBRARY VERSION"; + case QDMI_DEVICE_PROPERTY_QUBITSNUM: + return "QUBITS NUM"; + case QDMI_DEVICE_PROPERTY_SITES: + return "SITES"; + case QDMI_DEVICE_PROPERTY_OPERATIONS: + 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: + return "LENGTH SCALE FACTOR"; + case QDMI_DEVICE_PROPERTY_DURATIONUNIT: + return "DURATION UNIT"; + case QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR: + 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_MAX: + return "MAX"; + case QDMI_DEVICE_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_DEVICE_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_DEVICE_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_DEVICE_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_DEVICE_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +} // namespace qdmi diff --git a/src/fomac/CMakeLists.txt b/src/fomac/CMakeLists.txt index ebe79627f7..554e1e7959 100644 --- a/src/fomac/CMakeLists.txt +++ b/src/fomac/CMakeLists.txt @@ -22,7 +22,7 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries( ${TARGET_NAME} - PUBLIC qdmi::qdmi MQT::CoreQDMIDriver + PUBLIC qdmi::qdmi MQT::CoreQDMICommon MQT::CoreQDMIDriver PRIVATE spdlog::spdlog) # add to list of MQT core targets diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 2b430f41ce..246a956c41 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -10,6 +10,8 @@ #include "fomac/FoMaC.hpp" +#include "qdmi/Common.hpp" + #include #include #include @@ -28,236 +30,6 @@ #include namespace fomac { - -namespace { -/** - * @brief Function used to mark unreachable code - * @details Uses compiler specific extensions if possible. Even if no extension - * is used, undefined behavior is still raised by an empty function body and the - * noreturn attribute. - */ -[[noreturn]] inline void unreachable() { -#ifdef __GNUC__ // GCC, Clang, ICC - __builtin_unreachable(); -#elif defined(_MSC_VER) // MSVC - __assume(false); -#endif -} -} // namespace - -auto toString(const QDMI_STATUS result) -> std::string { - switch (result) { - case QDMI_WARN_GENERAL: - return "General warning"; - case QDMI_SUCCESS: - return "Success"; - case QDMI_ERROR_FATAL: - return "A fatal error"; - case QDMI_ERROR_OUTOFMEM: - return "Out of memory"; - case QDMI_ERROR_NOTIMPLEMENTED: - return "Not implemented"; - case QDMI_ERROR_LIBNOTFOUND: - return "Library not found"; - case QDMI_ERROR_NOTFOUND: - return "Element not found"; - case QDMI_ERROR_OUTOFRANGE: - return "Out of range"; - case QDMI_ERROR_INVALIDARGUMENT: - return "Invalid argument"; - case QDMI_ERROR_PERMISSIONDENIED: - return "Permission denied"; - case QDMI_ERROR_NOTSUPPORTED: - return "Not supported"; - case QDMI_ERROR_BADSTATE: - return "Bad state"; - case QDMI_ERROR_TIMEOUT: - return "Timeout"; - } - unreachable(); -} - -auto toString(const QDMI_SESSION_PARAMETER_T param) -> std::string { - switch (param) { - case QDMI_SESSION_PARAMETER_TOKEN: - return "TOKEN"; - case QDMI_SESSION_PARAMETER_AUTHFILE: - return "AUTHFILE"; - case QDMI_SESSION_PARAMETER_AUTHURL: - return "AUTHURL"; - case QDMI_SESSION_PARAMETER_USERNAME: - return "USERNAME"; - case QDMI_SESSION_PARAMETER_PASSWORD: - return "PASSWORD"; - case QDMI_SESSION_PARAMETER_PROJECTID: - return "PROJECTID"; - case QDMI_SESSION_PARAMETER_MAX: - return "MAX"; - case QDMI_SESSION_PARAMETER_CUSTOM1: - return "CUSTOM1"; - case QDMI_SESSION_PARAMETER_CUSTOM2: - return "CUSTOM2"; - case QDMI_SESSION_PARAMETER_CUSTOM3: - return "CUSTOM3"; - case QDMI_SESSION_PARAMETER_CUSTOM4: - return "CUSTOM4"; - case QDMI_SESSION_PARAMETER_CUSTOM5: - return "CUSTOM5"; - } - unreachable(); -} - -auto toString(const QDMI_Site_Property prop) -> std::string { - switch (prop) { - case QDMI_SITE_PROPERTY_INDEX: - return "QDMI_SITE_PROPERTY_INDEX"; - case QDMI_SITE_PROPERTY_T1: - return "QDMI_SITE_PROPERTY_T1"; - case QDMI_SITE_PROPERTY_T2: - return "QDMI_SITE_PROPERTY_T2"; - case QDMI_SITE_PROPERTY_NAME: - return "QDMI_SITE_PROPERTY_NAME"; - case QDMI_SITE_PROPERTY_XCOORDINATE: - return "QDMI_SITE_PROPERTY_XCOORDINATE"; - case QDMI_SITE_PROPERTY_YCOORDINATE: - return "QDMI_SITE_PROPERTY_YCOORDINATE"; - case QDMI_SITE_PROPERTY_ZCOORDINATE: - return "QDMI_SITE_PROPERTY_ZCOORDINATE"; - case QDMI_SITE_PROPERTY_ISZONE: - return "QDMI_SITE_PROPERTY_ISZONE"; - case QDMI_SITE_PROPERTY_XEXTENT: - return "QDMI_SITE_PROPERTY_XEXTENT"; - case QDMI_SITE_PROPERTY_YEXTENT: - return "QDMI_SITE_PROPERTY_YEXTENT"; - case QDMI_SITE_PROPERTY_ZEXTENT: - return "QDMI_SITE_PROPERTY_ZEXTENT"; - case QDMI_SITE_PROPERTY_MODULEINDEX: - return "QDMI_SITE_PROPERTY_MODULEINDEX"; - case QDMI_SITE_PROPERTY_SUBMODULEINDEX: - return "QDMI_SITE_PROPERTY_SUBMODULEINDEX"; - case QDMI_SITE_PROPERTY_MAX: - case QDMI_SITE_PROPERTY_CUSTOM1: - case QDMI_SITE_PROPERTY_CUSTOM2: - case QDMI_SITE_PROPERTY_CUSTOM3: - case QDMI_SITE_PROPERTY_CUSTOM4: - case QDMI_SITE_PROPERTY_CUSTOM5: - return "QDMI_SITE_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto toString(const QDMI_Operation_Property prop) -> std::string { - switch (prop) { - case QDMI_OPERATION_PROPERTY_NAME: - return "QDMI_OPERATION_PROPERTY_NAME"; - case QDMI_OPERATION_PROPERTY_QUBITSNUM: - return "QDMI_OPERATION_PROPERTY_QUBITSNUM"; - case QDMI_OPERATION_PROPERTY_PARAMETERSNUM: - return "QDMI_OPERATION_PROPERTY_PARAMETERSNUM"; - case QDMI_OPERATION_PROPERTY_DURATION: - return "QDMI_OPERATION_PROPERTY_DURATION"; - case QDMI_OPERATION_PROPERTY_FIDELITY: - return "QDMI_OPERATION_PROPERTY_FIDELITY"; - case QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS: - return "QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS"; - case QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS: - return "QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS"; - case QDMI_OPERATION_PROPERTY_IDLINGFIDELITY: - return "QDMI_OPERATION_PROPERTY_IDLINGFIDELITY"; - case QDMI_OPERATION_PROPERTY_ISZONED: - return "QDMI_OPERATION_PROPERTY_ISZONED"; - case QDMI_OPERATION_PROPERTY_SITES: - return "QDMI_OPERATION_PROPERTY_SITES"; - case QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED: - return "QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED"; - case QDMI_OPERATION_PROPERTY_MAX: - case QDMI_OPERATION_PROPERTY_CUSTOM1: - case QDMI_OPERATION_PROPERTY_CUSTOM2: - case QDMI_OPERATION_PROPERTY_CUSTOM3: - case QDMI_OPERATION_PROPERTY_CUSTOM4: - case QDMI_OPERATION_PROPERTY_CUSTOM5: - return "QDMI_OPERATION_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto toString(const QDMI_Device_Property prop) -> std::string { - switch (prop) { - case QDMI_DEVICE_PROPERTY_NAME: - return "QDMI_DEVICE_PROPERTY_NAME"; - case QDMI_DEVICE_PROPERTY_VERSION: - return "QDMI_DEVICE_PROPERTY_VERSION"; - case QDMI_DEVICE_PROPERTY_STATUS: - return "QDMI_DEVICE_PROPERTY_STATUS"; - case QDMI_DEVICE_PROPERTY_LIBRARYVERSION: - return "QDMI_DEVICE_PROPERTY_LIBRARYVERSION"; - case QDMI_DEVICE_PROPERTY_QUBITSNUM: - return "QDMI_DEVICE_PROPERTY_QUBITSNUM"; - case QDMI_DEVICE_PROPERTY_SITES: - return "QDMI_DEVICE_PROPERTY_SITES"; - case QDMI_DEVICE_PROPERTY_OPERATIONS: - return "QDMI_DEVICE_PROPERTY_OPERATIONS"; - case QDMI_DEVICE_PROPERTY_COUPLINGMAP: - return "QDMI_DEVICE_PROPERTY_COUPLINGMAP"; - case QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION: - return "QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION"; - case QDMI_DEVICE_PROPERTY_LENGTHUNIT: - return "QDMI_DEVICE_PROPERTY_LENGTHUNIT"; - case QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR: - return "QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR"; - case QDMI_DEVICE_PROPERTY_DURATIONUNIT: - return "QDMI_DEVICE_PROPERTY_DURATIONUNIT"; - case QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR: - return "QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR"; - case QDMI_DEVICE_PROPERTY_MINATOMDISTANCE: - return "QDMI_DEVICE_PROPERTY_MINATOMDISTANCE"; - case QDMI_DEVICE_PROPERTY_PULSESUPPORT: - return "QDMI_DEVICE_PROPERTY_PULSESUPPORT"; - case QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS: - return "QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS"; - case QDMI_DEVICE_PROPERTY_MAX: - case QDMI_DEVICE_PROPERTY_CUSTOM1: - case QDMI_DEVICE_PROPERTY_CUSTOM2: - case QDMI_DEVICE_PROPERTY_CUSTOM3: - case QDMI_DEVICE_PROPERTY_CUSTOM4: - case QDMI_DEVICE_PROPERTY_CUSTOM5: - return "QDMI_DEVICE_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto throwError(const int result, const std::string& msg) -> void { - std::ostringstream ss; - ss << msg << ": " << toString(static_cast(result)) << "."; - switch (result) { - case QDMI_ERROR_OUTOFMEM: - throw std::bad_alloc(); - case QDMI_ERROR_OUTOFRANGE: - throw std::out_of_range(ss.str()); - case QDMI_ERROR_INVALIDARGUMENT: - throw std::invalid_argument(ss.str()); - case QDMI_ERROR_FATAL: - case QDMI_ERROR_NOTIMPLEMENTED: - case QDMI_ERROR_LIBNOTFOUND: - case QDMI_ERROR_NOTFOUND: - case QDMI_ERROR_PERMISSIONDENIED: - case QDMI_ERROR_NOTSUPPORTED: - case QDMI_ERROR_BADSTATE: - case QDMI_ERROR_TIMEOUT: - throw std::runtime_error(ss.str()); - default: - throw std::runtime_error("Unknown error code: " + - toString(static_cast(result)) + "."); - } -} -auto throwIfError(const int result, const std::string& msg) -> void { - switch (result) { - case QDMI_SUCCESS: - break; - case QDMI_WARN_GENERAL: - SPDLOG_WARN("{}", msg); - break; - default: - throwError(result, msg); - } -} auto Session::Device::Site::getIndex() const -> size_t { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } @@ -506,33 +278,35 @@ auto Session::Device::submitJob(const std::string& program, const QDMI_Program_Format format, const size_t numShots) const -> Job { QDMI_Job job = nullptr; - throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); + qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions // Set program format - throwIfError(QDMI_job_set_parameter(jobWrapper, - QDMI_JOB_PARAMETER_PROGRAMFORMAT, - sizeof(format), &format), - "Setting program format"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(format), &format), + "Setting program format"); // Set program - throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, - program.size() + 1, program.c_str()), - "Setting program"); + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, + program.size() + 1, program.c_str()), + "Setting program"); // Set number of shots - throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, - sizeof(numShots), &numShots), - "Setting number of shots"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_SHOTSNUM, + sizeof(numShots), &numShots), + "Setting number of shots"); // Submit the job - throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); + qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); return jobWrapper; } auto Session::Job::check() const -> QDMI_Job_Status { QDMI_Job_Status status{}; - throwIfError(QDMI_job_check(job_, &status), "Checking job status"); + qdmi::throwIfError(QDMI_job_check(job_, &status), "Checking job status"); return status; } @@ -544,58 +318,60 @@ auto Session::Job::wait(const size_t timeout) const -> bool { if (ret == QDMI_ERROR_TIMEOUT) { return false; } - throwIfError(ret, "Waiting for job"); - unreachable(); + qdmi::throwIfError(ret, "Waiting for job"); + qdmi::unreachable(); } auto Session::Job::cancel() const -> void { - throwIfError(QDMI_job_cancel(job_), "Cancelling job"); + qdmi::throwIfError(QDMI_job_cancel(job_), "Cancelling job"); } auto Session::Job::getId() const -> std::string { size_t size = 0; - throwIfError( + qdmi::throwIfError( QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, 0, nullptr, &size), "Querying job ID size"); std::string id(size - 1, '\0'); - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, size, - id.data(), nullptr), - "Querying job ID"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, size, + id.data(), nullptr), + "Querying job ID"); return id; } auto Session::Job::getProgramFormat() const -> QDMI_Program_Format { QDMI_Program_Format format{}; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAMFORMAT, - sizeof(format), &format, nullptr), - "Querying program format"); + qdmi::throwIfError(QDMI_job_query_property(job_, + QDMI_JOB_PROPERTY_PROGRAMFORMAT, + sizeof(format), &format, nullptr), + "Querying program format"); return format; } auto Session::Job::getProgram() const -> std::string { size_t size = 0; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, - nullptr, &size), - "Querying program size"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, + nullptr, &size), + "Querying program size"); std::string program(size - 1, '\0'); - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, size, - program.data(), nullptr), - "Querying program"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, + size, program.data(), nullptr), + "Querying program"); return program; } auto Session::Job::getNumShots() const -> size_t { size_t numShots = 0; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, - sizeof(numShots), &numShots, nullptr), - "Querying number of shots"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, + sizeof(numShots), &numShots, + nullptr), + "Querying number of shots"); return numShots; } auto Session::Job::getShots() const -> std::vector { size_t shotsSize = 0; - throwIfError( + qdmi::throwIfError( QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, 0, nullptr, &shotsSize), "Querying shots size"); @@ -604,9 +380,9 @@ auto Session::Job::getShots() const -> std::vector { } std::string shots(shotsSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, shotsSize, - shots.data(), nullptr), - "Querying shots"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, + shotsSize, shots.data(), nullptr), + "Querying shots"); // Parse the shots (comma-separated) std::vector shotsVec; @@ -627,24 +403,24 @@ auto Session::Job::getShots() const -> std::vector { auto Session::Job::getCounts() const -> std::map { // Get the histogram keys size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, nullptr, - &keysSize), - "Querying histogram keys size"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, + nullptr, &keysSize), + "Querying histogram keys size"); if (keysSize == 0) { return {}; // Empty histogram } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, keysSize, - keys.data(), nullptr), - "Querying histogram keys"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, + keysSize, keys.data(), nullptr), + "Querying histogram keys"); // Get the histogram values size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, 0, - nullptr, &valuesSize), - "Querying histogram values size"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, 0, + nullptr, &valuesSize), + "Querying histogram values size"); if (valuesSize % sizeof(size_t) != 0) { throw std::runtime_error( @@ -652,9 +428,9 @@ auto Session::Job::getCounts() const -> std::map { } std::vector values(valuesSize / sizeof(size_t)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, - valuesSize, values.data(), nullptr), - "Querying histogram values"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, + valuesSize, values.data(), nullptr), + "Querying histogram values"); // Parse the keys (comma-separated) std::map counts; @@ -678,9 +454,10 @@ auto Session::Job::getCounts() const -> std::map { auto Session::Job::getDenseStateVector() const -> std::vector> { size_t size = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, - nullptr, &size), - "Querying dense state vector size"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, + nullptr, &size), + "Querying dense state vector size"); if (size % sizeof(std::complex) != 0) { throw std::runtime_error( @@ -689,17 +466,19 @@ auto Session::Job::getDenseStateVector() const std::vector> stateVector(size / sizeof(std::complex)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_DENSE, - size, stateVector.data(), nullptr), - "Querying dense state vector"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_STATEVECTOR_DENSE, + size, stateVector.data(), nullptr), + "Querying dense state vector"); return stateVector; } auto Session::Job::getDenseProbabilities() const -> std::vector { size_t size = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_DENSE, - 0, nullptr, &size), - "Querying dense probabilities size"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_PROBABILITIES_DENSE, + 0, nullptr, &size), + "Querying dense probabilities size"); if (size % sizeof(double) != 0) { throw std::runtime_error( @@ -707,35 +486,36 @@ auto Session::Job::getDenseProbabilities() const -> std::vector { } std::vector probabilities(size / sizeof(double)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_DENSE, - size, probabilities.data(), nullptr), - "Querying dense probabilities"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_PROBABILITIES_DENSE, + size, probabilities.data(), nullptr), + "Querying dense probabilities"); return probabilities; } auto Session::Job::getSparseStateVector() const -> std::map> { size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, 0, - nullptr, &keysSize), - "Querying sparse state vector keys size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, 0, + nullptr, &keysSize), + "Querying sparse state vector keys size"); if (keysSize == 0) { return {}; // Empty state vector } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, - keysSize, keys.data(), nullptr), - "Querying sparse state vector keys"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, + keysSize, keys.data(), nullptr), + "Querying sparse state vector keys"); size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, - 0, nullptr, &valuesSize), - "Querying sparse state vector values size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, 0, + nullptr, &valuesSize), + "Querying sparse state vector values size"); if (valuesSize % sizeof(std::complex) != 0) { throw std::runtime_error( @@ -745,10 +525,10 @@ auto Session::Job::getSparseStateVector() const std::vector> values(valuesSize / sizeof(std::complex)); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, - valuesSize, values.data(), nullptr), - "Querying sparse state vector values"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, + valuesSize, values.data(), nullptr), + "Querying sparse state vector values"); // Parse the keys (comma-separated) std::map> stateVector; @@ -772,26 +552,26 @@ auto Session::Job::getSparseStateVector() const auto Session::Job::getSparseProbabilities() const -> std::map { size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, - 0, nullptr, &keysSize), - "Querying sparse probabilities keys size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, 0, + nullptr, &keysSize), + "Querying sparse probabilities keys size"); if (keysSize == 0) { return {}; // Empty probabilities } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, - keysSize, keys.data(), nullptr), - "Querying sparse probabilities keys"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, + keysSize, keys.data(), nullptr), + "Querying sparse probabilities keys"); size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, - 0, nullptr, &valuesSize), - "Querying sparse probabilities values size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, 0, + nullptr, &valuesSize), + "Querying sparse probabilities values size"); if (valuesSize % sizeof(double) != 0) { throw std::runtime_error( @@ -799,10 +579,10 @@ auto Session::Job::getSparseProbabilities() const } std::vector values(valuesSize / sizeof(double)); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, - valuesSize, values.data(), nullptr), - "Querying sparse probabilities values"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, + valuesSize, values.data(), nullptr), + "Querying sparse probabilities values"); // Parse the keys (comma-separated) std::map probabilities; @@ -824,7 +604,7 @@ auto Session::Job::getSparseProbabilities() const Session::Session(const SessionConfig& config) { const auto result = QDMI_session_alloc(&session_); - throwIfError(result, "Allocating QDMI session"); + qdmi::throwIfError(result, "Allocating QDMI session"); // Helper to ensure session is freed if an exception is thrown during setup const auto cleanup = [this]() -> void { @@ -842,18 +622,16 @@ Session::Session(const SessionConfig& config) { if (status == QDMI_ERROR_NOTSUPPORTED) { // Optional parameter not supported by session - skip it SPDLOG_INFO("Session parameter {} not supported (skipped)", - toString(param)); + qdmi::toString(param)); return; } - if (status != QDMI_SUCCESS && status != QDMI_WARN_GENERAL) { - std::ostringstream ss; - ss << "Setting session parameter " << toString(param) << ": " - << toString(status) << " (status = " << status << ")"; - throwError(status, ss.str()); - } - if (status == QDMI_WARN_GENERAL) { - SPDLOG_WARN("Setting session parameter {}", toString(param)); + if (status == QDMI_SUCCESS) { + return; } + std::ostringstream ss; + ss << "Setting session parameter " << qdmi::toString(param) << ": " + << qdmi::toString(status) << " (status = " << status << ")"; + qdmi::throwIfError(status, ss.str()); } }; @@ -889,7 +667,7 @@ Session::Session(const SessionConfig& config) { setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); // Initialize the session - throwIfError(QDMI_session_init(session_), "Initializing session"); + qdmi::throwIfError(QDMI_session_init(session_), "Initializing session"); } catch (...) { cleanup(); throw; diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index 455308d64d..3df41c680f 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -6,6 +6,29 @@ # # Licensed under the MIT License +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-common) + +if(NOT TARGET ${TARGET_NAME}) + # Add driver library + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMICommon) + + # Add sources to target + target_sources(${TARGET_NAME} PRIVATE Common.cpp) + + # Add headers using file sets + target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} + FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/Common.hpp) + + # Add link libraries + target_link_libraries( + ${TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE qdmi::project_warnings) + + # add to list of MQT core targets + list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) +endif() + add_subdirectory(dd) add_subdirectory(sc) add_subdirectory(na) @@ -26,12 +49,14 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries( ${TARGET_NAME} - PUBLIC qdmi::qdmi + PUBLIC qdmi::qdmi MQT::CoreQDMICommon PRIVATE MQT::CoreQDMINaDevice MQT::CoreQDMIScDevice MQT::CoreQDMI_DDSIM_Device qdmi::project_warnings spdlog::spdlog) # add to list of MQT core targets - set(MQT_CORE_TARGETS - ${MQT_CORE_TARGETS} ${TARGET_NAME} - PARENT_SCOPE) + list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() + +set(MQT_CORE_TARGETS + ${MQT_CORE_TARGETS} + PARENT_SCOPE) diff --git a/src/qdmi/Common.cpp b/src/qdmi/Common.cpp new file mode 100644 index 0000000000..0a8f6b9484 --- /dev/null +++ b/src/qdmi/Common.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +/// @file Common.cpp +/// @brief Common definitions and utilities for working with QDMI in C++. +/// @note This file will be upstreamed to the QDMI core library in the future. + +#include "qdmi/Common.hpp" + +#include +#include +#include +#include +#include + +namespace qdmi { + +auto throwIfError(const int result, const std::string& msg) -> void { + switch (const auto res = static_cast(result)) { + case QDMI_SUCCESS: + break; + case QDMI_WARN_GENERAL: + std::cerr << "Warning: " << msg << '\n'; + break; + default: + std::ostringstream ss; + ss << msg << ": " << toString(res) << "."; + switch (res) { + case QDMI_ERROR_OUTOFMEM: + throw std::bad_alloc(); + case QDMI_ERROR_OUTOFRANGE: + throw std::out_of_range(ss.str()); + case QDMI_ERROR_INVALIDARGUMENT: + throw std::invalid_argument(ss.str()); + case QDMI_ERROR_FATAL: + case QDMI_ERROR_NOTIMPLEMENTED: + case QDMI_ERROR_LIBNOTFOUND: + case QDMI_ERROR_NOTFOUND: + case QDMI_ERROR_PERMISSIONDENIED: + case QDMI_ERROR_NOTSUPPORTED: + case QDMI_ERROR_BADSTATE: + case QDMI_ERROR_TIMEOUT: + throw std::runtime_error(ss.str()); + default: + throw std::runtime_error("Unknown QDMI error code. " + ss.str()); + } + } +} + +} // namespace qdmi diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index afe0a29a4f..c0eadd10c0 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -13,6 +13,7 @@ #include "mqt_ddsim_qdmi/device.h" #include "mqt_na_qdmi/device.h" #include "mqt_sc_qdmi/device.h" +#include "qdmi/Common.hpp" #include #include @@ -35,82 +36,6 @@ #endif // _WIN32 namespace qdmi { -namespace { -/** - * @brief Function used to mark unreachable code - * @details Uses compiler specific extensions if possible. Even if no extension - * is used, undefined behavior is still raised by an empty function body and the - * noreturn attribute. - */ -[[noreturn]] void unreachable() { -#ifdef __GNUC__ // GCC, Clang, ICC - __builtin_unreachable(); -#elif defined(_MSC_VER) // MSVC - __assume(false); -#endif -} -auto toString(const QDMI_Device_Session_Parameter param) -> std::string { - switch (param) { - case QDMI_DEVICE_SESSION_PARAMETER_BASEURL: - return "BASEURL"; - case QDMI_DEVICE_SESSION_PARAMETER_TOKEN: - return "TOKEN"; - case QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE: - return "AUTHFILE"; - case QDMI_DEVICE_SESSION_PARAMETER_AUTHURL: - return "AUTHURL"; - case QDMI_DEVICE_SESSION_PARAMETER_USERNAME: - return "USERNAME"; - case QDMI_DEVICE_SESSION_PARAMETER_PASSWORD: - return "PASSWORD"; - case QDMI_DEVICE_SESSION_PARAMETER_MAX: - return "MAX"; - case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1: - return "CUSTOM1"; - case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2: - return "CUSTOM2"; - case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3: - return "CUSTOM3"; - case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4: - return "CUSTOM4"; - case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5: - return "CUSTOM5"; - } - unreachable(); -} -auto toString(const QDMI_STATUS result) -> std::string { - switch (result) { - case QDMI_WARN_GENERAL: - return "General warning"; - case QDMI_SUCCESS: - return "Success"; - case QDMI_ERROR_FATAL: - return "A fatal error"; - case QDMI_ERROR_OUTOFMEM: - return "Out of memory"; - case QDMI_ERROR_NOTIMPLEMENTED: - return "Not implemented"; - case QDMI_ERROR_LIBNOTFOUND: - return "Library not found"; - case QDMI_ERROR_NOTFOUND: - return "Element not found"; - case QDMI_ERROR_OUTOFRANGE: - return "Out of range"; - case QDMI_ERROR_INVALIDARGUMENT: - return "Invalid argument"; - case QDMI_ERROR_PERMISSIONDENIED: - return "Permission denied"; - case QDMI_ERROR_NOTSUPPORTED: - return "Not supported"; - case QDMI_ERROR_BADSTATE: - return "Bad state"; - case QDMI_ERROR_TIMEOUT: - return "Timeout"; - } - unreachable(); -} -} // namespace - // Macro to load a static symbol from a statically linked library. // @param prefix is the prefix used for the function names in the library. // @param symbol is the name of the symbol to load. diff --git a/src/qdmi/dd/CMakeLists.txt b/src/qdmi/dd/CMakeLists.txt index 6b5c7065ac..849574eb80 100644 --- a/src/qdmi/dd/CMakeLists.txt +++ b/src/qdmi/dd/CMakeLists.txt @@ -41,8 +41,7 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreCircuitOptimizer MQT::ProjectOptions - MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreCircuitOptimizer MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index 5611b7a334..09a165d75e 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -22,6 +22,7 @@ #include "ir/QuantumComputation.hpp" #include "mqt_ddsim_qdmi/device.h" #include "qasm3/Importer.hpp" +#include "qdmi/Common.hpp" #include #include @@ -144,75 +145,6 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, } // namespace -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, size); -#else -#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ - (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ - (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::dd { Device::Device() : name_("MQT Core DDSIM QDMI Device"), diff --git a/src/qdmi/na/CMakeLists.txt b/src/qdmi/na/CMakeLists.txt index 9a9bd3ee47..e59de2206c 100644 --- a/src/qdmi/na/CMakeLists.txt +++ b/src/qdmi/na/CMakeLists.txt @@ -132,22 +132,12 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # set c++ standard - target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) - - # set versioning information - set_target_properties( - ${TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMINaDevice) - # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} @@ -191,7 +181,8 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${DYN_TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) # set c++ standard target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) # set versioning information diff --git a/src/qdmi/na/Device.cpp b/src/qdmi/na/Device.cpp index 02a8d82410..758cc31901 100644 --- a/src/qdmi/na/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -15,6 +15,7 @@ #include "qdmi/na/Device.hpp" #include "mqt_na_qdmi/device.h" +#include "qdmi/Common.hpp" #include "qdmi/na/DeviceMemberInitializers.hpp" #include @@ -30,75 +31,6 @@ #include #include -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, size); -#else -#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ - (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ - (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::na { Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) diff --git a/src/qdmi/sc/CMakeLists.txt b/src/qdmi/sc/CMakeLists.txt index f83c70bbac..e477eedd23 100644 --- a/src/qdmi/sc/CMakeLists.txt +++ b/src/qdmi/sc/CMakeLists.txt @@ -132,22 +132,12 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # set c++ standard - target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) - - # set versioning information - set_target_properties( - ${TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMIScDevice) - # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} @@ -191,7 +181,8 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${DYN_TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) # set c++ standard target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) # set versioning information diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 0fc4b595bd..436a0b5e17 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -15,6 +15,7 @@ #include "qdmi/sc/Device.hpp" #include "mqt_sc_qdmi/device.h" +#include "qdmi/Common.hpp" #include "qdmi/sc/DeviceMemberInitializers.hpp" #include @@ -30,76 +31,6 @@ #include #include -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, (size) - 1); -#else -#define STRNCPY(dest, src, size) \ - strncpy(static_cast(dest), src, (size) - 1); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define IS_INVALID_ARGUMENT(prop, prefix) \ - ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ - (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ - (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::sc { Device::Device() { diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 064760b608..76dd10ce2c 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -9,6 +9,7 @@ */ #include "fomac/FoMaC.hpp" +#include "qdmi/Common.hpp" #include #include @@ -102,112 +103,129 @@ cx q[0], q[1]; }; TEST(FoMaCTest, StatusToString) { - EXPECT_EQ(toString(QDMI_WARN_GENERAL), "General warning"); - EXPECT_EQ(toString(QDMI_SUCCESS), "Success"); - EXPECT_EQ(toString(QDMI_ERROR_FATAL), "A fatal error"); - EXPECT_EQ(toString(QDMI_ERROR_OUTOFMEM), "Out of memory"); - EXPECT_EQ(toString(QDMI_ERROR_NOTIMPLEMENTED), "Not implemented"); - EXPECT_EQ(toString(QDMI_ERROR_LIBNOTFOUND), "Library not found"); - EXPECT_EQ(toString(QDMI_ERROR_NOTFOUND), "Element not found"); - EXPECT_EQ(toString(QDMI_ERROR_OUTOFRANGE), "Out of range"); - EXPECT_EQ(toString(QDMI_ERROR_INVALIDARGUMENT), "Invalid argument"); - EXPECT_EQ(toString(QDMI_ERROR_PERMISSIONDENIED), "Permission denied"); - EXPECT_EQ(toString(QDMI_ERROR_NOTSUPPORTED), "Not supported"); - EXPECT_EQ(toString(QDMI_ERROR_BADSTATE), "Bad state"); - EXPECT_EQ(toString(QDMI_ERROR_TIMEOUT), "Timeout"); + EXPECT_STREQ(qdmi::toString(QDMI_WARN_GENERAL), "General warning"); + EXPECT_STREQ(qdmi::toString(QDMI_SUCCESS), "Success"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_FATAL), "A fatal error"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_OUTOFMEM), "Out of memory"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTIMPLEMENTED), "Not implemented"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_LIBNOTFOUND), "Library not found"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTFOUND), "Element not found"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_OUTOFRANGE), "Out of range"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_INVALIDARGUMENT), "Invalid argument"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_PERMISSIONDENIED), + "Permission denied"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTSUPPORTED), "Not supported"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_BADSTATE), "Bad state"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_TIMEOUT), "Timeout"); } TEST(FoMaCTest, SitePropertyToString) { - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_INDEX), "QDMI_SITE_PROPERTY_INDEX"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_T1), "QDMI_SITE_PROPERTY_T1"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_T2), "QDMI_SITE_PROPERTY_T2"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_NAME), "QDMI_SITE_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_XCOORDINATE), - "QDMI_SITE_PROPERTY_XCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_YCOORDINATE), - "QDMI_SITE_PROPERTY_YCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ZCOORDINATE), - "QDMI_SITE_PROPERTY_ZCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ISZONE), "QDMI_SITE_PROPERTY_ISZONE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_XEXTENT), "QDMI_SITE_PROPERTY_XEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_YEXTENT), "QDMI_SITE_PROPERTY_YEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ZEXTENT), "QDMI_SITE_PROPERTY_ZEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_MODULEINDEX), - "QDMI_SITE_PROPERTY_MODULEINDEX"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_SUBMODULEINDEX), - "QDMI_SITE_PROPERTY_SUBMODULEINDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_INDEX), "INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_T1), "T1"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_T2), "T2"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_XCOORDINATE), "X COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_YCOORDINATE), "Y COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ZCOORDINATE), "Z COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ISZONE), "IS ZONE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_XEXTENT), "X EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_YEXTENT), "Y EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ZEXTENT), "Z EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_MODULEINDEX), "MODULE INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_SUBMODULEINDEX), + "SUBMODULE INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, OperationPropertyToString) { - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_NAME), - "QDMI_OPERATION_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_QUBITSNUM), - "QDMI_OPERATION_PROPERTY_QUBITSNUM"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_PARAMETERSNUM), - "QDMI_OPERATION_PROPERTY_PARAMETERSNUM"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_DURATION), - "QDMI_OPERATION_PROPERTY_DURATION"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_FIDELITY), - "QDMI_OPERATION_PROPERTY_FIDELITY"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS), - "QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS), - "QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_QUBITSNUM), "QUBITS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_PARAMETERSNUM), + "PARAMETERS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_DURATION), "DURATION"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_FIDELITY), "FIDELITY"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS), + "INTERACTION RADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS), + "BLOCKING RADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_IDLINGFIDELITY), + "IDLING FIDELITY"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_ISZONED), "IS ZONED"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED), + "MEAN SHUTTLING SPEED"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, DevicePropertyToString) { - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_NAME), "QDMI_DEVICE_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_VERSION), - "QDMI_DEVICE_PROPERTY_VERSION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_STATUS), - "QDMI_DEVICE_PROPERTY_STATUS"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LIBRARYVERSION), - "QDMI_DEVICE_PROPERTY_LIBRARYVERSION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_QUBITSNUM), - "QDMI_DEVICE_PROPERTY_QUBITSNUM"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_SITES), "QDMI_DEVICE_PROPERTY_SITES"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_OPERATIONS), - "QDMI_DEVICE_PROPERTY_OPERATIONS"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_COUPLINGMAP), - "QDMI_DEVICE_PROPERTY_COUPLINGMAP"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION), - "QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LENGTHUNIT), - "QDMI_DEVICE_PROPERTY_LENGTHUNIT"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR), - "QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_DURATIONUNIT), - "QDMI_DEVICE_PROPERTY_DURATIONUNIT"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR), - "QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_MINATOMDISTANCE), - "QDMI_DEVICE_PROPERTY_MINATOMDISTANCE"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_VERSION), "VERSION"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_STATUS), "STATUS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LIBRARYVERSION), + "LIBRARY VERSION"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_QUBITSNUM), "QUBITS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_SITES), "SITES"); + 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"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_DURATIONUNIT), + "DURATION UNIT"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR), + "DURATION SCALE FACTOR"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_MINATOMDISTANCE), + "MIN ATOM DISTANCE"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS), + "SUPPORTED PROGRAM FORMATS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, SessionPropertyToString) { - EXPECT_EQ(toString(QDMI_SESSION_PROPERTY_DEVICES), - "QDMI_SESSION_PROPERTY_DEVICES"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PROPERTY_DEVICES), "DEVICES"); } TEST(FoMaCTest, ThrowIfError) { - EXPECT_NO_THROW(throwIfError(QDMI_SUCCESS, "Test")); - EXPECT_NO_THROW(throwIfError(QDMI_WARN_GENERAL, "Test")); - EXPECT_THROW(throwIfError(QDMI_ERROR_FATAL, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_OUTOFMEM, "Test"), std::bad_alloc); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTIMPLEMENTED, "Test"), + EXPECT_NO_THROW(qdmi::throwIfError(QDMI_SUCCESS, "Test")); + EXPECT_NO_THROW(qdmi::throwIfError(QDMI_WARN_GENERAL, "Test")); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_FATAL, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_LIBNOTFOUND, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_OUTOFMEM, "Test"), std::bad_alloc); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTIMPLEMENTED, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTFOUND, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_OUTOFRANGE, "Test"), std::out_of_range); - EXPECT_THROW(throwIfError(QDMI_ERROR_INVALIDARGUMENT, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_LIBNOTFOUND, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTFOUND, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_OUTOFRANGE, "Test"), + std::out_of_range); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_INVALIDARGUMENT, "Test"), std::invalid_argument); - EXPECT_THROW(throwIfError(QDMI_ERROR_PERMISSIONDENIED, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_PERMISSIONDENIED, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTSUPPORTED, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_BADSTATE, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTSUPPORTED, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_TIMEOUT, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_BADSTATE, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_TIMEOUT, "Test"), std::runtime_error); } TEST_P(DeviceTest, Name) { @@ -725,18 +743,18 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { } TEST(AuthenticationTest, SessionParameterToString) { - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTHFILE"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTHURL"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECTID"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); - EXPECT_EQ(toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTH FILE"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTH URL"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECT ID"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); + EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } TEST(AuthenticationTest, SessionConstructionWithToken) { From 59b6db4deb3a783989f6c7a80df9f888da21a89a Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 16:59:38 +0100 Subject: [PATCH 68/72] :memo: Add missing PR reference --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9b38b3b0b..f80b289c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed -- ✨ Add common definitions and utilities for QDMI ([**@burgholzer**]) +- ✨ Add common definitions and utilities for QDMI ([#1355]) ([**@burgholzer**]) - 🚚 Move `NA` QDMI device in its right place next to other QDMI devices ([#1355]) ([**@burgholzer**]) - ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**]) - ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) From 1a49aea3419a6d923885007023be3ddb19ed15b8 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 17:07:53 +0100 Subject: [PATCH 69/72] :green_heart: Fix Windows tests by using `EXPECT_STREQ` instead of `EXPECT_EQ` to test for content equivalence, which might be wonky on Windows in `constexpr` contexts --- test/fomac/test_fomac.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 76dd10ce2c..84fc7e3165 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -743,18 +743,18 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { } TEST(AuthenticationTest, SessionParameterToString) { - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTH FILE"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTH URL"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECT ID"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); - EXPECT_EQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTH FILE"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTH URL"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECT ID"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); } TEST(AuthenticationTest, SessionConstructionWithToken) { From df67ab42f89faff9a94115c7dd2f90fe53cec348 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 17:27:37 +0100 Subject: [PATCH 70/72] :recycle: Refactor URL validation regex to allow IPv4 and IPv6 addresses --- src/fomac/FoMaC.cpp | 26 ++++++++++++++++++++++++-- test/fomac/test_fomac.cpp | 10 ++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 246a956c41..d907ae3136 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -645,9 +645,31 @@ Session::Session(const SessionConfig& config) { } // Validate URL format for authUrl if (config.authUrl) { - // Adapted from: https://uibakery.io/regex-library/url + // Breakdown of the regex pattern: + // 1. ^https?:// -> Start with http:// or https:// + // 2. (?: -> Start Host Group + // \[[a-fA-F0-9:]+\] -> Branch A: IPv6 (Must be in brackets like + // [::1]) + // -> Note: No \b used here because ']' is a + // non-word char + // | -> OR + // (?: -> Branch B: Alphanumeric Hosts (Group for + // \b check) + // (?:\d{1,3}\.){3}\d{1,3} -> IPv4 (e.g., 127.0.0.1) + // | -> OR + // localhost -> Localhost + // | -> OR + // (?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6} -> + // Domain + // )\b -> End Branch B + Word Boundary (Prevents + // "localhostX") + // ) -> End Host Group + // 3. (?::\d+)? -> Optional Port (e.g., :8080) + // 4. (?:...)*$ -> Optional Path/Query params + End of + // string static const std::regex URL_PATTERN( - R"(^https?://(?:localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)"); + R"(^https?://(?:\[[a-fA-F0-9:]+\]|(?:(?:\d{1,3}\.){3}\d{1,3}|localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b)(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)", + std::regex::optimize); if (!std::regex_match(*config.authUrl, URL_PATTERN)) { throw std::runtime_error("Invalid URL format: " + *config.authUrl); } diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 84fc7e3165..cc4ad04b42 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -805,6 +805,16 @@ TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { configLocalhostPath.authUrl = "https://localhost:3000/auth/api"; EXPECT_NO_THROW({ const Session session(configLocalhostPath); }); + // Valid IPv4 address URL + SessionConfig configIPv4; + configIPv4.authUrl = "http://127.0.0.1:5000/auth"; + EXPECT_NO_THROW({ const Session session(configIPv4); }); + + // Valid IPv6 address URL + SessionConfig configIPv6; + configIPv6.authUrl = "https://[::1]:8080/auth"; + EXPECT_NO_THROW({ const Session session(configIPv6); }); + // Invalid URL - not a URL at all (validation fails before setting parameter) SessionConfig config4; config4.authUrl = "not-a-url"; From 2ea83b7f498e4f38217cdc7895b49a1b28a4979f Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 17:28:35 +0100 Subject: [PATCH 71/72] :pencil2: Fix typo --- src/qdmi/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index 3df41c680f..acd14d0bbf 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -9,7 +9,7 @@ set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-common) if(NOT TARGET ${TARGET_NAME}) - # Add driver library + # Add common library add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMICommon) # Add sources to target From 931e0e43bc644ffd73404cf0ec3a18ac60aaddb4 Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Sun, 7 Dec 2025 17:31:15 +0100 Subject: [PATCH 72/72] :memo: Add comments to highlight the subtle difference between `strncpy` and `strncpy_s` --- include/mqt-core/qdmi/Common.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/mqt-core/qdmi/Common.hpp b/include/mqt-core/qdmi/Common.hpp index 31260547ad..c3a75cd73f 100644 --- a/include/mqt-core/qdmi/Common.hpp +++ b/include/mqt-core/qdmi/Common.hpp @@ -51,6 +51,8 @@ namespace qdmi { } \ } +// STRNCPY wrapper: strncpy_s on Windows (auto null-terminates), +// strncpy on other platforms (requires manual null-termination - see usage). #ifdef _WIN32 #define STRNCPY(dest, src, size) \ strncpy_s(static_cast(dest), size, src, size); @@ -67,6 +69,7 @@ namespace qdmi { return QDMI_ERROR_INVALIDARGUMENT; \ } \ STRNCPY(value, prop_value, size); \ + /* Ensure null-termination: strncpy doesn't guarantee it on non-Win */ \ /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ static_cast(value)[size - 1] = '\0'; \ } \