diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f018b1ab..a263ea52d7 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 a new QDMI device that represents a superconducting architecture featuring a coupling map ([#1328]) ([**@ystade**]) - ✨ 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**]) @@ -265,6 +266,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336 +[#1328]: https://github.com/munich-quantum-toolkit/core/pull/1328 [#1327]: https://github.com/munich-quantum-toolkit/core/pull/1327 [#1310]: https://github.com/munich-quantum-toolkit/core/pull/1310 [#1301]: https://github.com/munich-quantum-toolkit/core/pull/1301 diff --git a/include/mqt-core/na/device/Device.hpp b/include/mqt-core/na/device/Device.hpp index 3a0f5dcdc3..7f1ed958df 100644 --- a/include/mqt-core/na/device/Device.hpp +++ b/include/mqt-core/na/device/Device.hpp @@ -27,7 +27,7 @@ #include #include -namespace qdmi { +namespace qdmi::na { class Device final { /// @brief Provides access to the device name. std::string name_; @@ -118,7 +118,7 @@ class Device final { auto queryProperty(QDMI_Device_Property prop, size_t size, void* value, size_t* sizeRet) -> int; }; -} // namespace qdmi +} // namespace qdmi::na /** * @brief Implementation of the MQT_NA_QDMI_Device_Session structure. @@ -216,46 +216,46 @@ struct MQT_NA_QDMI_Device_Job_impl_d { * @brief Sets a parameter for the job. * @see MQT_NA_QDMI_device_job_set_parameter */ - static auto setParameter(QDMI_Device_Job_Parameter param, size_t size, - const void* value) -> int; + auto setParameter(QDMI_Device_Job_Parameter param, size_t size, + const void* value) -> int; /** * @brief Queries a property of the job. * @see MQT_NA_QDMI_device_job_query_property */ - static auto queryProperty(QDMI_Device_Job_Property prop, size_t size, - void* value, size_t* sizeRet) -> int; + auto queryProperty(QDMI_Device_Job_Property prop, size_t size, void* value, + size_t* sizeRet) -> int; /** * @brief Submits the job to the device. * @see MQT_NA_QDMI_device_job_submit */ - static auto submit() -> int; + auto submit() -> int; /** * @brief Cancels the job. * @see MQT_NA_QDMI_device_job_cancel */ - static auto cancel() -> int; + auto cancel() -> int; /** * @brief Checks the status of the job. * @see MQT_NA_QDMI_device_job_check */ - static auto check(QDMI_Job_Status* status) -> int; + auto check(QDMI_Job_Status* status) -> int; /** * @brief Waits for the job to complete but at most for the specified timeout. * @see MQT_NA_QDMI_device_job_wait */ - static auto wait(size_t timeout) -> int; + auto wait(size_t timeout) -> int; /** * @brief Gets the results of the job. * @see MQT_NA_QDMI_device_job_get_results */ - static auto getResults(QDMI_Job_Result result, size_t size, void* data, - [[maybe_unused]] size_t* sizeRet) -> int; + auto getResults(QDMI_Job_Result result, size_t size, void* data, + [[maybe_unused]] size_t* sizeRet) -> int; }; /** diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 11512b5eee..9a8735a3ce 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -140,6 +140,7 @@ class DynamicDeviceLibrary final : public DeviceLibrary { // Call the above macro for all static libraries that we want to support. DECLARE_STATIC_LIBRARY(MQT_NA) DECLARE_STATIC_LIBRARY(MQT_DDSIM) +DECLARE_STATIC_LIBRARY(MQT_SC) /** * @brief The status of a session. @@ -407,11 +408,22 @@ class Driver final { #ifndef _WIN32 /** * @brief Adds a dynamic device library to the driver. - * @param libName is the path to the dynamic library to load. - * @param prefix is the prefix used for the device interface functions. - * @returns `true` if the library was successfully loaded, `false` - * if it was already loaded. + * @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. + * + * @param libName The path to the dynamic library to load. + * @param prefix The prefix used for the device interface functions in the + * library. + * @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::bad_alloc If memory allocation fails during the process. */ auto addDynamicDeviceLibrary(const std::string& libName, const std::string& prefix) -> bool; diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp new file mode 100644 index 0000000000..3376b6a06e --- /dev/null +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -0,0 +1,336 @@ +/* + * 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 + */ + +#pragma once + +/** @file + * @brief The MQT QDMI device implementation for superconducting devices. + */ + +#include "mqt_sc_qdmi/device.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qdmi::sc { +class Device final { + /// @brief Provides access to the device name. + std::string name_; + + /// @brief The number of qubits in the device. + size_t qubitsNum_ = 0; + + /// @brief The list of sites. + std::vector> sites_; + + /// @brief The list of couplings, i.e., qubit pairs + std::vector> couplingMap_; + + /// @brief The list of operations. + std::vector> operations_; + + /// @brief The list of device sessions. + std::unordered_map> + sessions_; + + /// @brief Private constructor to enforce the singleton pattern. + Device(); + + /// @brief The singleton instance. + static std::atomic instance; + +public: + // Delete move constructor and move assignment operator. + Device(Device&&) = delete; + Device& operator=(Device&&) = delete; + // Delete copy constructor and assignment operator to enforce singleton. + Device(const Device&) = delete; + Device& operator=(const Device&) = delete; + + /// @brief Destructor for the Device class. + ~Device(); + + /** + * @brief Initializes the singleton instance. + * @details Must be called before `get()`. + */ + static void initialize(); + + /** + * @brief Destroys the singleton instance. + * @details After this call, `get()` must not be called until a new + * `initialize()` call. + */ + static void finalize(); + + /// @returns the singleton instance of the Device class. + [[nodiscard]] static auto get() -> Device&; + + /** + * @brief Allocates a new device session. + * @see MQT_SC_QDMI_device_session_alloc + */ + auto sessionAlloc(MQT_SC_QDMI_Device_Session* session) -> int; + + /** + * @brief Frees a device session. + * @see MQT_SC_QDMI_device_session_free + */ + auto sessionFree(MQT_SC_QDMI_Device_Session session) -> void; + + /** + * @brief Query a device property. + * @see MQT_SC_QDMI_device_session_query_device_property + */ + auto queryProperty(QDMI_Device_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; +}; +} // namespace qdmi::sc + +/** + * @brief Implementation of the MQT_SC_QDMI_Device_Session structure. + */ +struct MQT_SC_QDMI_Device_Session_impl_d { +private: + /// The status of the session. + enum class Status : uint8_t { + ALLOCATED, ///< The session has been allocated but not initialized + INITIALIZED, ///< The session has been initialized and is ready for use + }; + /// @brief The current status of the session. + Status status_ = Status::ALLOCATED; + /// @brief The device jobs associated with this session. + std::unordered_map> + jobs_; + +public: + /** + * @brief Initializes the device session. + * @see MQT_SC_QDMI_device_session_init + */ + auto init() -> int; + + /** + * @brief Sets a parameter for the device session. + * @see MQT_SC_QDMI_device_session_set_parameter + */ + auto setParameter(QDMI_Device_Session_Parameter param, size_t size, + const void* value) const -> int; + + /** + * @brief Create a new device job. + * @see MQT_SC_QDMI_device_session_create_device_job + */ + auto createDeviceJob(MQT_SC_QDMI_Device_Job* job) -> int; + + /** + * @brief Frees the device job. + * @see MQT_SC_QDMI_device_job_free + */ + auto freeDeviceJob(MQT_SC_QDMI_Device_Job job) -> void; + + /** + * @brief Forwards a query of a device property to the device. + * @see MQT_SC_QDMI_device_session_query_device_property + */ + auto queryDeviceProperty(QDMI_Device_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; + + /** + * @brief Forwards a query of a site property to the site. + * @see MQT_SC_QDMI_device_session_query_site_property + */ + auto querySiteProperty(MQT_SC_QDMI_Site site, QDMI_Site_Property prop, + size_t size, void* value, size_t* sizeRet) const + -> int; + + /** + * @brief Forwards a query of an operation property to the operation. + * @see MQT_SC_QDMI_device_session_query_operation_property + */ + auto queryOperationProperty(MQT_SC_QDMI_Operation operation, size_t numSites, + const MQT_SC_QDMI_Site* sites, size_t numParams, + const double* params, + QDMI_Operation_Property prop, size_t size, + void* value, size_t* sizeRet) const -> int; +}; + +/** + * @brief Implementation of the MQT_SC_QDMI_Device_Job structure. + */ +struct MQT_SC_QDMI_Device_Job_impl_d { +private: + /// @brief The device session associated with the job. + MQT_SC_QDMI_Device_Session_impl_d* session_; + +public: + /** + * @brief Initializes a device job implementation bound to the given session. + * + * @param session Pointer to the owning MQT_SC_QDMI_Device_Session_impl_d. The + * session must remain valid for the job's lifetime. + */ + explicit MQT_SC_QDMI_Device_Job_impl_d( + MQT_SC_QDMI_Device_Session_impl_d* session) + : session_(session) {} + /** + * @brief Frees the device job. + * @note This function just forwards to the session's @ref freeDeviceJob + * function. This function is needed because the interface only provides the + * job handle to the @ref QDMI_job_free function and the job's session handle + * is private. + * @see QDMI_job_free + */ + auto free() -> void; + + /** + * @brief Sets a parameter for the job. + * @see MQT_SC_QDMI_device_job_set_parameter + */ + auto setParameter(QDMI_Device_Job_Parameter param, size_t size, + const void* value) -> int; + + /** + * @brief Queries a property of the job. + * @see MQT_SC_QDMI_device_job_query_property + */ + auto queryProperty(QDMI_Device_Job_Property prop, size_t size, void* value, + size_t* sizeRet) -> int; + + /** + * @brief Submits the job to the device. + * @see MQT_SC_QDMI_device_job_submit + */ + auto submit() -> int; + + /** + * @brief Cancels the job. + * @see MQT_SC_QDMI_device_job_cancel + */ + auto cancel() -> int; + + /** + * @brief Checks the status of the job. + * @see MQT_SC_QDMI_device_job_check + */ + auto check(QDMI_Job_Status* status) -> int; + + /** + * @brief Waits for the job to complete but at most for the specified timeout. + * @see MQT_SC_QDMI_device_job_wait + */ + auto wait(size_t timeout) -> int; + + /** + * @brief Gets the results of the job. + * @see MQT_SC_QDMI_device_job_get_results + */ + auto getResults(QDMI_Job_Result result, size_t size, void* data, + [[maybe_unused]] size_t* sizeRet) -> int; +}; + +/** + * @brief Implementation of the MQT_SC_QDMI_Site structure. + */ +struct MQT_SC_QDMI_Site_impl_d { + friend MQT_SC_QDMI_Operation_impl_d; + +private: + uint64_t id_ = 0; ///< Unique identifier of the site + + /** + * @brief Initializes a site implementation with the given unique identifier. + * + * @param id Unique identifier for the site. + */ + explicit MQT_SC_QDMI_Site_impl_d(uint64_t id) : id_(id) {} + +public: + /// @brief Factory function for regular sites. + [[nodiscard]] static auto makeUniqueSite(uint64_t id) + -> std::unique_ptr; + /** + * @brief Queries a property of the site. + * @see MQT_SC_QDMI_device_session_query_site_property + */ + auto queryProperty(QDMI_Site_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; +}; + +/** + * @brief Implementation of the MQT_SC_QDMI_Operation structure. + */ +struct MQT_SC_QDMI_Operation_impl_d { +private: + std::string name_; ///< Name of the operation + size_t numParameters_; ///< Number of parameters for the operation + /** + * @brief Number of qubits involved in the operation + */ + size_t numQubits_{}; + /** + * @brief Storage for individual sites and site pairs. + * @details Uses std::variant to preserve the tuple structure of the operation + * sites: + * - Single-qubit and zoned operations: vector + * - Local two-qubit operations: vector> + * This maintains type safety and QDMI specification compliance, which states + * that operation sites should be "a list of tuples" for local multi-qubit + * operations. + */ + using SitesStorage = + std::variant, + std::vector>>; + + /// The operation's supported sites + SitesStorage supportedSites_; + /// @brief Constructor for a single-qubit operation. + MQT_SC_QDMI_Operation_impl_d(std::string name, size_t numParameters, + const std::vector& sites); + /// @brief Constructor for a two-qubit operation. + MQT_SC_QDMI_Operation_impl_d( + std::string name, size_t numParameters, + const std::vector>& sites); + + /// @brief Sort the sites such that the occurrence of a given site can be + /// determined in O(log n) time. + auto sortSites() -> void; + +public: + /// @brief Factory function a single-qubit operation. + [[nodiscard]] static auto + makeUniqueSingleQubit(std::string name, size_t numParameters, + const std::vector& sites) + -> std::unique_ptr; + /// @brief Factory function a two-qubit operation. + [[nodiscard]] static auto makeUniqueTwoQubit( + std::string name, size_t numParameters, + const std::vector>& sites) + -> std::unique_ptr; + + /** + * @brief Queries a property of the operation. + * @see MQT_SC_QDMI_device_session_query_operation_property + */ + auto queryProperty(size_t numSites, const MQT_SC_QDMI_Site* sites, + size_t numParams, const double* params, + QDMI_Operation_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; +}; diff --git a/include/mqt-core/qdmi/sc/Generator.hpp b/include/mqt-core/qdmi/sc/Generator.hpp new file mode 100644 index 0000000000..19c86964d8 --- /dev/null +++ b/include/mqt-core/qdmi/sc/Generator.hpp @@ -0,0 +1,123 @@ +/* + * 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 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +// NOLINTNEXTLINE(misc-include-cleaner) +#include +#include +#include +#include +#include +#include +#include + +namespace sc { +/** + * @brief Represents a superconducting device configuration. + * @details This struct defines the schema for the JSON representation of a + * superconducting device configuration. This struct, including all its + * sub-structs, implements functions to serialize and deserialize to and from + * JSON using the nlohmann::json library. + */ +struct Device { + /// @brief The name of the device. + std::string name; + /// @brief The number of qubits in the device. + uint64_t numQubits = 0; + /// @brief The list of couplings the device supports. + std::vector> couplings; + +private: + struct Operation { + /// @brief The name of the operation. + std::string name; + /// @brief The number of parameters the operation takes. + uint64_t numParameters = 0; + /// @brief The number of qubits the operation takes. + uint64_t numQubits = 0; + + // NOLINTNEXTLINE(misc-include-cleaner) + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Operation, name, numParameters, + numQubits) + }; + +public: + /// @brief The list of operations the device supports. + std::vector operations; + + // NOLINTNEXTLINE(misc-include-cleaner) + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Device, name, numQubits, + couplings, operations) +}; + +/** + * @brief Writes a JSON schema with default values for the device configuration + * to the specified output stream. + * @param os is the output stream to write the JSON schema to. + * @throws std::runtime_error if the JSON conversion fails. + */ +auto writeJSONSchema(std::ostream& os) -> void; + +/** + * @brief Writes a JSON schema with default values for the device configuration + * to the specified path. + * @param path The path to write the JSON schema to. + * @throws std::runtime_error if the JSON conversion fails or the file cannot be + * opened. + */ +auto writeJSONSchema(const std::string& path) -> void; + +/** + * @brief Parses the device configuration from an input stream. + * @param is is the input stream containing the JSON representation of the + * device configuration. + * @returns The parsed device configuration as a @ref sc::Device object. + * @throws std::runtime_error if the JSON cannot be parsed. + */ +[[nodiscard]] auto readJSON(std::istream& is) -> Device; + +/** + * @brief Parses the device configuration from a JSON file. + * @param path is the path to the JSON file containing the device configuration. + * @returns The parsed device configuration as a @ref sc::Device object. + * @throws std::runtime_error if the JSON file does not exist, or the JSON file + * cannot be parsed. + */ +[[nodiscard]] auto readJSON(const std::string& path) -> Device; + +/** + * @brief Writes a header file with the device configuration to the specified + * output stream. + * @param device is a parsed and in-memory representation of the device. + * @param os is the output stream to write the header file to. + * @throws std::runtime_error if the file cannot be opened or written to. + * @note This implementation only supports multi-qubit gates up to two + * qubits. + */ +auto writeHeader(const Device& device, std::ostream& os) -> void; + +/** + * @brief Writes a header file with the device configuration to the specified + * path. + * @param device is a parsed and in-memory representation of the device. + * @param path is the path to write the header file to. + * @throws std::runtime_error if the file cannot be opened or written to. + * @note This implementation only supports multi-qubit gates up to two + * qubits. + */ +auto writeHeader(const Device& device, const std::string& path) -> void; +} // namespace sc diff --git a/json/sc/device.json b/json/sc/device.json new file mode 100644 index 0000000000..8ebbc0ef8b --- /dev/null +++ b/json/sc/device.json @@ -0,0 +1,190 @@ +{ + "name": "MQT SC Default QDMI Device", + "numQubits": 100, + "couplings": [ + [0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [10, 11], + [11, 12], + [12, 13], + [13, 14], + [14, 15], + [15, 16], + [16, 17], + [17, 18], + [18, 19], + [20, 21], + [21, 22], + [22, 23], + [23, 24], + [24, 25], + [25, 26], + [26, 27], + [27, 28], + [28, 29], + [30, 31], + [31, 32], + [32, 33], + [33, 34], + [34, 35], + [35, 36], + [36, 37], + [37, 38], + [38, 39], + [40, 41], + [41, 42], + [42, 43], + [43, 44], + [44, 45], + [45, 46], + [46, 47], + [47, 48], + [48, 49], + [50, 51], + [51, 52], + [52, 53], + [53, 54], + [54, 55], + [55, 56], + [56, 57], + [57, 58], + [58, 59], + [60, 61], + [61, 62], + [62, 63], + [63, 64], + [64, 65], + [65, 66], + [66, 67], + [67, 68], + [68, 69], + [70, 71], + [71, 72], + [72, 73], + [73, 74], + [74, 75], + [75, 76], + [76, 77], + [77, 78], + [78, 79], + [80, 81], + [81, 82], + [82, 83], + [83, 84], + [84, 85], + [85, 86], + [86, 87], + [87, 88], + [88, 89], + [90, 91], + [91, 92], + [92, 93], + [93, 94], + [94, 95], + [95, 96], + [96, 97], + [97, 98], + [98, 99], + [0, 10], + [1, 11], + [2, 12], + [3, 13], + [4, 14], + [5, 15], + [6, 16], + [7, 17], + [8, 18], + [9, 19], + [10, 20], + [11, 21], + [12, 22], + [13, 23], + [14, 24], + [15, 25], + [16, 26], + [17, 27], + [18, 28], + [19, 29], + [20, 30], + [21, 31], + [22, 32], + [23, 33], + [24, 34], + [25, 35], + [26, 36], + [27, 37], + [28, 38], + [29, 39], + [30, 40], + [31, 41], + [32, 42], + [33, 43], + [34, 44], + [35, 45], + [36, 46], + [37, 47], + [38, 48], + [39, 49], + [40, 50], + [41, 51], + [42, 52], + [43, 53], + [44, 54], + [45, 55], + [46, 56], + [47, 57], + [48, 58], + [49, 59], + [50, 60], + [51, 61], + [52, 62], + [53, 63], + [54, 64], + [55, 65], + [56, 66], + [57, 67], + [58, 68], + [59, 69], + [60, 70], + [61, 71], + [62, 72], + [63, 73], + [64, 74], + [65, 75], + [66, 76], + [67, 77], + [68, 78], + [69, 79], + [70, 80], + [71, 81], + [72, 82], + [73, 83], + [74, 84], + [75, 85], + [76, 86], + [77, 87], + [78, 88], + [79, 89], + [80, 90], + [81, 91], + [82, 92], + [83, 93], + [84, 94], + [85, 95], + [86, 96], + [87, 97], + [88, 98], + [89, 99] + ], + "operations": [ + { "name": "r", "numParameters": 2, "numQubits": 1 }, + { "name": "cz", "numParameters": 0, "numQubits": 2 } + ] +} diff --git a/src/na/device/App.cpp b/src/na/device/App.cpp index 7b7e5c25e1..9901218b2a 100644 --- a/src/na/device/App.cpp +++ b/src/na/device/App.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -60,10 +61,8 @@ auto printSchemaUsage(const std::string& programName) -> void { "\n" "Options:\n" " -h, --help Show this help message and exit.\n" - " -o, --output Specify the output file. If no output " - " file is " - " specified, the schema is printed to " - " stdout.\n"; + " -o, --output Specify the output file. If not\n" + " specified, prints to stdout.\n"; } /** diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp index 6e4d53a3c5..634f9487b7 100644 --- a/src/na/device/Device.cpp +++ b/src/na/device/Device.cpp @@ -95,7 +95,7 @@ } // NOLINTEND(bugprone-macro-parentheses) -namespace qdmi { +namespace qdmi::na { std::atomic Device::instance = nullptr; Device::Device() { @@ -206,7 +206,7 @@ auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, } return QDMI_ERROR_NOTSUPPORTED; } -} // namespace qdmi +} // namespace qdmi::na auto MQT_NA_QDMI_Device_Session_impl_d::init() -> int { if (status_ != Status::ALLOCATED) { @@ -252,7 +252,7 @@ auto MQT_NA_QDMI_Device_Session_impl_d::queryDeviceProperty( if (status_ != Status::INITIALIZED) { return QDMI_ERROR_BADSTATE; } - return qdmi::Device::get().queryProperty(prop, size, value, sizeRet); + return qdmi::na::Device::get().queryProperty(prop, size, value, sizeRet); } auto MQT_NA_QDMI_Device_Session_impl_d::querySiteProperty( MQT_NA_QDMI_Site site, const QDMI_Site_Property prop, const size_t size, @@ -282,6 +282,7 @@ auto MQT_NA_QDMI_Device_Session_impl_d::queryOperationProperty( auto MQT_NA_QDMI_Device_Job_impl_d::free() -> void { session_->freeDeviceJob(this); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { @@ -291,6 +292,7 @@ auto MQT_NA_QDMI_Device_Job_impl_d::setParameter( } return QDMI_ERROR_NOTSUPPORTED; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) 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, @@ -300,23 +302,27 @@ auto MQT_NA_QDMI_Device_Job_impl_d::queryProperty( } return QDMI_ERROR_NOTSUPPORTED; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::submit() -> int { return QDMI_ERROR_NOTSUPPORTED; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::cancel() -> int { return QDMI_ERROR_NOTSUPPORTED; } -// NOLINTNEXTLINE(readability-non-const-parameter) +// NOLINTNEXTLINE(readability-non-const-parameter,readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::check(QDMI_Job_Status* status) -> int { if (status == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::wait([[maybe_unused]] const size_t timeout) -> int { return QDMI_ERROR_NOTSUPPORTED; } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) auto MQT_NA_QDMI_Device_Job_impl_d::getResults( QDMI_Job_Result result, // NOLINTNEXTLINE(readability-non-const-parameter) @@ -648,17 +654,17 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( } int MQT_NA_QDMI_device_initialize() { - qdmi::Device::initialize(); + qdmi::na::Device::initialize(); return QDMI_SUCCESS; } int MQT_NA_QDMI_device_finalize() { - qdmi::Device::finalize(); + qdmi::na::Device::finalize(); return QDMI_SUCCESS; } int MQT_NA_QDMI_device_session_alloc(MQT_NA_QDMI_Device_Session* session) { - return qdmi::Device::get().sessionAlloc(session); + return qdmi::na::Device::get().sessionAlloc(session); } int MQT_NA_QDMI_device_session_init(MQT_NA_QDMI_Device_Session session) { @@ -669,7 +675,7 @@ int MQT_NA_QDMI_device_session_init(MQT_NA_QDMI_Device_Session session) { } void MQT_NA_QDMI_device_session_free(MQT_NA_QDMI_Device_Session session) { - qdmi::Device::get().sessionFree(session); + qdmi::na::Device::get().sessionFree(session); } int MQT_NA_QDMI_device_session_set_parameter( @@ -697,11 +703,6 @@ int MQT_NA_QDMI_device_job_set_parameter(MQT_NA_QDMI_Device_Job job, if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. However, - // we keep the function call for a complete implementation of QDMI and silence - // the respective warning. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->setParameter(param, size, value); } @@ -712,11 +713,6 @@ int MQT_NA_QDMI_device_job_query_property(MQT_NA_QDMI_Device_Job job, if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->queryProperty(prop, size, value, sizeRet); } @@ -724,11 +720,7 @@ int MQT_NA_QDMI_device_job_submit(MQT_NA_QDMI_Device_Job job) { if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) + return job->submit(); } @@ -736,11 +728,6 @@ int MQT_NA_QDMI_device_job_cancel(MQT_NA_QDMI_Device_Job job) { if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->cancel(); } @@ -749,11 +736,6 @@ int MQT_NA_QDMI_device_job_check(MQT_NA_QDMI_Device_Job job, if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->check(status); } @@ -762,11 +744,6 @@ int MQT_NA_QDMI_device_job_wait(MQT_NA_QDMI_Device_Job job, if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->wait(timeout); } @@ -777,11 +754,6 @@ int MQT_NA_QDMI_device_job_get_results(MQT_NA_QDMI_Device_Job job, if (job == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; } - // The called function is only static because jobs are not supported. We keep - // the function call, however, as it is needed to free the job when the jobs - // are supported. - //===--------------------------------------------------------------------===// - // NOLINTNEXTLINE(readability-static-accessed-through-instance) return job->getResults(result, size, data, sizeRet); } diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index cf77b3e7fd..2a74069adf 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -7,6 +7,7 @@ # Licensed under the MIT License add_subdirectory(dd) +add_subdirectory(sc) set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-driver) @@ -25,7 +26,8 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::CoreQDMINaDevice MQT::CoreQDMI_DDSIM_Device qdmi::project_warnings) + PRIVATE MQT::CoreQDMINaDevice MQT::CoreQDMIScDevice MQT::CoreQDMI_DDSIM_Device + qdmi::project_warnings) # add to list of MQT core targets set(MQT_CORE_TARGETS diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 73d03bd821..3438a48f17 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -12,6 +12,7 @@ #include "mqt_ddsim_qdmi/device.h" #include "mqt_na_qdmi/device.h" +#include "mqt_sc_qdmi/device.h" #include #include @@ -76,6 +77,7 @@ namespace qdmi { } DEFINE_STATIC_LIBRARY(MQT_NA) DEFINE_STATIC_LIBRARY(MQT_DDSIM) +DEFINE_STATIC_LIBRARY(MQT_SC) #ifndef _WIN32 DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, @@ -336,6 +338,8 @@ Driver::Driver() { std::make_unique())); devices_.emplace_back(std::make_unique( std::make_unique())); + devices_.emplace_back(std::make_unique( + std::make_unique())); } Driver::~Driver() { @@ -355,6 +359,8 @@ auto Driver::addDynamicDeviceLibrary(const std::string& libName, // false. return false; } + // Re-throw other exception + throw; } return true; } diff --git a/src/qdmi/sc/App.cpp b/src/qdmi/sc/App.cpp new file mode 100644 index 0000000000..c9c33f493f --- /dev/null +++ b/src/qdmi/sc/App.cpp @@ -0,0 +1,506 @@ +/* + * 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 + */ + +#include "qdmi/sc/Generator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +/** + * @brief Writes usage information and available commands and options to stdout. + * + * @param programName Program executable name inserted into the Usage line. + */ +auto printUsage(const std::string& programName) -> void { + std::cout + << "Generator for turning superconducting computer JSON specifications " + "into " + "header files to be used as part of a superconducting QDMI device " + "implementation.\n" + "\n" + "Usage: " + << programName + << " [OPTIONS] [ARGS]\n" + "\n" + "Commands:\n" + " schema Generate a default JSON schema.\n" + " validate Validate a JSON specification.\n" + " generate Generate a header file from a JSON specification.\n" + "\n" + "Options:\n" + " -h, --help Show this help message and exit.\n" + " -v, --version Show version information and exit.\n"; +} + +/** + * Prints the usage information for the schema sub-command. + * @param programName is the name of the program executable. + */ +auto printSchemaUsage(const std::string& programName) -> void { + std::cout << "Generates a JSON schema with default values.\n" + "\n" + "Usage: " + << programName + << " schema [options]\n" + "\n" + "Options:\n" + " -h, --help Show this help message and exit.\n" + " -o, --output Specify the output file. If not\n" + " specified, prints to stdout.\n"; +} + +/** + * Prints the usage information for the validate sub-command. + * @param programName is the name of the program executable. + */ +auto printValidateUsage(const std::string& programName) -> void { + std::cout << "Validates a JSON specification against the schema.\n" + "\n" + "Usage: " + << programName + << " validate [options] []\n" + "\n" + "Arguments:\n" + " json_file the path to the JSON file to validate. If\n" + " not specified, the JSON is read from stdin.\n" + "\n" + "Options:\n" + " -h, --help Show this help message and exit.\n"; +} + +/** + * Prints the usage information for the generate sub-command. + * @param programName is the name of the program executable. + */ +auto printGenerateUsage(const std::string& programName) -> void { + std::cout << "Generates a header file from a JSON specification.\n" + "\n" + "Usage: " + << programName + << " generate [options] []\n" + "\n" + "Arguments:\n" + " json_file the path to the JSON file to generate the\n" + " header file from. If not specified, the\n" + " JSON is read from stdin.\n" + "\n" + "Options:\n" + " -h, --help Show this help message and exit.\n" + " -o, --output Specify the output file for the\n" + " generated header file. If no output\n" + " file is specified, the header file is\n" + " printed to stdout.\n"; +} + +/** + * @brief Writes the tool's version string to standard output. + * + * Prints the program name and the embedded MQT core version to stdout. + */ +auto printVersion() -> void { + // NOLINTNEXTLINE(misc-include-cleaner) + std::cout << "MQT QDMI SC Device Generator (MQT Version " MQT_CORE_VERSION + ")\n"; +} + +/// Enum to represent the different commands that can be executed. +enum class Command : uint8_t { + Schema, ///< Command to generate a JSON schema + Validate, ///< Command to validate a JSON specification + Generate ///< Command to generate a header file from a JSON specification +}; + +/// Struct to hold the parsed command line arguments. +struct Arguments { + std::string programName; ///< Name of the program executable + bool help = false; ///< Flag to indicate if help is requested + /// Flag to indicate if version information is requested + bool version = false; + std::optional command; ///< Command to execute +}; + +/// Struct to hold the parsed schema command line arguments. +struct SchemaArguments { + bool help = false; ///< Flag to indicate if help is requested + /// Optional output file for the schema + std::optional outputFile; +}; + +/// Struct to hold the parsed validate command line arguments. +struct ValidateArguments { + bool help = false; ///< Flag to indicate if help is requested + /// Optional JSON file to validate + std::optional jsonFile; +}; + +/// Struct to hold the parsed generate command line arguments. +struct GenerateArguments { + bool help = false; ///< Flag to indicate if help is requested + /// Optional output file for the generated header file + std::optional outputFile; + /// Optional JSON file to parse the device configuration + std::optional jsonFile; +}; + +/** + * @brief Parse top-level command-line options and locate the chosen + * sub-command. + * + * @param args Vector of command-line tokens (typically argv converted to + * std::string), where args[0] is the program name. + * @return std::pair The first element is the parsed + * top-level Arguments; the second element is the index in `args` of the + * first argument belonging to the chosen sub-command (i.e., one past the + * sub-command token). If no sub-command is present, the returned index + * will be `args.size() + 1`. + */ +auto parseArguments(const std::vector& args) + -> std::pair { + Arguments arguments; + arguments.programName = + args.empty() ? "mqt-core-sc-device-gen" : args.front(); + size_t i = 1; + while (i < args.size()) { + if (const std::string& arg = args.at(i); arg == "-h" || arg == "--help") { + arguments.help = true; + } else if (arg == "-v" || arg == "--version") { + arguments.version = true; + } else if (arg == "schema") { + arguments.command = Command::Schema; + break; // Stop top-level parsing; remaining args handled by schema parser + } else if (arg == "validate") { + arguments.command = Command::Validate; + // Stop top-level parsing; remaining args handled by validate parser + break; + } else if (arg == "generate") { + arguments.command = Command::Generate; + // Stop top-level parsing; remaining args handled by generate parser + break; + } else { + throw std::invalid_argument("Unknown argument: " + arg); + } + ++i; + } + return {arguments, i + 1}; +} + +/** + * @brief Parse arguments for the "schema" sub-command. + * + * Parses options for the schema command and produces a SchemaArguments value + * describing whether help was requested and which output file (if any) was set. + * + * @param args Vector of all command-line arguments. + * @param i Index of the first argument belonging to the schema sub-command. + * @return SchemaArguments Struct with `help` set if help was requested and + * `outputFile` containing the path provided with `-o|--output`, if any. + */ +auto parseSchemaArguments(const std::vector& args, size_t i) + -> SchemaArguments { + SchemaArguments schemaArgs; + while (i < args.size()) { + if (const std::string& arg = args.at(i); arg == "-h" || arg == "--help") { + schemaArgs.help = true; + } else if (arg == "-o" || arg == "--output") { + if (++i >= args.size()) { + throw std::invalid_argument("Missing value for output option."); + } + schemaArgs.outputFile = args.at(i); + } else { + throw std::invalid_argument("Unknown argument: " + arg); + } + ++i; + } + return schemaArgs; +} + +/** + * @brief Parses arguments for the "validate" subcommand. + * + * @param args Vector of command-line arguments. + * @param i Index of the first validate subcommand argument within @p args. + * @return ValidateArguments Parsed flags and optional JSON input file path: + * `help` is set if -h/--help was present, `jsonFile` contains the positional + * JSON file if provided. + * @throws std::invalid_argument if multiple JSON files are specified. + */ +auto parseValidateArguments(const std::vector& args, size_t i) + -> ValidateArguments { + ValidateArguments validateArgs; + while (i < args.size()) { + if (const std::string& arg = args.at(i); arg == "-h" || arg == "--help") { + validateArgs.help = true; + } else { + if (validateArgs.jsonFile.has_value()) { + throw std::invalid_argument("Multiple JSON files specified"); + } + validateArgs.jsonFile = arg; + } + ++i; + } + return validateArgs; +} + +/** + * Parse arguments for the "generate" subcommand. + * + * Recognizes the following arguments: + * - `-h`, `--help`: sets the help flag. + * - `-o `, `--output `: sets the output header file path. + * - `` (positional): sets the input JSON file; if omitted, input is + * read from stdin. + * + * @param args Vector of command-line arguments. + * @param i Index of the first argument belonging to the subcommand within + * `args`. + * @return GenerateArguments Structure with `help`, optional `outputFile`, and + * optional `jsonFile` populated. + * @throws std::invalid_argument If an `-o`/`--output` option is provided + * without a following value. + * @throws std::invalid_argument if multiple JSON files are specified. + */ +auto parseGenerateArguments(const std::vector& args, size_t i) + -> GenerateArguments { + GenerateArguments generateArgs; + while (i < args.size()) { + if (const std::string& arg = args.at(i); arg == "-h" || arg == "--help") { + generateArgs.help = true; + } else if (arg == "-o" || arg == "--output") { + if (++i >= args.size()) { + throw std::invalid_argument("Missing value for output option."); + } + generateArgs.outputFile = args.at(i); + } else { + if (generateArgs.jsonFile.has_value()) { + throw std::invalid_argument("Multiple JSON files specified"); + } + generateArgs.jsonFile = arg; + } + ++i; + } + return generateArgs; +} + +/** + * Executes the schema command, generating a JSON schema and writing it to the + * specified output file or stdout. + * @param progName is the name of the program executable. + * @param argVec is the vector of command line arguments. + * @param i is the index to the first sub-command argument within @p argVec + * @return 0 on success, 1 on error. + */ +auto executeSchemaCommand(const std::string& progName, + const std::vector& argVec, + const size_t i) -> int { + SchemaArguments schemaArgs; + // parse the rest of the command line arguments for the schema command + try { + schemaArgs = parseSchemaArguments(argVec, i); + } catch (const std::exception& e) { + SPDLOG_ERROR("Error parsing schema arguments: {}", e.what()); + printSchemaUsage(progName); + return 1; + } + // if the help flag is set, print the schema usage information and exit + if (schemaArgs.help) { + printSchemaUsage(progName); + return 0; + } + // generate the JSON schema and write it to the output file or stdout + try { + if (schemaArgs.outputFile.has_value()) { + sc::writeJSONSchema(schemaArgs.outputFile.value()); + } else { + sc::writeJSONSchema(std::cout); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error generating JSON schema: {}", e.what()); + return 1; + } + return 0; +} + +/** + * @brief Run the "validate" subcommand to validate a JSON input. + * + * Parses validate-specific arguments, prints subcommand usage if the help + * flag is set, and validates JSON read from the provided file path or from + * standard input. + * + * @param progName Name of the program executable (used for usage output). + * @param argVec Full command-line argument vector. + * @param i Index of the first argument belonging to the validate subcommand. + * @return int `0` on successful validation or when help was printed, `1` on + * error. + */ +auto executeValidateCommand(const std::string& progName, + const std::vector& argVec, + const size_t i) -> int { + ValidateArguments validateArgs; + // parse the rest of the command line arguments for the validate command + try { + validateArgs = parseValidateArguments(argVec, i); + } catch (const std::exception& e) { + SPDLOG_ERROR("Error parsing validate arguments: {}", e.what()); + printValidateUsage(progName); + return 1; + } + + // if the help flag is set, print the validate usage information and exit + if (validateArgs.help) { + printValidateUsage(progName); + return 0; + } + // validate the JSON file or the JSON string from stdin + try { + if (validateArgs.jsonFile.has_value()) { + std::ignore = sc::readJSON(validateArgs.jsonFile.value()); + } else { + std::ignore = sc::readJSON(std::cin); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error validating JSON: {}", e.what()); + return 1; + } + return 0; +} + +/** + * @brief Generates a C++ header from a device JSON specification (file or + * stdin). + * + * Parses generate-specific arguments from argVec starting at index i, reads a + * sc::Device from the specified JSON file or from stdin, and writes a header to + * the specified output file or to stdout. + * + * @param progName Program executable name (used for usage/help output). + * @param argVec Full command-line argument vector. + * @param i Index in argVec of the first generate sub-command argument. + * @return int 0 on success, 1 on error. + */ +auto executeGenerateCommand(const std::string& progName, + const std::vector& argVec, + const size_t i) -> int { + GenerateArguments generateArgs; + // parse the rest of the command line arguments for the generate command + try { + generateArgs = parseGenerateArguments(argVec, i); + } catch (const std::exception& e) { + SPDLOG_ERROR("Error parsing generate arguments: {}", e.what()); + printGenerateUsage(progName); + return 1; + } + // if the help flag is set, print the 'generate' usage information and exit + if (generateArgs.help) { + printGenerateUsage(progName); + return 0; + } + // generate the header file from the JSON specification + try { + sc::Device device; + // read the JSON file or the JSON string from stdin + if (generateArgs.jsonFile.has_value()) { + device = sc::readJSON(generateArgs.jsonFile.value()); + } else { + device = sc::readJSON(std::cin); + } + // write the header file to the output file or stdout + if (generateArgs.outputFile.has_value()) { + sc::writeHeader(device, generateArgs.outputFile.value()); + } else { + sc::writeHeader(device, std::cout); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error generating header file: {}", e.what()); + return 1; + } + return 0; +} +} // namespace + +/** + * @brief Parses command-line arguments, dispatches the selected subcommand + * (schema, validate, generate), and performs the requested operation. + * + * The function handles global flags (help, version), prints usage/version + * information when requested, and forwards remaining arguments to the + * appropriate subcommand executor which performs IO and error handling. + * + * @param argc Number of command-line arguments. + * @param argv Array of command-line argument strings. + * @return int Exit code: `0` on success, `1` on error. + */ +int main(int argc, char* argv[]) { + std::vector argVec; + std::pair parsedArgs; + // `main` functions should not throw exceptions. Apparently, the + // initialization of a vector can throw exceptions, so we catch them here. + try { + argVec.reserve(argc); + for (const auto& arg : std::span(argv, argc)) { + argVec.emplace_back(arg); + } + } catch (std::exception& e) { + SPDLOG_ERROR("Error parsing arguments into vector: {}", e.what()); + return 1; + } + // parse the command line arguments up to the first sub-command + try { + parsedArgs = parseArguments(argVec); + } catch (const std::exception& e) { + SPDLOG_ERROR("Error parsing arguments: {}", e.what()); + printUsage(argVec.empty() ? "mqt-core-sc-device-gen" : argVec.front()); + return 1; + } + // unpack the parsed arguments and the index of the first sub-command here + // because structured bindings only work with fresh variables + const auto& [args, i] = parsedArgs; + // print help or version information if requested + if (args.help) { + printUsage(args.programName); + return 0; + } + // if the version flag is set, print the version information and exit + if (args.version) { + printVersion(); + return 0; + } + // if no command is specified, print the usage information + if (!args.command.has_value()) { + printUsage(args.programName); + return 1; + } + switch (*args.command) { + case Command::Schema: + return executeSchemaCommand(args.programName, argVec, i); + case Command::Validate: + return executeValidateCommand(args.programName, argVec, i); + case Command::Generate: + return executeGenerateCommand(args.programName, argVec, i); + } + // LCOV_EXCL_START +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif + // LCOV_EXCL_STOP +} diff --git a/src/qdmi/sc/CMakeLists.txt b/src/qdmi/sc/CMakeLists.txt new file mode 100644 index 0000000000..f83c70bbac --- /dev/null +++ b/src/qdmi/sc/CMakeLists.txt @@ -0,0 +1,206 @@ +# 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 + +# Set target name +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-gen) + +# If the target is not already defined +if(NOT TARGET ${TARGET_NAME}) + + # Add library for device generation + # + # Note: We use a static library here to avoid issues with RPATH and finding the executable during + # the build process in Python builds + add_library(${TARGET_NAME} STATIC) + add_library(MQT::CoreQDMIScDeviceGen ALIAS ${TARGET_NAME}) + + # add sources to target + target_sources(${TARGET_NAME} PRIVATE Generator.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/sc/Generator.hpp) + + # Link nlohmann_json, spdlog + target_link_libraries( + ${TARGET_NAME} + PUBLIC nlohmann_json::nlohmann_json + PRIVATE spdlog::spdlog MQT::ProjectOptions MQT::ProjectWarnings) + + # set versioning information + set_target_properties( + ${TARGET_NAME} + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + EXPORT_NAME CoreScDeviceGen) + + # set c++ standard + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) + + # add to list of MQT core targets + set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME}) + + # Make version available + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_VERSION="${MQT_CORE_VERSION}") +endif() + +# Set target name +set(TARGET_NAME mqt-core-qdmi-sc-device-generator) + +# If the target is not already defined +if(NOT TARGET ${TARGET_NAME}) + # Add executable for device generation + add_executable(${TARGET_NAME}) + + # add sources to target + target_sources(${TARGET_NAME} PRIVATE App.cpp) + + # Link Generator library + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQDMIScDeviceGen spdlog::spdlog) + + # set versioning information + set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} EXPORT_NAME + CoreScDeviceGen) + # place in the bin directory + set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/bin") + + # set c++ standard + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) + + # Make version available + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_VERSION="${MQT_CORE_VERSION}") + + # Create an alias for the target + add_executable(MQT::CoreQDMIScDeviceGenerator ALIAS ${TARGET_NAME}) +endif() + +# Set target name +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device) + +# Set prefix for QDMI +set(QDMI_PREFIX "MQT_SC") + +# If the target is not already defined +if(NOT TARGET ${TARGET_NAME}) + + # Set paths + set(JSON_FILE ${PROJECT_SOURCE_DIR}/json/sc/device.json) + set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/sc/DeviceMemberInitializers.hpp) + + # Create include directory + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/sc) + + # Generate definitions for device + add_custom_command( + OUTPUT ${DEVICE_HDR} + COMMAND MQT::CoreQDMIScDeviceGenerator ARGS generate --output ${DEVICE_HDR} ${JSON_FILE} + DEPENDS ${JSON_FILE} MQT::CoreQDMIScDeviceGenerator + COMMENT "Generating C++ header from ${JSON_FILE}") + add_custom_target(generate_qdmi_sc_device_header DEPENDS ${DEVICE_HDR}) + + # Add library + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMIScDevice) + add_dependencies(${TARGET_NAME} generate_qdmi_sc_device_header) + + # Generate prefixed QDMI headers + generate_prefixed_qdmi_headers(${QDMI_PREFIX}) + file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_sc_qdmi/**.h) + + # add sources to target + target_sources(${TARGET_NAME} PRIVATE Device.cpp) + + # add headers using file sets + target_sources( + ${TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/include + FILES + ${DEVICE_HDR} + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/sc/Device.hpp + ${QDMI_HDRS}) + + # add link libraries + target_link_libraries( + ${TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings 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} + PARENT_SCOPE) + + # Make QDMI and MQT Core Version available + target_compile_definitions(${TARGET_NAME} PRIVATE QDMI_VERSION="${QDMI_VERSION}" + MQT_CORE_VERSION="${MQT_CORE_VERSION}") + + # Generate additional alias for the target required for generate_device_defs_executable function + # in the tests + add_library(qdmi::mqt_sc_device ALIAS ${TARGET_NAME}) + + # Do not build dynamic SC device on Windows because it cannot be used anyways in the current setup + if(NOT WIN32) + set(DYN_TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-dyn) + if(NOT TARGET ${DYN_TARGET_NAME}) + # Set prefix for QDMI + set(QDMI_PREFIX "MQT_SC_DYN") + # Generate prefixed QDMI headers + generate_prefixed_qdmi_headers(${QDMI_PREFIX}) + file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_sc_dyn_qdmi/**.hpp) + # Add dynamic library target + add_library(${DYN_TARGET_NAME} SHARED) + add_dependencies(${DYN_TARGET_NAME} ${TARGET_NAME}) + # add sources to target + target_sources(${DYN_TARGET_NAME} PRIVATE DynDevice.cpp) + # add headers using file sets + target_sources( + ${DYN_TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/include + FILES + ${DEVICE_HDR} + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/sc/Device.hpp + ${QDMI_HDRS}) + # add link libraries + target_link_libraries( + ${DYN_TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE ${TARGET_NAME} MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + # set c++ standard + target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) + # set versioning information + set_target_properties( + ${DYN_TARGET_NAME} + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + EXPORT_NAME CoreQDMIScDeviceDyn) + add_library(MQT::CoreQDMIScDeviceDyn ALIAS ${DYN_TARGET_NAME}) + endif() + endif() +endif() diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp new file mode 100644 index 0000000000..3b248e2263 --- /dev/null +++ b/src/qdmi/sc/Device.cpp @@ -0,0 +1,617 @@ +/* + * 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 + * @brief The MQT QDMI device implementation for superconducting devices. + */ + +#include "qdmi/sc/Device.hpp" + +#include "mqt_sc_qdmi/device.h" +#include "qdmi/sc/DeviceMemberInitializers.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; \ + } \ + } +// NOLINTEND(bugprone-macro-parentheses) + +namespace qdmi::sc { +std::atomic Device::instance = nullptr; + +Device::Device() { + // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) + INITIALIZE_NAME(name_); + INITIALIZE_QUBITSNUM(qubitsNum_); + // NOLINTEND(cppcoreguidelines-prefer-member-initializer) + // NOLINTNEXTLINE(misc-const-correctness) + INITIALIZE_SITES(sites_); + INITIALIZE_COUPLINGMAP(couplingMap_); + INITIALIZE_OPERATIONS(operations_); +} +Device::~Device() { + // Explicitly clear sessions before destruction to avoid spurious segfaults + sessions_.clear(); +} +void Device::initialize() { + // NOLINTNEXTLINE(misc-const-correctness) + Device* expected = nullptr; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* newInstance = new Device(); + if (!instance.compare_exchange_strong(expected, newInstance)) { + // Another thread won the race, so delete the instance we created. + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + delete newInstance; + } +} +void Device::finalize() { + // 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; +} +auto Device::get() -> Device& { + auto* loadedInstance = instance.load(); + assert(loadedInstance != nullptr && + "Device not initialized. Call `initialize()` first."); + return *loadedInstance; +} +auto Device::sessionAlloc(MQT_SC_QDMI_Device_Session* session) -> int { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto uniqueSession = std::make_unique(); + const auto& it = + sessions_.emplace(uniqueSession.get(), std::move(uniqueSession)).first; + // get the key, i.e., the raw pointer to the session from the map iterator + *session = it->first; + return QDMI_SUCCESS; +} +auto Device::sessionFree(MQT_SC_QDMI_Device_Session session) -> void { + if (session != nullptr) { + if (const auto& it = sessions_.find(session); it != sessions_.end()) { + sessions_.erase(it); + } + } +} +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, + value, sizeRet) + // NOLINTNEXTLINE(misc-include-cleaner) + ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_VERSION, MQT_CORE_VERSION, prop, + size, value, sizeRet) + // NOLINTNEXTLINE(misc-include-cleaner) + ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_LIBRARYVERSION, QDMI_VERSION, prop, + size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_STATUS, QDMI_Device_Status, + QDMI_DEVICE_STATUS_IDLE, prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_QUBITSNUM, size_t, qubitsNum_, + prop, size, value, sizeRet) + // This device never needs calibration + ADD_SINGLE_VALUE_PROPERTY(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, size_t, 0, + prop, size, value, sizeRet) + // This device does not support pulse-level control + ADD_SINGLE_VALUE_PROPERTY( + QDMI_DEVICE_PROPERTY_PULSESUPPORT, QDMI_Device_Pulse_Support_Level, + QDMI_DEVICE_PULSE_SUPPORT_LEVEL_NONE, prop, size, value, sizeRet) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_SITES, MQT_SC_QDMI_Site, sites_, prop, + size, value, sizeRet) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_COUPLINGMAP, MQT_SC_QDMI_Site, + couplingMap_, prop, size, value, sizeRet) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_OPERATIONS, MQT_SC_QDMI_Operation, + operations_, prop, size, value, sizeRet) + if (prop == (QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS)) { + if (value != nullptr && size > 0) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (sizeRet != nullptr) { + *sizeRet = 0; + } + return QDMI_SUCCESS; + } + return QDMI_ERROR_NOTSUPPORTED; +} +} // namespace qdmi::sc + +auto MQT_SC_QDMI_Device_Session_impl_d::init() -> int { + if (status_ != Status::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + status_ = Status::INITIALIZED; + return QDMI_SUCCESS; +} +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status_ != Status::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + return QDMI_ERROR_NOTSUPPORTED; +} +auto MQT_SC_QDMI_Device_Session_impl_d::createDeviceJob( + // NOLINTNEXTLINE(readability-non-const-parameter) + MQT_SC_QDMI_Device_Job* job) -> int { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status_ != Status::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + auto uniqueJob = std::make_unique(this); + *job = jobs_.emplace(uniqueJob.get(), std::move(uniqueJob)).first->first; + return QDMI_SUCCESS; +} +auto MQT_SC_QDMI_Device_Session_impl_d::freeDeviceJob( + MQT_SC_QDMI_Device_Job job) -> void { + if (job != nullptr) { + jobs_.erase(job); + } +} +auto MQT_SC_QDMI_Device_Session_impl_d::queryDeviceProperty( + const QDMI_Device_Property prop, const size_t size, void* value, + size_t* sizeRet) const -> int { + if (status_ != Status::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + return qdmi::sc::Device::get().queryProperty(prop, size, value, sizeRet); +} +auto MQT_SC_QDMI_Device_Session_impl_d::querySiteProperty( + MQT_SC_QDMI_Site site, const QDMI_Site_Property prop, const size_t size, + void* value, size_t* sizeRet) const -> int { + if (site == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status_ != Status::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + return site->queryProperty(prop, size, value, sizeRet); +} +auto MQT_SC_QDMI_Device_Session_impl_d::queryOperationProperty( + MQT_SC_QDMI_Operation operation, const size_t numSites, + const MQT_SC_QDMI_Site* sites, const size_t numParams, const double* params, + const QDMI_Operation_Property prop, const size_t size, void* value, + size_t* sizeRet) const -> int { + if (operation == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status_ != Status::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + return operation->queryProperty(numSites, sites, numParams, params, prop, + size, value, sizeRet); +} +auto MQT_SC_QDMI_Device_Job_impl_d::free() -> void { + session_->freeDeviceJob(this); +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +auto MQT_SC_QDMI_Device_Job_impl_d::submit() -> int { + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +auto MQT_SC_QDMI_Device_Job_impl_d::cancel() -> int { + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static,readability-non-const-parameter) +auto MQT_SC_QDMI_Device_Job_impl_d::check(QDMI_Job_Status* status) -> int { + if (status == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +auto MQT_SC_QDMI_Device_Job_impl_d::wait([[maybe_unused]] const size_t timeout) + -> int { + return QDMI_ERROR_NOTSUPPORTED; +} +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} +auto MQT_SC_QDMI_Site_impl_d::makeUniqueSite(const uint64_t id) + -> std::unique_ptr { + const MQT_SC_QDMI_Site_impl_d site(id); + return std::make_unique(site); +} +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) { + return QDMI_ERROR_INVALIDARGUMENT; + } + ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, + value, sizeRet) + return QDMI_ERROR_NOTSUPPORTED; +} +auto MQT_SC_QDMI_Operation_impl_d::sortSites() -> void { + std::visit( + [](auto& sites) { + using T = std::decay_t; + if constexpr (std::is_same_v>) { + // Single-qubit: sort flat list by pointer address + std::ranges::sort(sites, std::less{}); + } else if constexpr (std::is_same_v< + T, std::vector>>) { + // Two-qubit: normalize each pair (first < second) + // Use std::less for proper total order (pointer comparison with + // operator> invokes undefined behavior) + std::ranges::for_each(sites, [](auto& p) { + if (std::less{}(p.second, p.first)) { + std::swap(p.first, p.second); + } + }); + std::ranges::sort(sites); + } + // more cases go here if needed in the future + }, + supportedSites_); +} +MQT_SC_QDMI_Operation_impl_d::MQT_SC_QDMI_Operation_impl_d( + std::string name, const size_t numParameters, + const std::vector& sites) + : name_(std::move(name)), numParameters_(numParameters), numQubits_(1), + supportedSites_(sites) { + sortSites(); +} +MQT_SC_QDMI_Operation_impl_d::MQT_SC_QDMI_Operation_impl_d( + std::string name, const size_t numParameters, + const std::vector>& sites) + : name_(std::move(name)), numParameters_(numParameters), numQubits_(2), + supportedSites_(sites) { + sortSites(); +} +auto MQT_SC_QDMI_Operation_impl_d::makeUniqueSingleQubit( + std::string name, const size_t numParameters, + const std::vector& sites) + -> std::unique_ptr { + const MQT_SC_QDMI_Operation_impl_d op(std::move(name), numParameters, sites); + return std::make_unique(op); +} +auto MQT_SC_QDMI_Operation_impl_d::makeUniqueTwoQubit( + std::string name, const size_t numParameters, + const std::vector>& sites) + -> std::unique_ptr { + const MQT_SC_QDMI_Operation_impl_d op(std::move(name), numParameters, sites); + return std::make_unique(op); +} +auto MQT_SC_QDMI_Operation_impl_d::queryProperty( + const size_t numSites, const MQT_SC_QDMI_Site* sites, + const size_t numParams, const double* params, + const QDMI_Operation_Property prop, const size_t size, void* value, + size_t* sizeRet) const -> int { + if ((sites != nullptr && numSites == 0) || + (params != nullptr && numParams == 0) || + (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (sites != nullptr) { + // If numQubits_ == 1 or isZoned_ == true + if (numSites == 1) { + // If the (single) site is not supported, return with an error + const bool found = std::visit( + [sites](const S& storedSites) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v>) { + return std::ranges::binary_search(storedSites, *sites, + std::less{}); + } + return false; // Wrong variant type + }, + supportedSites_); + if (!found) { + return QDMI_ERROR_NOTSUPPORTED; + } + } else if (numSites == 2) { + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + const std::pair needle = std::less{}(sites[0], sites[1]) + ? std::make_pair(sites[0], sites[1]) + : std::make_pair(sites[1], sites[0]); + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + // if the pair of sites is not supported, return with an error + const bool found = std::visit( + [&needle](const S& storedSites) -> bool { + using T = std::decay_t; + if constexpr (std::is_same_v< + T, std::vector>>) { + return std::ranges::binary_search(storedSites, needle); + } + return false; // Wrong variant type + }, + supportedSites_); + if (!found) { + return QDMI_ERROR_NOTSUPPORTED; + } + } // this device does not support operations with more than two qubits + } + ADD_STRING_PROPERTY(QDMI_OPERATION_PROPERTY_NAME, name_.c_str(), prop, size, + value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_PARAMETERSNUM, size_t, + numParameters_, prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_QUBITSNUM, size_t, + numQubits_, prop, size, value, sizeRet) + if (prop == QDMI_OPERATION_PROPERTY_SITES) { + return std::visit( + [&](const S& storedSites) -> int { + using T = std::decay_t; + if constexpr (std::is_same_v>) { + // Single-qubit: return flat array + ADD_LIST_PROPERTY(QDMI_OPERATION_PROPERTY_SITES, MQT_SC_QDMI_Site, + storedSites, prop, size, value, sizeRet) + } else if constexpr (std::is_same_v< + T, + std::vector>>) { + // Ensure std::pair has standard layout and expected size + static_assert(std::is_standard_layout_v< + std::pair>); + static_assert( + sizeof(std::pair) == + 2 * sizeof(MQT_SC_QDMI_Site)); + // Two-qubit: reinterpret as flat array of sites using std::span + // std::pair has standard layout, so the memory layout of + // vector> is equivalent to Site[2*N] + const auto flatView = std::span( + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + reinterpret_cast(storedSites.data()), + storedSites.size() * 2); + ADD_LIST_PROPERTY(QDMI_OPERATION_PROPERTY_SITES, MQT_SC_QDMI_Site, + flatView, prop, size, value, sizeRet) + } + // more cases go here if needed in the future + return QDMI_ERROR_NOTSUPPORTED; + }, + supportedSites_); + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_SC_QDMI_device_initialize() { + qdmi::sc::Device::initialize(); + return QDMI_SUCCESS; +} + +int MQT_SC_QDMI_device_finalize() { + qdmi::sc::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); +} + +int MQT_SC_QDMI_device_session_init(MQT_SC_QDMI_Device_Session session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->init(); +} + +void MQT_SC_QDMI_device_session_free(MQT_SC_QDMI_Device_Session session) { + qdmi::sc::Device::get().sessionFree(session); +} + +int MQT_SC_QDMI_device_session_set_parameter( + MQT_SC_QDMI_Device_Session session, QDMI_Device_Session_Parameter param, + const size_t size, const void* value) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->setParameter(param, size, value); +} + +int MQT_SC_QDMI_device_session_create_device_job( + MQT_SC_QDMI_Device_Session session, MQT_SC_QDMI_Device_Job* job) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->createDeviceJob(job); +} + +void MQT_SC_QDMI_device_job_free(MQT_SC_QDMI_Device_Job job) { job->free(); } + +int MQT_SC_QDMI_device_job_set_parameter(MQT_SC_QDMI_Device_Job job, + const QDMI_Device_Job_Parameter param, + const size_t size, const void* value) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->setParameter(param, size, value); +} + +int MQT_SC_QDMI_device_job_query_property(MQT_SC_QDMI_Device_Job job, + const QDMI_Device_Job_Property prop, + const size_t size, void* value, + size_t* sizeRet) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->queryProperty(prop, size, value, sizeRet); +} + +int MQT_SC_QDMI_device_job_submit(MQT_SC_QDMI_Device_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->submit(); +} + +int MQT_SC_QDMI_device_job_cancel(MQT_SC_QDMI_Device_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->cancel(); +} + +int MQT_SC_QDMI_device_job_check(MQT_SC_QDMI_Device_Job job, + QDMI_Job_Status* status) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->check(status); +} + +int MQT_SC_QDMI_device_job_wait(MQT_SC_QDMI_Device_Job job, + const size_t timeout) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->wait(timeout); +} + +int MQT_SC_QDMI_device_job_get_results(MQT_SC_QDMI_Device_Job job, + QDMI_Job_Result result, + const size_t size, void* data, + size_t* sizeRet) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->getResults(result, size, data, sizeRet); +} + +int MQT_SC_QDMI_device_session_query_device_property( + MQT_SC_QDMI_Device_Session session, const QDMI_Device_Property prop, + const size_t size, void* value, size_t* sizeRet) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->queryDeviceProperty(prop, size, value, sizeRet); +} + +int MQT_SC_QDMI_device_session_query_site_property( + MQT_SC_QDMI_Device_Session session, MQT_SC_QDMI_Site site, + const QDMI_Site_Property prop, const size_t size, void* value, + size_t* sizeRet) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->querySiteProperty(site, prop, size, value, sizeRet); +} + +int MQT_SC_QDMI_device_session_query_operation_property( + MQT_SC_QDMI_Device_Session session, MQT_SC_QDMI_Operation operation, + const size_t numSites, const MQT_SC_QDMI_Site* sites, + const size_t numParams, const double* params, + const QDMI_Operation_Property prop, const size_t size, void* value, + size_t* sizeRet) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->queryOperationProperty(operation, numSites, sites, numParams, + params, prop, size, value, sizeRet); +} diff --git a/src/qdmi/sc/DynDevice.cpp b/src/qdmi/sc/DynDevice.cpp new file mode 100644 index 0000000000..91552f9c44 --- /dev/null +++ b/src/qdmi/sc/DynDevice.cpp @@ -0,0 +1,155 @@ +/* + * 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 This file is a thin wrapper around MQT's Superconducting QDMI Device + * with another prefix. + */ + +#include "mqt_sc_dyn_qdmi/device.h" +#include "mqt_sc_qdmi/device.h" + +#include +#include + +int MQT_SC_DYN_QDMI_device_initialize() { + return MQT_SC_QDMI_device_initialize(); +} + +int MQT_SC_DYN_QDMI_device_finalize() { return MQT_SC_QDMI_device_finalize(); } + +int MQT_SC_DYN_QDMI_device_session_alloc( + MQT_SC_DYN_QDMI_Device_Session* session) { + return MQT_SC_QDMI_device_session_alloc( + reinterpret_cast(session)); +} + +int MQT_SC_DYN_QDMI_device_session_init( + MQT_SC_DYN_QDMI_Device_Session session) { + return MQT_SC_QDMI_device_session_init( + reinterpret_cast(session)); +} + +void MQT_SC_DYN_QDMI_device_session_free( + MQT_SC_DYN_QDMI_Device_Session session) { + MQT_SC_QDMI_device_session_free( + reinterpret_cast(session)); +} + +int MQT_SC_DYN_QDMI_device_session_set_parameter( + MQT_SC_DYN_QDMI_Device_Session session, QDMI_Device_Session_Parameter param, + const size_t size, const void* value) { + return MQT_SC_QDMI_device_session_set_parameter( + reinterpret_cast(session), param, size, + value); +} + +int MQT_SC_DYN_QDMI_device_session_create_device_job( + MQT_SC_DYN_QDMI_Device_Session session, MQT_SC_DYN_QDMI_Device_Job* job) { + return MQT_SC_QDMI_device_session_create_device_job( + reinterpret_cast(session), + reinterpret_cast(job)); +} + +void MQT_SC_DYN_QDMI_device_job_free(MQT_SC_DYN_QDMI_Device_Job job) { + MQT_SC_QDMI_device_job_free(reinterpret_cast(job)); +} + +int MQT_SC_DYN_QDMI_device_job_set_parameter( + MQT_SC_DYN_QDMI_Device_Job job, const QDMI_Device_Job_Parameter param, + const size_t size, const void* value) { + return MQT_SC_QDMI_device_job_set_parameter( + reinterpret_cast(job), param, size, value); +} + +int MQT_SC_DYN_QDMI_device_job_query_property( + MQT_SC_DYN_QDMI_Device_Job job, const QDMI_Device_Job_Property prop, + const size_t size, void* value, size_t* sizeRet) { + return MQT_SC_QDMI_device_job_query_property( + reinterpret_cast(job), prop, size, value, + sizeRet); +} + +int MQT_SC_DYN_QDMI_device_job_submit(MQT_SC_DYN_QDMI_Device_Job job) { + return MQT_SC_QDMI_device_job_submit( + reinterpret_cast(job)); +} + +int MQT_SC_DYN_QDMI_device_job_cancel(MQT_SC_DYN_QDMI_Device_Job job) { + return MQT_SC_QDMI_device_job_cancel( + reinterpret_cast(job)); +} + +int MQT_SC_DYN_QDMI_device_job_check(MQT_SC_DYN_QDMI_Device_Job job, + QDMI_Job_Status* status) { + return MQT_SC_QDMI_device_job_check( + reinterpret_cast(job), status); +} + +int MQT_SC_DYN_QDMI_device_job_wait(MQT_SC_DYN_QDMI_Device_Job job, + const size_t timeout) { + return MQT_SC_QDMI_device_job_wait( + reinterpret_cast(job), timeout); +} + +int MQT_SC_DYN_QDMI_device_job_get_results(MQT_SC_DYN_QDMI_Device_Job job, + QDMI_Job_Result result, + const size_t size, void* data, + size_t* sizeRet) { + return MQT_SC_QDMI_device_job_get_results( + reinterpret_cast(job), result, size, data, + sizeRet); +} + +int MQT_SC_DYN_QDMI_device_session_query_device_property( + MQT_SC_DYN_QDMI_Device_Session session, const QDMI_Device_Property prop, + const size_t size, void* value, size_t* sizeRet) { + const auto result = MQT_SC_QDMI_device_session_query_device_property( + reinterpret_cast(session), prop, size, value, + sizeRet); + // let the proper device implementation do the error handling and check for + // the name property afterward + if (result == QDMI_SUCCESS && prop == QDMI_DEVICE_PROPERTY_NAME) { + if (value != nullptr) { + if (size < 27) { + return QDMI_ERROR_INVALIDARGUMENT; + } + strncpy(static_cast(value), "MQT SC Dynamic QDMI Device", size); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + static_cast(value)[size - 1] = '\0'; + } + if ((sizeRet) != nullptr) { + *sizeRet = 27; + } + } + return result; +} + +int MQT_SC_DYN_QDMI_device_session_query_site_property( + MQT_SC_DYN_QDMI_Device_Session session, MQT_SC_DYN_QDMI_Site site, + const QDMI_Site_Property prop, const size_t size, void* value, + size_t* sizeRet) { + return MQT_SC_QDMI_device_session_query_site_property( + reinterpret_cast(session), + reinterpret_cast(site), prop, size, value, sizeRet); +} + +int MQT_SC_DYN_QDMI_device_session_query_operation_property( + MQT_SC_DYN_QDMI_Device_Session session, MQT_SC_DYN_QDMI_Operation operation, + const size_t numSites, const MQT_SC_DYN_QDMI_Site* sites, + const size_t numParams, const double* params, + const QDMI_Operation_Property prop, const size_t size, void* value, + size_t* sizeRet) { + return MQT_SC_QDMI_device_session_query_operation_property( + reinterpret_cast(session), + reinterpret_cast(operation), numSites, + reinterpret_cast(sites), numParams, params, prop, + size, value, sizeRet); +} diff --git a/src/qdmi/sc/Generator.cpp b/src/qdmi/sc/Generator.cpp new file mode 100644 index 0000000000..09c83d5fb8 --- /dev/null +++ b/src/qdmi/sc/Generator.cpp @@ -0,0 +1,277 @@ +/* + * 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 + * @brief The MQT QDMI device generator for superconducting devices. + */ + +#include "qdmi/sc/Generator.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sc { +namespace { +/** + * @brief Ensure array fields of a Device contain default entries. + * + * Ensures arrays that must not be empty have default elements; in particular, + * appends a default (empty) coupling to device.couplings. + * + * @param device Device instance whose array fields will be populated. + */ +auto populateArrayFields(Device& device) -> void { + device.couplings.emplace_back(); + device.operations.emplace_back(); +} + +/** + * @brief Writes a C preprocessor macro that initializes a variable with the + * device's name. + * + * The macro emitted is `#define INITIALIZE_NAME(var) var = ""`. + */ +auto writeName(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_NAME(var) var = \"" << device.name << "\"\n"; +} + +/** + * @brief Emits a C macro that initializes the device's qubit count. + * + * @param device Device whose `numQubits` value will be embedded in the macro. + * @param os Output stream to which the macro definition is written. + */ +auto writeQubitsNum(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_QUBITSNUM(var) var = " << device.numQubits + << "ULL\n"; +} + +/** + * @brief Generates a C preprocessor macro that initializes the device's qubit + * sites and coupling map. + * + * Writes a macro `INITIALIZE_SITES(var)` which clears `var`, appends + * `numQubits` unique sites (by index), and constructs a `_couplings` vector + * reserved to the number of couplings and populated with pairs of site pointers + * corresponding to `device.couplings`. + * + * @param device Device containing `numQubits` and `couplings`. + * @param os Output stream to which the macro definition is written. + */ +auto writeSites(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_SITES(var) var.clear()"; + for (uint64_t id = 0; id < device.numQubits; ++id) { + os << ";\\\n " + "var.emplace_back(MQT_SC_QDMI_Site_impl_d::makeUniqueSite(" + << id << "ULL))"; + } + os << ";\\\n std::vector _singleQubitSites"; + os << ";\\\n _singleQubitSites.reserve(var.size())"; + os << ";\\\n std::ranges::transform(var, " + "std::back_inserter(_singleQubitSites), [](const " + "std::unique_ptr& site) { return site.get(); " + "})"; + os << ";\\\n std::vector> " + "_couplings"; + os << ";\\\n _couplings.reserve(" << device.couplings.size() << ")"; + for (const auto& [i1, i2] : device.couplings) { + os << ";\\\n " + "_couplings.emplace_back(var.at(" + << i1 << ").get(), var.at(" << i2 << ").get())"; + } + os << "\n"; +} + +/** + * @brief Writes the operations from the device object. + * @param device is the device object containing the operations. + * @param os is the output stream to write the operations to. + */ +auto writeOperations(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_OPERATIONS(var) var.clear()"; + for (const auto& operation : device.operations) { + if (operation.numQubits == 1) { + os << ";\\\n" + " " + "var.emplace_back(MQT_SC_QDMI_Operation_impl_d::" + "makeUniqueSingleQubit(\"" + << operation.name << "\", " << operation.numParameters + << ", _singleQubitSites))"; + } else if (operation.numQubits == 2) { + os << ";\\\n" + " " + "var.emplace_back(MQT_SC_QDMI_Operation_impl_d::" + "makeUniqueTwoQubit(\"" + << operation.name << "\", " << operation.numParameters + << ", _couplings))"; + } else { + std::ostringstream ss; + ss << "Got operation with " << operation.numQubits << " qubits but only " + << "single- and two-qubit operations are supported."; + throw std::runtime_error(ss.str()); + } + } + os << "\n"; +} + +/** + * @brief Emits a macro to initialize the device coupling map. + * + * Writes the C preprocessor macro `INITIALIZE_COUPLINGMAP(var)` which assigns + * `var = _couplings`. + * + * @note This macro depends on the `_couplings` variable created by + * the INITIALIZE_SITES macro from writeSites(). The macro + * INITIALIZE_SITES must be invoked before INITIALIZE_COUPLINGMAP. + * + * @param os Output stream to write the macro definition to. + */ +auto writeCouplingMap(const Device& /* unused */, std::ostream& os) -> void { + os << "#define INITIALIZE_COUPLINGMAP(var) var = _couplings\n"; +} +} // namespace + +auto writeJSONSchema(std::ostream& os) -> void { + // Create a default device configuration + Device device; + + // Fill each array field with default values + populateArrayFields(device); + + // Convert the device configuration to a JSON object + // NOLINTNEXTLINE(misc-include-cleaner) + const nlohmann::json json = device; + + // Write to the output stream + os << json; +} + +/** + * @brief Write a default device JSON schema to the specified file path. + * + * Opens the file at `path` for writing and writes a JSON template representing + * a default Device configuration. The function closes the file on completion. + * + * @param path Filesystem path where the JSON template will be written. + * @throws std::runtime_error If the file at `path` cannot be opened for + * writing. + */ +auto writeJSONSchema(const std::string& path) -> void { + // Write to a file + std::ofstream ofs(path); + if (!ofs.good()) { + std::stringstream ss; + ss << "Failed to open file for writing: " << path; + throw std::runtime_error(ss.str()); + } + writeJSONSchema(ofs); + ofs.close(); + SPDLOG_INFO("JSON template written to {}", path); +} + +/** + * @brief Parses a Device configuration from an input stream containing JSON. + * + * Reads JSON from the provided input stream and converts it into a Device. + * + * @param is Input stream that supplies the JSON representation of the Device. + * @return Device constructed from the parsed JSON. + * @throws std::runtime_error If JSON parsing fails; the exception message + * contains parser error details. + */ +[[nodiscard]] auto readJSON(std::istream& is) -> Device { + // Read the device configuration from the input stream + nlohmann::json json; + try { + is >> json; + // NOLINTNEXTLINE(misc-include-cleaner) + } catch (const nlohmann::detail::parse_error& e) { + std::stringstream ss; + ss << "Failed to parse JSON string: " << e.what(); + throw std::runtime_error(ss.str()); + } + return json; +} + +/** + * @brief Read a Device configuration from a JSON file. + * + * @param path Filesystem path to the JSON file containing the device + * configuration. + * @return Device parsed from the JSON file. + * @throws std::runtime_error If the file cannot be opened or if parsing the + * JSON fails. + */ +[[nodiscard]] auto readJSON(const std::string& path) -> Device { + // Read the device configuration from a JSON file + std::ifstream ifs(path); + if (!ifs.good()) { + throw std::runtime_error("Failed to open JSON file: " + std::string(path)); + } + auto device = readJSON(ifs); + ifs.close(); + return device; +} + +/** + * @brief Writes a C++ header snippet that initializes the provided Device as C + * macros. + * + * Writes macros defining the device name, qubit count, site initializers, and + * coupling map to the given output stream; the header begins with a pragma once + * guard. + * + * @param device Device to serialize into header macros. + * @param os Output stream to write the header content to. + */ +auto writeHeader(const Device& device, std::ostream& os) -> void { + os << "#pragma once\n\n" + << "#include \n" + << "#include \n" + << "#include \n" + << "#include \n" + << "#include \n\n"; + writeName(device, os); + writeQubitsNum(device, os); + writeSites(device, os); + writeCouplingMap(device, os); + writeOperations(device, os); +} + +/** + * @brief Write a C++ header file that defines macros to initialize the given + * Device. + * + * @param device Device to serialize into initialization macros (name, qubit + * sites, coupling map). + * @param path Filesystem path where the header will be created/overwritten. + * @throws std::runtime_error if the file at `path` cannot be opened for + * writing. + */ +auto writeHeader(const Device& device, const std::string& path) -> void { + std::ofstream ofs(path); + if (!ofs.good()) { + std::stringstream ss; + ss << "Failed to open header file for writing: " << path; + throw std::runtime_error(ss.str()); + } + writeHeader(device, ofs); + ofs.close(); + SPDLOG_INFO("Header file written to {}", path); +} +} // namespace sc diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 3a4ac9cad8..8ab717e673 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -33,16 +33,16 @@ class DeviceTest : public testing::TestWithParam { class SiteTest : public DeviceTest { protected: - FoMaC::Device::Site site; + std::vector sites; - SiteTest() : site(device.getSites().front()) {} + void SetUp() override { sites = device.getSites(); } }; class OperationTest : public DeviceTest { protected: - FoMaC::Device::Operation operation; + std::vector operations; - OperationTest() : operation(device.getOperations().front()) {} + void SetUp() override { operations = device.getOperations(); } }; class DDSimulatorDeviceTest : public testing::Test { @@ -230,10 +230,6 @@ TEST_P(DeviceTest, Sites) { EXPECT_NO_THROW(EXPECT_FALSE(device.getSites().empty())); } -TEST_P(DeviceTest, Operations) { - EXPECT_NO_THROW(EXPECT_FALSE(device.getOperations().empty())); -} - TEST_P(DeviceTest, CouplingMap) { EXPECT_NO_THROW(std::ignore = device.getCouplingMap()); } @@ -266,104 +262,232 @@ TEST_P(DeviceTest, SupportedProgramFormats) { EXPECT_NO_THROW(std::ignore = device.getSupportedProgramFormats()); } -TEST_P(SiteTest, Index) { EXPECT_NO_THROW(std::ignore = site.getIndex()); } +TEST_P(SiteTest, Index) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getIndex()); + } +} -TEST_P(SiteTest, T1) { EXPECT_NO_THROW(std::ignore = site.getT1()); } +TEST_P(SiteTest, T1) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getT1()); + } +} -TEST_P(SiteTest, T2) { EXPECT_NO_THROW(std::ignore = site.getT2()); } +TEST_P(SiteTest, T2) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getT2()); + } +} -TEST_P(SiteTest, Name) { EXPECT_NO_THROW(std::ignore = site.getName()); } +TEST_P(SiteTest, Name) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getName()); + } +} TEST_P(SiteTest, XCoordinate) { - EXPECT_NO_THROW(std::ignore = site.getXCoordinate()); + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getXCoordinate()); + } } TEST_P(SiteTest, YCoordinate) { - EXPECT_NO_THROW(std::ignore = site.getYCoordinate()); + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getYCoordinate()); + } } TEST_P(SiteTest, ZCoordinate) { - EXPECT_NO_THROW(std::ignore = site.getZCoordinate()); + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getZCoordinate()); + } } -TEST_P(SiteTest, IsZone) { EXPECT_NO_THROW(std::ignore = site.isZone()); } +TEST_P(SiteTest, IsZone) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.isZone()); + } +} -TEST_P(SiteTest, XExtent) { EXPECT_NO_THROW(std::ignore = site.getXExtent()); } +TEST_P(SiteTest, XExtent) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getXExtent()); + } +} -TEST_P(SiteTest, YExtent) { EXPECT_NO_THROW(std::ignore = site.getYExtent()); } +TEST_P(SiteTest, YExtent) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getYExtent()); + } +} -TEST_P(SiteTest, ZExtent) { EXPECT_NO_THROW(std::ignore = site.getZExtent()); } +TEST_P(SiteTest, ZExtent) { + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getZExtent()); + } +} TEST_P(SiteTest, ModuleIndex) { - EXPECT_NO_THROW(std::ignore = site.getModuleIndex()); + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getModuleIndex()); + } } TEST_P(SiteTest, SubmoduleIndex) { - EXPECT_NO_THROW(std::ignore = site.getSubmoduleIndex()); + for (const auto& site : sites) { + EXPECT_NO_THROW(std::ignore = site.getSubmoduleIndex()); + } } TEST_P(OperationTest, Name) { - EXPECT_NO_THROW(EXPECT_FALSE(operation.getName().empty());); + for (const auto& operation : operations) { + EXPECT_NO_THROW(EXPECT_FALSE(operation.getName().empty())); + } } TEST_P(OperationTest, QubitsNum) { - EXPECT_NO_THROW(std::ignore = operation.getQubitsNum()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getQubitsNum()); + } } TEST_P(OperationTest, ParametersNum) { - EXPECT_NO_THROW(std::ignore = operation.getParametersNum()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getParametersNum()); + } } TEST_P(OperationTest, Duration) { - EXPECT_NO_THROW(std::ignore = operation.getDuration()); + for (const auto& operation : operations) { + const auto qubitsNum = operation.getQubitsNum(); + if (!qubitsNum.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getDuration()); + continue; + } + const auto numQubits = *qubitsNum; + if (numQubits == 1) { + const auto sites = operation.getSites(); + if (!sites.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getDuration()); + continue; + } + for (const auto& site : *sites) { + EXPECT_NO_THROW(std::ignore = operation.getDuration({site})); + } + continue; + } + + if (numQubits == 2) { + const auto sitePairs = operation.getSitePairs(); + if (!sitePairs.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getDuration()); + continue; + } + for (const auto& [site1, site2] : *sitePairs) { + EXPECT_NO_THROW(std::ignore = operation.getDuration({site1, site2})); + } + continue; + } + + EXPECT_NO_THROW(std::ignore = operation.getDuration()); + } } TEST_P(OperationTest, Fidelity) { - EXPECT_NO_THROW(std::ignore = operation.getFidelity()); + for (const auto& operation : operations) { + const auto qubitsNum = operation.getQubitsNum(); + if (!qubitsNum.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getFidelity()); + continue; + } + const auto numQubits = *qubitsNum; + if (numQubits == 1) { + const auto sites = operation.getSites(); + if (!sites.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getFidelity()); + continue; + } + for (const auto& site : *sites) { + EXPECT_NO_THROW(std::ignore = operation.getFidelity({site})); + } + continue; + } + + if (numQubits == 2) { + const auto sitePairs = operation.getSitePairs(); + if (!sitePairs.has_value()) { + EXPECT_NO_THROW(std::ignore = operation.getFidelity()); + continue; + } + for (const auto& [site1, site2] : *sitePairs) { + EXPECT_NO_THROW(std::ignore = operation.getFidelity({site1, site2})); + } + continue; + } + + EXPECT_NO_THROW(std::ignore = operation.getFidelity()); + } } TEST_P(OperationTest, InteractionRadius) { - EXPECT_NO_THROW(std::ignore = operation.getInteractionRadius()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getInteractionRadius()); + } } TEST_P(OperationTest, BlockingRadius) { - EXPECT_NO_THROW(std::ignore = operation.getBlockingRadius()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getBlockingRadius()); + } } TEST_P(OperationTest, IdlingFidelity) { - EXPECT_NO_THROW(std::ignore = operation.getIdlingFidelity()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getIdlingFidelity()); + } } -TEST_P(OperationTest, oned) { - EXPECT_NO_THROW(std::ignore = operation.isZoned()); +TEST_P(OperationTest, IsZoned) { + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.isZoned()); + } } TEST_P(OperationTest, Sites) { - EXPECT_NO_THROW(std::ignore = operation.getSites()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getSites()); + } } TEST_P(OperationTest, SitePairs) { - const auto sitePairs = operation.getSitePairs(); - const auto qubitsNum = operation.getQubitsNum(); - const auto isZonedOp = operation.isZoned(); + for (const auto& operation : operations) { + const auto sitePairs = operation.getSitePairs(); + const auto qubitsNum = operation.getQubitsNum(); + const auto isZonedOp = operation.isZoned(); + + if (!qubitsNum.has_value() || *qubitsNum != 2 || isZonedOp) { + EXPECT_FALSE(sitePairs.has_value()); + continue; + } - if (!qubitsNum.has_value() || *qubitsNum != 2 || isZonedOp) { - EXPECT_FALSE(sitePairs.has_value()); - } else { const auto sites = operation.getSites(); if (!sites.has_value() || sites->empty() || sites->size() % 2 != 0) { EXPECT_FALSE(sitePairs.has_value()); - } else { - EXPECT_TRUE(sitePairs.has_value()); - if (sitePairs.has_value()) { - EXPECT_EQ(sitePairs->size(), sites->size() / 2); - } + continue; + } + + EXPECT_TRUE(sitePairs.has_value()); + if (sitePairs.has_value()) { + EXPECT_EQ(sitePairs->size(), sites->size() / 2); } } } TEST_P(OperationTest, MeanShuttlingSpeed) { - EXPECT_NO_THROW(std::ignore = operation.getMeanShuttlingSpeed()); + for (const auto& operation : operations) { + EXPECT_NO_THROW(std::ignore = operation.getMeanShuttlingSpeed()); + } } TEST_P(DeviceTest, RegularSitesAndZones) { diff --git a/test/na/device/test_app.cpp b/test/na/device/test_app.cpp index 23493955e7..dae2b8cf4c 100644 --- a/test/na/device/test_app.cpp +++ b/test/na/device/test_app.cpp @@ -27,7 +27,7 @@ // NOLINTEND(misc-include-cleaner) #endif // _WIN32 -TEST(ExecutableTest, Version) { +TEST(NaExecutableTest, Version) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " --version"; @@ -55,7 +55,7 @@ TEST(ExecutableTest, Version) { ")\n"); } -TEST(ExecutableTest, MissingSubcommand) { +TEST(NaExecutableTest, MissingSubcommand) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH; @@ -77,7 +77,7 @@ TEST(ExecutableTest, MissingSubcommand) { EXPECT_NE(returnCode, 0); } -TEST(ExecutableTest, UnknownSubcommand) { +TEST(NaExecutableTest, UnknownSubcommand) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " unknown"; @@ -99,7 +99,7 @@ TEST(ExecutableTest, UnknownSubcommand) { EXPECT_NE(returnCode, 0); } -TEST(ExecutableTest, SchemaUnknownOption) { +TEST(NaExecutableTest, SchemaUnknownOption) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " schema --unknown-option"; @@ -121,7 +121,7 @@ TEST(ExecutableTest, SchemaUnknownOption) { EXPECT_NE(returnCode, 0); } -TEST(ExecutableTest, SchemaMissingFile) { +TEST(NaExecutableTest, SchemaMissingFile) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " schema --output"; @@ -143,7 +143,7 @@ TEST(ExecutableTest, SchemaMissingFile) { EXPECT_NE(returnCode, 0); } -TEST(ExecutableTest, ValidateInvalidJson) { +TEST(NaExecutableTest, ValidateInvalidJson) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " validate"; @@ -158,7 +158,7 @@ TEST(ExecutableTest, ValidateInvalidJson) { << returnCode; } -TEST(ExecutableTest, GenerateMissingFile) { +TEST(NaExecutableTest, GenerateMissingFile) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " generate --output"; @@ -180,7 +180,7 @@ TEST(ExecutableTest, GenerateMissingFile) { EXPECT_NE(returnCode, 0); } -TEST(ExecutableTest, Usage) { +TEST(NaExecutableTest, Usage) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " --help"; @@ -204,7 +204,7 @@ TEST(ExecutableTest, Usage) { EXPECT_FALSE(output.str().empty()); } -TEST(ExecutableTest, SchemaUsage) { +TEST(NaExecutableTest, SchemaUsage) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " schema --help"; @@ -228,7 +228,7 @@ TEST(ExecutableTest, SchemaUsage) { EXPECT_TRUE(output.str().rfind("Generates a JSON schema", 0) == 0); } -TEST(ExecutableTest, ValidateUsage) { +TEST(NaExecutableTest, ValidateUsage) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " validate --help"; @@ -252,7 +252,7 @@ TEST(ExecutableTest, ValidateUsage) { EXPECT_TRUE(output.str().rfind("Validates", 0) == 0); } -TEST(ExecutableTest, GenerateUsage) { +TEST(NaExecutableTest, GenerateUsage) { // Command to execute // NOLINTNEXTLINE(misc-include-cleaner) const std::string command = EXECUTABLE_PATH " generate --help"; @@ -276,7 +276,7 @@ TEST(ExecutableTest, GenerateUsage) { EXPECT_TRUE(output.str().rfind("Generates a header file", 0) == 0); } -TEST(ExecutableTest, RoundTrip) { +TEST(NaExecutableTest, RoundTrip) { std::string schema; // Capture the output of the schema command { @@ -319,7 +319,7 @@ TEST(ExecutableTest, RoundTrip) { } } -TEST(ExecutableTest, RoundTripFile) { +TEST(NaExecutableTest, RoundTripFile) { // Write schema to a file { // Command to execute diff --git a/test/na/device/test_device.cpp b/test/na/device/test_device.cpp index 490b1609e0..abc07fa292 100644 --- a/test/na/device/test_device.cpp +++ b/test/na/device/test_device.cpp @@ -96,7 +96,7 @@ struct PairHash { } } // namespace -class QDMISpecificationTest : public ::testing::Test { +class NaQDMISpecificationTest : public ::testing::Test { protected: MQT_NA_QDMI_Device_Session session = nullptr; @@ -123,12 +123,12 @@ class QDMISpecificationTest : public ::testing::Test { } }; -class QDMIJobSpecificationTest : public QDMISpecificationTest { +class NaQDMIJobSpecificationTest : public NaQDMISpecificationTest { protected: MQT_NA_QDMI_Device_Job job = nullptr; void SetUp() override { - QDMISpecificationTest::SetUp(); + NaQDMISpecificationTest::SetUp(); ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), QDMI_SUCCESS) << "Failed to create a device job."; @@ -139,22 +139,22 @@ class QDMIJobSpecificationTest : public QDMISpecificationTest { MQT_NA_QDMI_device_job_free(job); job = nullptr; } - QDMISpecificationTest::TearDown(); + NaQDMISpecificationTest::TearDown(); } }; -TEST_F(QDMISpecificationTest, SessionAlloc) { +TEST_F(NaQDMISpecificationTest, SessionAlloc) { EXPECT_EQ(MQT_NA_QDMI_device_session_alloc(nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMISpecificationTest, SessionInit) { +TEST_F(NaQDMISpecificationTest, SessionInit) { EXPECT_EQ(MQT_NA_QDMI_device_session_init(session), QDMI_ERROR_BADSTATE); EXPECT_EQ(MQT_NA_QDMI_device_session_init(nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMISpecificationTest, SessionSetParameter) { +TEST_F(NaQDMISpecificationTest, SessionSetParameter) { MQT_NA_QDMI_Device_Session uninitializedSession = nullptr; ASSERT_EQ(MQT_NA_QDMI_device_session_alloc(&uninitializedSession), QDMI_SUCCESS); @@ -172,7 +172,7 @@ TEST_F(QDMISpecificationTest, SessionSetParameter) { QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMISpecificationTest, JobCreate) { +TEST_F(NaQDMISpecificationTest, JobCreate) { MQT_NA_QDMI_Device_Session uninitializedSession = nullptr; MQT_NA_QDMI_Device_Job job = nullptr; ASSERT_EQ(MQT_NA_QDMI_device_session_alloc(&uninitializedSession), @@ -189,13 +189,13 @@ TEST_F(QDMISpecificationTest, JobCreate) { MQT_NA_QDMI_device_job_free(job); } -TEST_F(QDMISpecificationTest, JobSetParameter) { +TEST_F(NaQDMISpecificationTest, JobSetParameter) { EXPECT_EQ(MQT_NA_QDMI_device_job_set_parameter( nullptr, QDMI_DEVICE_JOB_PARAMETER_MAX, 0, nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobSetParameter) { +TEST_F(NaQDMIJobSpecificationTest, JobSetParameter) { QDMI_Program_Format value = QDMI_PROGRAM_FORMAT_QASM2; EXPECT_THAT(MQT_NA_QDMI_device_job_set_parameter( job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, @@ -206,13 +206,13 @@ TEST_F(QDMIJobSpecificationTest, JobSetParameter) { QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMISpecificationTest, JobQueryProperty) { +TEST_F(NaQDMISpecificationTest, JobQueryProperty) { EXPECT_EQ(MQT_NA_QDMI_device_job_query_property( nullptr, QDMI_DEVICE_JOB_PROPERTY_MAX, 0, nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobQueryProperty) { +TEST_F(NaQDMIJobSpecificationTest, JobQueryProperty) { EXPECT_THAT(MQT_NA_QDMI_device_job_query_property( job, QDMI_DEVICE_JOB_PROPERTY_ID, 0, nullptr, nullptr), testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); @@ -221,7 +221,7 @@ TEST_F(QDMIJobSpecificationTest, JobQueryProperty) { QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, QueryJobId) { +TEST_F(NaQDMIJobSpecificationTest, QueryJobId) { size_t size = 0; const auto status = MQT_NA_QDMI_device_job_query_property( job, QDMI_DEVICE_JOB_PROPERTY_ID, 0, nullptr, &size); @@ -236,54 +236,54 @@ TEST_F(QDMIJobSpecificationTest, QueryJobId) { testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, JobSubmit) { +TEST_F(NaQDMISpecificationTest, JobSubmit) { EXPECT_EQ(MQT_NA_QDMI_device_job_submit(nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobSubmit) { +TEST_F(NaQDMIJobSpecificationTest, JobSubmit) { const auto status = MQT_NA_QDMI_device_job_submit(job); ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, JobCancel) { +TEST_F(NaQDMISpecificationTest, JobCancel) { EXPECT_EQ(MQT_NA_QDMI_device_job_cancel(nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobCancel) { +TEST_F(NaQDMIJobSpecificationTest, JobCancel) { const auto status = MQT_NA_QDMI_device_job_cancel(job); ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_INVALIDARGUMENT, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, JobCheck) { +TEST_F(NaQDMISpecificationTest, JobCheck) { EXPECT_EQ(MQT_NA_QDMI_device_job_check(nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobCheck) { +TEST_F(NaQDMIJobSpecificationTest, JobCheck) { QDMI_Job_Status jobStatus = QDMI_JOB_STATUS_RUNNING; const auto status = MQT_NA_QDMI_device_job_check(job, &jobStatus); ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, JobWait) { +TEST_F(NaQDMISpecificationTest, JobWait) { EXPECT_EQ(MQT_NA_QDMI_device_job_wait(nullptr, 0), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobWait) { +TEST_F(NaQDMIJobSpecificationTest, JobWait) { const auto status = MQT_NA_QDMI_device_job_wait(job, 1); ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED, QDMI_ERROR_TIMEOUT)); } -TEST_F(QDMISpecificationTest, JobGetResults) { +TEST_F(NaQDMISpecificationTest, JobGetResults) { EXPECT_EQ(MQT_NA_QDMI_device_job_get_results(nullptr, QDMI_JOB_RESULT_MAX, 0, nullptr, nullptr), QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMIJobSpecificationTest, JobGetResults) { +TEST_F(NaQDMIJobSpecificationTest, JobGetResults) { EXPECT_THAT(MQT_NA_QDMI_device_job_get_results(job, QDMI_JOB_RESULT_SHOTS, 0, nullptr, nullptr), testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); @@ -292,7 +292,7 @@ TEST_F(QDMIJobSpecificationTest, JobGetResults) { QDMI_ERROR_INVALIDARGUMENT); } -TEST_F(QDMISpecificationTest, QueryDeviceProperty) { +TEST_F(NaQDMISpecificationTest, QueryDeviceProperty) { MQT_NA_QDMI_Device_Session uninitializedSession = nullptr; ASSERT_EQ(MQT_NA_QDMI_device_session_alloc(&uninitializedSession), QDMI_SUCCESS); @@ -312,7 +312,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceProperty) { testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, QuerySiteProperty) { +TEST_F(NaQDMISpecificationTest, QuerySiteProperty) { MQT_NA_QDMI_Site site = querySites(session).front(); EXPECT_EQ( MQT_NA_QDMI_device_session_query_site_property( @@ -329,7 +329,7 @@ TEST_F(QDMISpecificationTest, QuerySiteProperty) { testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, QueryOperationProperty) { +TEST_F(NaQDMISpecificationTest, QueryOperationProperty) { MQT_NA_QDMI_Operation operation = queryOperations(session).front(); EXPECT_EQ(MQT_NA_QDMI_device_session_query_operation_property( nullptr, operation, 0, nullptr, 0, nullptr, @@ -345,7 +345,7 @@ TEST_F(QDMISpecificationTest, QueryOperationProperty) { testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); } -TEST_F(QDMISpecificationTest, QueryDeviceName) { +TEST_F(NaQDMISpecificationTest, QueryDeviceName) { size_t size = 0; ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &size), @@ -360,7 +360,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceName) { EXPECT_FALSE(value.empty()) << "Devices must provide a name"; } -TEST_F(QDMISpecificationTest, QueryDeviceVersion) { +TEST_F(NaQDMISpecificationTest, QueryDeviceVersion) { size_t size = 0; ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_VERSION, 0, nullptr, &size), @@ -375,7 +375,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceVersion) { EXPECT_FALSE(value.empty()) << "Devices must provide a version"; } -TEST_F(QDMISpecificationTest, QueryDeviceLibraryVersion) { +TEST_F(NaQDMISpecificationTest, QueryDeviceLibraryVersion) { size_t size = 0; ASSERT_EQ( MQT_NA_QDMI_device_session_query_device_property( @@ -391,7 +391,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceLibraryVersion) { EXPECT_FALSE(value.empty()) << "Devices must provide a library version"; } -TEST_F(QDMISpecificationTest, QueryDeviceLengthUnit) { +TEST_F(NaQDMISpecificationTest, QueryDeviceLengthUnit) { size_t size = 0; ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_LENGTHUNIT, 0, nullptr, &size), @@ -412,7 +412,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceLengthUnit) { } } -TEST_F(QDMISpecificationTest, QueryDeviceDurationUnit) { +TEST_F(NaQDMISpecificationTest, QueryDeviceDurationUnit) { size_t size = 0; ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_DURATIONUNIT, 0, nullptr, &size), @@ -433,7 +433,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceDurationUnit) { } } -TEST_F(QDMISpecificationTest, QueryDeviceMinAtomDistance) { +TEST_F(NaQDMISpecificationTest, QueryDeviceMinAtomDistance) { uint64_t minAtomDistance = 0; EXPECT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_MINATOMDISTANCE, sizeof(uint64_t), @@ -441,7 +441,7 @@ TEST_F(QDMISpecificationTest, QueryDeviceMinAtomDistance) { QDMI_SUCCESS); } -TEST_F(QDMISpecificationTest, QuerySiteIndex) { +TEST_F(NaQDMISpecificationTest, QuerySiteIndex) { size_t id = 0; EXPECT_NO_THROW(for (auto* site : querySites(session)) { EXPECT_EQ(MQT_NA_QDMI_device_session_query_site_property( @@ -452,7 +452,7 @@ TEST_F(QDMISpecificationTest, QuerySiteIndex) { }) << "Devices must provide a list of sites"; } -TEST_F(QDMISpecificationTest, QueryOperationName) { +TEST_F(NaQDMISpecificationTest, QueryOperationName) { size_t nameSize = 0; EXPECT_NO_THROW(for (auto* operation : queryOperations(session)) { EXPECT_EQ(MQT_NA_QDMI_device_session_query_operation_property( @@ -469,7 +469,7 @@ TEST_F(QDMISpecificationTest, QueryOperationName) { }) << "Devices must provide a list of operations"; } -TEST_F(QDMISpecificationTest, QueryDeviceQubitNum) { +TEST_F(NaQDMISpecificationTest, QueryDeviceQubitNum) { size_t numQubits = 0; EXPECT_EQ(MQT_NA_QDMI_device_session_query_device_property( session, QDMI_DEVICE_PROPERTY_QUBITSNUM, sizeof(size_t), @@ -477,13 +477,13 @@ TEST_F(QDMISpecificationTest, QueryDeviceQubitNum) { QDMI_SUCCESS); } -class NADeviceTest : public QDMISpecificationTest { +class NADeviceTest : public NaQDMISpecificationTest { protected: // NOLINTNEXTLINE(misc-include-cleaner) na::Device device; void SetUp() override { - QDMISpecificationTest::SetUp(); + NaQDMISpecificationTest::SetUp(); // Open the file // NOLINTNEXTLINE(misc-include-cleaner) std::ifstream file(NA_DEVICE_JSON); @@ -498,7 +498,7 @@ class NADeviceTest : public QDMISpecificationTest { } } - void TearDown() override { QDMISpecificationTest::TearDown(); } + void TearDown() override { NaQDMISpecificationTest::TearDown(); } }; TEST_F(NADeviceTest, QuerySiteData) { diff --git a/test/na/device/test_generator.cpp b/test/na/device/test_generator.cpp index 157141666d..f66b5e9193 100644 --- a/test/na/device/test_generator.cpp +++ b/test/na/device/test_generator.cpp @@ -41,7 +41,7 @@ auto testPopulation(const nlohmann::json& json) -> void { } } // namespace -TEST(GeneratorTest, WriteJSONSchema) { +TEST(NaGeneratorTest, WriteJSONSchema) { std::ostringstream os; EXPECT_NO_THROW(writeJSONSchema(os)); // clang-tidy wants to include the forward header, but we have the full @@ -53,7 +53,7 @@ TEST(GeneratorTest, WriteJSONSchema) { testPopulation(json); } -TEST(GeneratorTest, DurationUnitNanosecond) { +TEST(NaGeneratorTest, DurationUnitNanosecond) { std::istringstream is(R"({ "durationUnit": { "scaleFactor": 5, @@ -66,7 +66,7 @@ TEST(GeneratorTest, DurationUnitNanosecond) { EXPECT_EQ(device.durationUnit.unit, "ns"); } -TEST(GeneratorTest, DurationUnitInvalid) { +TEST(NaGeneratorTest, DurationUnitInvalid) { std::istringstream is(R"({ "durationUnit": { "scaleFactor": 1, @@ -76,7 +76,7 @@ TEST(GeneratorTest, DurationUnitInvalid) { EXPECT_THROW(std::ignore = readJSON(is), std::runtime_error); } -TEST(GeneratorTest, LengthUnitNanometer) { +TEST(NaGeneratorTest, LengthUnitNanometer) { std::istringstream is(R"({ "lengthUnit": { "scaleFactor": 5, @@ -89,7 +89,7 @@ TEST(GeneratorTest, LengthUnitNanometer) { EXPECT_EQ(device.lengthUnit.unit, "nm"); } -TEST(GeneratorTest, LengthUnitInvalid) { +TEST(NaGeneratorTest, LengthUnitInvalid) { std::istringstream is(R"({ "lengthUnit": { "scaleFactor": 1, diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 6a54119d0a..0e449efd84 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -46,9 +46,15 @@ def device_and_operation(request: pytest.FixtureRequest) -> tuple[Device, Device Returns: A tuple containing a quantum device instance and one of its operations. """ - dev = request.param - operation = dev.operations()[0] - return dev, operation + device = request.param + + # If the device has no operations, skip tests that use this fixture. + ops = device.operations() + if not ops: + pytest.skip(f"Device '{device.name()}' has no operations.") + + operation = ops[0] + return device, operation @pytest.fixture @@ -107,10 +113,9 @@ def test_device_sites(device: Device) -> None: def test_device_operations(device: Device) -> None: - """Test that the device operations is a non-empty list of Device.Operation objects.""" + """Test that the device operations is a list of Device.Operation objects.""" operations = device.operations() assert isinstance(operations, list) - assert len(operations) > 0 assert all(isinstance(op, Device.Operation) for op in operations) diff --git a/test/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt index 833d3b2c11..6934a23d54 100644 --- a/test/qdmi/CMakeLists.txt +++ b/test/qdmi/CMakeLists.txt @@ -7,14 +7,18 @@ # Licensed under the MIT License add_subdirectory(dd) +add_subdirectory(sc) set(TARGET_NAME mqt-core-qdmi-driver-test) if(TARGET MQT::CoreQDMIDriver) package_add_test(${TARGET_NAME} MQT::CoreQDMIDriver test_driver.cpp) if(NOT WIN32) - add_dependencies(${TARGET_NAME} MQT::CoreQDMINaDeviceDyn) - target_compile_definitions(${TARGET_NAME} - PRIVATE DYN_DEV_LIB="$") + add_dependencies(${TARGET_NAME} MQT::CoreQDMINaDeviceDyn MQT::CoreQDMIScDeviceDyn) + target_compile_definitions( + ${TARGET_NAME} + PRIVATE + "DYN_DEV_LIBS=std::array{ std::pair{\"$\", \"MQT_NA_DYN\"}, std::pair{\"$\", \"MQT_SC_DYN\"} }" + ) endif() endif() diff --git a/test/qdmi/sc/CMakeLists.txt b/test/qdmi/sc/CMakeLists.txt new file mode 100644 index 0000000000..81c5ace0d2 --- /dev/null +++ b/test/qdmi/sc/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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 + +if(TARGET MQT::CoreQDMIScDeviceGen + AND TARGET MQT::CoreQDMIScDeviceGenerator + AND TARGET MQT::CoreQDMIScDevice) + # Set QDMI prefix again for the tests + set(QDMI_PREFIX "MQT_SC") + generate_device_defs_executable(${QDMI_PREFIX}) + # Set test target name + set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-sc-device-test) + # Add the test executable + package_add_test(${TARGET_NAME} MQT::CoreQDMIScDevice test_generator.cpp test_app.cpp + test_device.cpp) + # Add dependency to enforce implementation of all interface functions + add_dependencies(${TARGET_NAME} qdmi_test_mqt_sc_device_defs) + # Link further libraries + target_link_libraries(${TARGET_NAME} PRIVATE MQT::CoreQDMIScDeviceGen + nlohmann_json::nlohmann_json) + # Set the executable path and version information + target_compile_definitions( + ${TARGET_NAME} + PRIVATE EXECUTABLE_PATH="$" + MQT_CORE_VERSION="${MQT_CORE_VERSION}" + SC_DEVICE_JSON="${PROJECT_SOURCE_DIR}/json/sc/device.json") +endif() diff --git a/test/qdmi/sc/test_app.cpp b/test/qdmi/sc/test_app.cpp new file mode 100644 index 0000000000..52aa7bd536 --- /dev/null +++ b/test/qdmi/sc/test_app.cpp @@ -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 + */ + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define PLATFORM_POPEN _popen +#define PLATFORM_PCLOSE _pclose +#else +// The following are included via but the direct include are platform- +// specific, so ignore the corresponding warning for platform-agnostic includes. +// NOLINTBEGIN(misc-include-cleaner) +#define PLATFORM_POPEN popen +#define PLATFORM_PCLOSE pclose +// NOLINTEND(misc-include-cleaner) +#endif // _WIN32 + +TEST(ScExecutableTest, Version) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " --version"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) << "Executable failed with return code: " + << returnCode; + // Validate the output + EXPECT_EQ(output.str(), + // NOLINTNEXTLINE(misc-include-cleaner) + "MQT QDMI SC Device Generator (MQT Version " MQT_CORE_VERSION + ")\n"); +} + +TEST(ScExecutableTest, MissingSubcommand) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_NE(returnCode, 0); +} + +TEST(ScExecutableTest, UnknownSubcommand) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " unknown"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_NE(returnCode, 0); +} + +TEST(ScExecutableTest, SchemaUnknownOption) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " schema --unknown-option"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_NE(returnCode, 0); +} + +TEST(ScExecutableTest, SchemaMissingFile) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " schema --output"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_NE(returnCode, 0); +} + +TEST(ScExecutableTest, ValidateInvalidJson) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " validate"; + // Open a pipe to the executable + FILE* pipe = PLATFORM_POPEN(command.c_str(), "w"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Write the schema to the executable's stdin + fwrite("{", sizeof(char), 2, pipe); + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + EXPECT_NE(returnCode, 0) << "Executable failed with return code: " + << returnCode; +} + +TEST(ScExecutableTest, GenerateMissingFile) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " generate --output"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_NE(returnCode, 0); +} + +TEST(ScExecutableTest, Usage) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " --help"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) << "Executable failed with return code: " + << returnCode; + EXPECT_FALSE(output.str().empty()); +} + +TEST(ScExecutableTest, SchemaUsage) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " schema --help"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) << "Executable failed with return code: " + << returnCode; + EXPECT_TRUE(output.str().rfind("Generates a JSON schema", 0) == 0); +} + +TEST(ScExecutableTest, ValidateUsage) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " validate --help"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) << "Executable failed with return code: " + << returnCode; + EXPECT_TRUE(output.str().rfind("Validates", 0) == 0); +} + +TEST(ScExecutableTest, GenerateUsage) { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " generate --help"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) << "Executable failed with return code: " + << returnCode; + EXPECT_TRUE(output.str().rfind("Generates a header file", 0) == 0); +} + +TEST(ScExecutableTest, RoundTrip) { + std::string schema; + // Capture the output of the schema command + { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " schema"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + schema = output.str(); + // Print the captured output + std::cout << "Captured Output:\n" << schema << "\n"; + ASSERT_EQ(returnCode, 0) + << "Executable failed with return code: " << returnCode; + } + // Validate the output + { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " validate"; + // Open a pipe to the executable with write mode + FILE* pipe = PLATFORM_POPEN(command.c_str(), "w"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Write the schema to the executable's stdin + fwrite(schema.c_str(), sizeof(char), schema.size(), pipe); + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + ASSERT_EQ(returnCode, 0) + << "Executable failed with return code: " << returnCode; + } +} + +TEST(ScExecutableTest, RoundTripFile) { + // Write schema to a file + { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " schema --output schema.json"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + ASSERT_EQ(returnCode, 0) + << "Executable failed with return code: " << returnCode; + } + // Validate the output + { + // Command to execute + // NOLINTNEXTLINE(misc-include-cleaner) + const std::string command = EXECUTABLE_PATH " validate schema.json"; + // Open a pipe to capture the output + FILE* pipe = PLATFORM_POPEN(command.c_str(), "r"); + ASSERT_NE(pipe, nullptr) << "Failed to open pipe"; + // Read the output + std::array buffer{}; + buffer.fill('\0'); + std::stringstream output; + while (fgets(buffer.data(), static_cast(buffer.size()), pipe) != + nullptr) { + output << buffer.data(); + } + // Close the pipe + const int returnCode = PLATFORM_PCLOSE(pipe); + // Print the captured output + std::cout << "Captured Output:\n" << output.str() << "\n"; + EXPECT_EQ(returnCode, 0) + << "Executable failed with return code: " << returnCode; + } + // Remove created file + std::remove("schema.json"); +} diff --git a/test/qdmi/sc/test_device.cpp b/test/qdmi/sc/test_device.cpp new file mode 100644 index 0000000000..4ba8122d60 --- /dev/null +++ b/test/qdmi/sc/test_device.cpp @@ -0,0 +1,342 @@ +/* + * 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 + */ + +#include "mqt_sc_qdmi/device.h" + +#include +#include +#include +// NOLINTNEXTLINE(misc-include-cleaner) +#include +#include +#include +#include + +namespace { +[[nodiscard]] auto querySites(MQT_SC_QDMI_Device_Session session) + -> std::vector { + size_t size = 0; + if (MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_SITES, 0, nullptr, &size) != + QDMI_SUCCESS) { + throw std::runtime_error("Failed to query sites"); + } + if (size == 0) { + throw std::runtime_error("No sites available"); + } + std::vector sites(size / sizeof(MQT_SC_QDMI_Site)); + if (MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_SITES, size, + static_cast(sites.data()), nullptr) != QDMI_SUCCESS) { + throw std::runtime_error("Failed to query sites"); + } + return sites; +} +} // namespace + +class ScQDMISpecificationTest : public ::testing::Test { +protected: + MQT_SC_QDMI_Device_Session session = nullptr; + + void SetUp() override { + ASSERT_EQ(MQT_SC_QDMI_device_initialize(), QDMI_SUCCESS) + << "Failed to initialize the device"; + + ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&session), QDMI_SUCCESS) + << "Failed to allocate a session"; + + ASSERT_EQ(MQT_SC_QDMI_device_session_init(session), QDMI_SUCCESS) + << "Failed to initialize a session. Potential errors: Wrong or missing " + "authentication information, device status is offline, or in " + "maintenance. To provide credentials, take a look in " __FILE__ + << (__LINE__ - 4); + } + + void TearDown() override { + if (session != nullptr) { + MQT_SC_QDMI_device_session_free(session); + session = nullptr; + } + MQT_SC_QDMI_device_finalize(); + } +}; + +class ScQDMIJobSpecificationTest : public ScQDMISpecificationTest { +protected: + MQT_SC_QDMI_Device_Job job = nullptr; + + void SetUp() override { + ScQDMISpecificationTest::SetUp(); + ASSERT_EQ(MQT_SC_QDMI_device_session_create_device_job(session, &job), + QDMI_SUCCESS) + << "Failed to create a device job."; + } + + void TearDown() override { + if (job != nullptr) { + MQT_SC_QDMI_device_job_free(job); + job = nullptr; + } + ScQDMISpecificationTest::TearDown(); + } +}; + +TEST_F(ScQDMISpecificationTest, SessionAlloc) { + EXPECT_EQ(MQT_SC_QDMI_device_session_alloc(nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMISpecificationTest, SessionInit) { + EXPECT_EQ(MQT_SC_QDMI_device_session_init(session), QDMI_ERROR_BADSTATE); + EXPECT_EQ(MQT_SC_QDMI_device_session_init(nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMISpecificationTest, SessionSetParameter) { + MQT_SC_QDMI_Device_Session uninitializedSession = nullptr; + ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&uninitializedSession), + QDMI_SUCCESS); + EXPECT_THAT(MQT_SC_QDMI_device_session_set_parameter( + uninitializedSession, QDMI_DEVICE_SESSION_PARAMETER_BASEURL, + 20, "https://example.com"), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED, + QDMI_ERROR_INVALIDARGUMENT)); + EXPECT_EQ(MQT_SC_QDMI_device_session_set_parameter( + session, QDMI_DEVICE_SESSION_PARAMETER_BASEURL, 20, + "https://example.com"), + QDMI_ERROR_BADSTATE); + EXPECT_EQ(MQT_SC_QDMI_device_session_set_parameter( + session, QDMI_DEVICE_SESSION_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + MQT_SC_QDMI_device_session_free(uninitializedSession); +} + +TEST_F(ScQDMISpecificationTest, JobCreate) { + MQT_SC_QDMI_Device_Session uninitializedSession = nullptr; + MQT_SC_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&uninitializedSession), + QDMI_SUCCESS); + EXPECT_EQ( + MQT_SC_QDMI_device_session_create_device_job(uninitializedSession, &job), + QDMI_ERROR_BADSTATE); + EXPECT_EQ(MQT_SC_QDMI_device_session_create_device_job(session, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(MQT_SC_QDMI_device_session_create_device_job(nullptr, &job), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_THAT(MQT_SC_QDMI_device_session_create_device_job(session, &job), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + MQT_SC_QDMI_device_job_free(job); + MQT_SC_QDMI_device_session_free(uninitializedSession); +} + +TEST_F(ScQDMISpecificationTest, JobSetParameter) { + EXPECT_EQ(MQT_SC_QDMI_device_job_set_parameter( + nullptr, QDMI_DEVICE_JOB_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobSetParameter) { + QDMI_Program_Format value = QDMI_PROGRAM_FORMAT_QASM2; + EXPECT_THAT(MQT_SC_QDMI_device_job_set_parameter( + job, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(QDMI_Program_Format), &value), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + EXPECT_EQ(MQT_SC_QDMI_device_job_set_parameter( + job, QDMI_DEVICE_JOB_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMISpecificationTest, JobQueryProperty) { + EXPECT_EQ(MQT_SC_QDMI_device_job_query_property( + nullptr, QDMI_DEVICE_JOB_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobQueryProperty) { + EXPECT_THAT(MQT_SC_QDMI_device_job_query_property( + job, QDMI_DEVICE_JOB_PROPERTY_ID, 0, nullptr, nullptr), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + EXPECT_EQ(MQT_SC_QDMI_device_job_query_property( + job, QDMI_DEVICE_JOB_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, QueryJobId) { + size_t size = 0; + const auto status = MQT_SC_QDMI_device_job_query_property( + job, QDMI_DEVICE_JOB_PROPERTY_ID, 0, nullptr, &size); + ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + if (status == QDMI_ERROR_NOTSUPPORTED) { + GTEST_SKIP() << "Job ID property is not supported by the device"; + } + ASSERT_GT(size, 0); + std::string id(size - 1, '\0'); + EXPECT_THAT(MQT_SC_QDMI_device_job_query_property( + job, QDMI_DEVICE_JOB_PROPERTY_ID, size, id.data(), nullptr), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); +} + +TEST_F(ScQDMISpecificationTest, JobSubmit) { + EXPECT_EQ(MQT_SC_QDMI_device_job_submit(nullptr), QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobSubmit) { + const auto status = MQT_SC_QDMI_device_job_submit(job); + ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); +} + +TEST_F(ScQDMISpecificationTest, JobCancel) { + EXPECT_EQ(MQT_SC_QDMI_device_job_cancel(nullptr), QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobCancel) { + const auto status = MQT_SC_QDMI_device_job_cancel(job); + ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_INVALIDARGUMENT, + QDMI_ERROR_NOTSUPPORTED)); +} + +TEST_F(ScQDMISpecificationTest, JobCheck) { + EXPECT_EQ(MQT_SC_QDMI_device_job_check(nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobCheck) { + QDMI_Job_Status jobStatus = QDMI_JOB_STATUS_RUNNING; + const auto status = MQT_SC_QDMI_device_job_check(job, &jobStatus); + ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); +} + +TEST_F(ScQDMISpecificationTest, JobWait) { + EXPECT_EQ(MQT_SC_QDMI_device_job_wait(nullptr, 0), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobWait) { + const auto status = MQT_SC_QDMI_device_job_wait(job, 1); + ASSERT_THAT(status, testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED, + QDMI_ERROR_TIMEOUT)); +} + +TEST_F(ScQDMISpecificationTest, JobGetResults) { + EXPECT_EQ(MQT_SC_QDMI_device_job_get_results(nullptr, QDMI_JOB_RESULT_MAX, 0, + nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMIJobSpecificationTest, JobGetResults) { + EXPECT_THAT(MQT_SC_QDMI_device_job_get_results(job, QDMI_JOB_RESULT_SHOTS, 0, + nullptr, nullptr), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); + EXPECT_EQ(MQT_SC_QDMI_device_job_get_results(job, QDMI_JOB_RESULT_MAX, 0, + nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(ScQDMISpecificationTest, QueryDeviceProperty) { + MQT_SC_QDMI_Device_Session uninitializedSession = nullptr; + ASSERT_EQ(MQT_SC_QDMI_device_session_alloc(&uninitializedSession), + QDMI_SUCCESS); + EXPECT_EQ( + MQT_SC_QDMI_device_session_query_device_property( + uninitializedSession, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, nullptr), + QDMI_ERROR_BADSTATE); + EXPECT_EQ(MQT_SC_QDMI_device_session_query_device_property( + nullptr, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_COUPLINGMAP, 0, nullptr, nullptr), + QDMI_SUCCESS); + MQT_SC_QDMI_device_session_free(uninitializedSession); +} + +TEST_F(ScQDMISpecificationTest, QuerySiteProperty) { + MQT_SC_QDMI_Site site = querySites(session).front(); + EXPECT_EQ( + MQT_SC_QDMI_device_session_query_site_property( + session, nullptr, QDMI_SITE_PROPERTY_INDEX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(MQT_SC_QDMI_device_session_query_site_property( + nullptr, site, QDMI_SITE_PROPERTY_INDEX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_EQ(MQT_SC_QDMI_device_session_query_site_property( + session, site, QDMI_SITE_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + EXPECT_THAT(MQT_SC_QDMI_device_session_query_site_property( + session, site, QDMI_SITE_PROPERTY_NAME, 0, nullptr, nullptr), + testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)); +} + +TEST_F(ScQDMISpecificationTest, QueryDeviceName) { + size_t size = 0; + ASSERT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a name"; + std::string value(size - 1, '\0'); + ASSERT_EQ( + MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_NAME, size, value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a name"; + EXPECT_FALSE(value.empty()) << "Devices must provide a name"; +} + +TEST_F(ScQDMISpecificationTest, QueryDeviceVersion) { + size_t size = 0; + ASSERT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_VERSION, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a version"; + std::string value(size - 1, '\0'); + ASSERT_EQ( + MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_VERSION, size, value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a version"; + EXPECT_FALSE(value.empty()) << "Devices must provide a version"; +} + +TEST_F(ScQDMISpecificationTest, QueryDeviceLibraryVersion) { + size_t size = 0; + ASSERT_EQ( + MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_LIBRARYVERSION, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a library version"; + std::string value(size - 1, '\0'); + ASSERT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_LIBRARYVERSION, size, + value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a library version"; + EXPECT_FALSE(value.empty()) << "Devices must provide a library version"; +} + +TEST_F(ScQDMISpecificationTest, QuerySiteIndex) { + size_t id = 0; + EXPECT_NO_THROW(for (auto* site : querySites(session)) { + EXPECT_EQ(MQT_SC_QDMI_device_session_query_site_property( + session, site, QDMI_SITE_PROPERTY_INDEX, sizeof(size_t), &id, + nullptr), + QDMI_SUCCESS) + << "Devices must provide a site id"; + }) << "Devices must provide a list of sites"; +} + +TEST_F(ScQDMISpecificationTest, QueryDeviceQubitNum) { + size_t numQubits = 0; + EXPECT_EQ(MQT_SC_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_QUBITSNUM, sizeof(size_t), + &numQubits, nullptr), + QDMI_SUCCESS); +} diff --git a/test/qdmi/sc/test_generator.cpp b/test/qdmi/sc/test_generator.cpp new file mode 100644 index 0000000000..0ba5e72342 --- /dev/null +++ b/test/qdmi/sc/test_generator.cpp @@ -0,0 +1,54 @@ +/* + * 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 + */ + +#include "qdmi/sc/Generator.hpp" + +#include +#include +// clang-tidy wants to include the forward header, but we need the full +// NOLINTNEXTLINE(misc-include-cleaner) +#include + +namespace sc { +namespace { +// clang-tidy wants to include the forward header, but we have the full +// NOLINTNEXTLINE(misc-include-cleaner) +auto testPopulation(const nlohmann::json& json) -> void { + for (const auto& [key, value] : json.items()) { + if (value.is_array()) { + // Array field should have at least one default entry + EXPECT_GT(value.size(), 0) << "Array field '" << key + << "' should have at least one default entry"; + for (const auto& item : value) { + // Each entry in the array should not be null + EXPECT_FALSE(item.is_null()) + << "Array field '" << key << "' should not have null entries"; + testPopulation(item); + } + } else if (value.is_object()) { + testPopulation(value); + } + } +} +} // namespace + +TEST(ScGeneratorTest, WriteJSONSchema) { + std::ostringstream os; + EXPECT_NO_THROW(writeJSONSchema(os)); + // clang-tidy wants to include the forward header, but we have the full + // NOLINTNEXTLINE(misc-include-cleaner) + nlohmann::json json; + EXPECT_NO_THROW(json = nlohmann::json::parse(os.str())); + EXPECT_TRUE(json.is_object()); + EXPECT_GT(json.size(), 0); + testPopulation(json); +} + +} // namespace sc diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index 6ccc62cd34..3473c05edd 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace testing { @@ -48,7 +49,11 @@ class DriverTest : public testing::TestWithParam { QDMI_Device device = nullptr; #ifndef _WIN32 static void SetUpTestSuite() { - qdmi::Driver::get().addDynamicDeviceLibrary(DYN_DEV_LIB, "MQT_NA_DYN"); + ASSERT_NO_THROW({ + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix); + } + }); } #endif // _WIN32 @@ -121,9 +126,15 @@ class DriverJobTest : public DriverTest { }; #ifndef _WIN32 -TEST_P(DriverTest, LoadLibraryTwice) { - EXPECT_NO_THROW( - qdmi::Driver::get().addDynamicDeviceLibrary(DYN_DEV_LIB, "MQT_NA_DYN")); +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). + 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)); + }); } #endif // _WIN32 @@ -503,11 +514,13 @@ TEST_P(DriverTest, QueryNeedsCalibration) { } #ifdef _WIN32 constexpr std::array DEVICES{"MQT NA Default QDMI Device", - "MQT Core DDSIM QDMI Device"}; + "MQT Core DDSIM QDMI Device", + "MQT SC Default QDMI Device"}; #else -constexpr std::array DEVICES{"MQT NA Default QDMI Device", - "MQT NA Dynamic QDMI Device", - "MQT Core DDSIM QDMI Device"}; +constexpr std::array DEVICES{ + "MQT NA Default QDMI Device", "MQT NA Dynamic QDMI Device", + "MQT Core DDSIM QDMI Device", "MQT SC Default QDMI Device", + "MQT SC Dynamic QDMI Device"}; #endif // Instantiate the test suite with different parameters INSTANTIATE_TEST_SUITE_P(