diff --git a/.license-tools-config.json b/.license-tools-config.json index ec4be4f5a..f2961007e 100644 --- a/.license-tools-config.json +++ b/.license-tools-config.json @@ -13,7 +13,8 @@ ".mlir": "SLASH_STYLE", ".td": "SLASH_STYLE", ".yaml": "POUND_STYLE", - ".toml": "POUND_STYLE" + ".toml": "POUND_STYLE", + ".proto": "SLASH_STYLE" }, "exclude": [ "^\\.[^/]+", diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 06f5e4036..b4dab54ce 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -59,10 +59,35 @@ else() list(APPEND FETCH_PACKAGES boost_mp) endif() +# Abseil is required by GTest and Protobuf. To avoid version conflicts, we add this dependency +# centrally. +set(ABSL_VERSION + 20250512.1 + CACHE STRING "abseil-cpp version") +set(ABSL_URL https://github.com/abseil/abseil-cpp/archive/refs/tags/${ABSL_VERSION}.tar.gz) +# The install instruction for re2 cannot be disabled and they require the export targets from +# abseil. +set(ABSL_ENABLE_INSTALL + ON + CACHE BOOL "" FORCE) +FetchContent_Declare(abseil-cpp URL ${ABSL_URL} FIND_PACKAGE_ARGS ${ABSL_VERSION} CONFIG NAMES absl) +list(APPEND FETCH_PACKAGES abseil-cpp) + if(BUILD_MQT_CORE_TESTS) + # When adding abseil manually, GTest also requires re2 + set(RE2_VERSION + 2024-07-02 + CACHE STRING "re2 version") + set(RE2_URL https://github.com/google/re2/archive/refs/tags/${RE2_VERSION}.tar.gz) + # remove '-' from version + string(REPLACE "-" "" RE2_VERSION "${RE2_VERSION}") + FetchContent_Declare(re2 URL ${RE2_URL} FIND_PACKAGE_ARGS ${RE2_VERSION} CONFIG) + list(APPEND FETCH_PACKAGES re2) + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + set(GTEST_HAS_ABSL ON) set(GTEST_VERSION 1.17.0 CACHE STRING "Google Test version") @@ -71,5 +96,46 @@ if(BUILD_MQT_CORE_TESTS) list(APPEND FETCH_PACKAGES googletest) endif() +set(Protobuf_VERSION + 31.1 + CACHE STRING "protobuf version") +set(Protobuf_URL + https://github.com/protocolbuffers/protobuf/releases/download/v${Protobuf_VERSION}/protobuf-${Protobuf_VERSION}.tar.gz +) +# the default of the following is ON, they are just here to make more explicit that they are +# required +set(protobuf_BUILD_PROTOBUF_BINARIES ON) +set(protobuf_BUILD_PROTOC_BINARIES ON) +set(protobuf_BUILD_LIBUPB ON) +# the default of the following is ON, but we do not need the tests +set(protobuf_BUILD_TESTS OFF) +FetchContent_Declare(protobuf URL ${Protobuf_URL} FIND_PACKAGE_ARGS ${Protobuf_VERSION} CONFIG + NAMES protobuf) +list(APPEND FETCH_PACKAGES protobuf) + +# cmake-format: off +set(QDMI_VERSION 1.2.0 + CACHE STRING "QDMI version") +set(QDMI_REV "9034e653a6e368579d99e352ecd8390e6e947bc6" + CACHE STRING "QDMI identifier (tag, branch or commit hash)") +set(QDMI_REPO_OWNER "Munich-Quantum-Software-Stack" + CACHE STRING "QDMI repository owner (change when using a fork)") +# cmake-format: on +FetchContent_Declare( + qdmi + GIT_REPOSITORY https://github.com/${QDMI_REPO_OWNER}/qdmi.git + GIT_TAG ${QDMI_REV} + FIND_PACKAGE_ARGS ${QDMI_VERSION}) +list(APPEND FETCH_PACKAGES qdmi) + +set(SPDLOG_VERSION + 1.15.3 + CACHE STRING "spdlog version") +set(SPDLOG_URL https://github.com/gabime/spdlog/archive/refs/tags/v${SPDLOG_VERSION}.tar.gz) +# Add position independent code for spdlog, this is required for python bindings on linux +set(SPDLOG_BUILD_PIC ON) +FetchContent_Declare(spdlog URL ${SPDLOG_URL} FIND_PACKAGE_ARGS ${SPDLOG_VERSION}) +list(APPEND FETCH_PACKAGES spdlog) + # Make all declared dependencies available. FetchContent_MakeAvailable(${FETCH_PACKAGES}) diff --git a/docs/na_qdmi_device.md b/docs/na_qdmi_device.md new file mode 100644 index 000000000..41000a755 --- /dev/null +++ b/docs/na_qdmi_device.md @@ -0,0 +1,115 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +mystnb: + number_source_lines: true +--- + +# MQT Core Neutral Atom QDMI Device + +## Objective + +Compilation passes throughout MQT need information about the target device. +The Neutral Atom QDMI device provides a uniform way to provide the necessary information. +It defines a representation to easily provide static information in the form of a file. + + + +## Describing a Device + +The basis of a device implementation is a specification in a JSON file. +These JSON files are defined on the basis of a protocol buffer schema that can be found in `proto/na/device.proto`. +During compilation, this JSON file is parsed and the corresponding C++ code is produced for the actual QDMI device implementation. +The C++ code is then compiled to a library that can be used by the QDMI driver. + +### JSON Schema + +An example instance of a device JSON file can be found in `json/na/device.json`. + +#### Top-Level Fields + +- **`name`** _(string)_: The name of the device. +- **`num_qubits`** _(integer)_: The total number of qubits in the device. +- **`traps`** _(array)_: A list of traps defining the qubit lattice structure. +- **`minAtomDistance`** _(number)_: The minimum distance between atoms in the lattice. +- **`globalSingleQubitOperations`** _(array)_: A list of global single-qubit operations supported by the device. +- **`globalMultiQubitOperations`** _(array)_: A list of global multi-qubit operations supported by the device. +- **`localSingleQubitOperations`** _(array)_: A list of local single-qubit operations supported by the device. +- **`localMultiQubitOperations`** _(array)_: A list of local multi-qubit operations supported by the device. +- **`shuttlingUnits`** _(array)_: A list of shuttling units for moving qubits. +- **`decoherenceTimes`** _(object)_: Decoherence times for the qubits. +- **`lengthUnit`** _(object)_: The unit of length used in the file. +- **`timeUnit`** _(object)_: The unit of time used in the file. + +--- + +#### Detailed Field Descriptions + +##### `traps` _(array of objects)_: + +- **`latticeOrigin`** _(object)_: The origin of the lattice. + - **`x`** _(number)_: X-coordinate of the origin. + - **`y`** _(number)_: Y-coordinate of the origin. +- **`latticeVector1`** _(object)_: Defines one lattice vector. + - **`vector`** _(object)_: A vector in the lattice. + - **`x`** _(number)_: X-component of the vector. + - **`y`** _(number)_: Y-component of the vector. + - **`repeat`** _(integer)_: Number of repetitions of the vector. +- **`latticeVector2`** _(object)_: Defines the other lattice vector. + - **`vector`** _(object)_: A vector in the lattice. + - **`x`** _(number)_: X-component of the vector. + - **`y`** _(number)_: Y-component of the vector. + - **`repeat`** _(integer)_: Number of repetitions of the vector. +- **`sublatticeOffsets`** _(array of objects)_: Offsets for sublattices. + - **`x`** _(number)_: X-offset. + - **`y`** _(number)_: Y-offset. + +##### `globalSingleQubitOperations` and `localSingleQubitOperations` _(array of objects)_: + +- **`name`** _(string)_: The name of the operation. +- **`region`** _(object)_: The region where the operation is applied. + - **`origin`** _(object)_: The origin of the region. + - **`x`** _(number)_: X-coordinate of the origin. + - **`y`** _(number)_: Y-coordinate of the origin. + - **`size`** _(object)_: The size of the region. + - **`width`** _(number)_: Width of the region. + - **`height`** _(number)_: Height of the region. +- **`duration`** _(number)_: Duration of the operation. +- **`fidelity`** _(number)_: Fidelity of the operation. +- **`numParameters`** _(integer)_: Number of parameters for the operation. + +##### `globalMultiQubitOperations` and `localMultiQubitOperations` _(array of objects)_: + +- **`name`** _(string)_: The name of the operation. +- **`region`** _(object)_: The region where the operation is applied (same structure as above). +- **`interactionRadius`** _(number)_: Radius of interaction for the operation. +- **`blockingRadius`** _(number)_: Blocking radius for the operation. +- **`numQubits`** _(integer)_: Number of qubits involved in the operation. +- **`numParameters`** _(integer)_: Number of parameters for the operation. +- **`duration`** _(number)_: Duration of the operation. +- **`fidelity`** _(number)_: Fidelity of the operation. +- **`idlingFidelity`** _(number, optional)_: Fidelity when qubits are idle. + +##### `shuttlingUnits` _(array of objects)_: + +- **`name`** _(string)_: The name of the shuttling unit. +- **`region`** _(object)_: The region where the shuttling unit operates (same structure as above). +- **`numRows`** _(integer)_: Number of rows in the shuttling unit. +- **`numColumns`** _(integer)_: Number of columns in the shuttling unit. +- **`movingSpeed`** _(number)_: Speed of movement. +- **`loadDuration`** _(number)_: Duration to load a qubit. +- **`storeDuration`** _(number)_: Duration to store a qubit. +- **`loadFidelity`** _(number)_: Fidelity of loading a qubit. +- **`storeFidelity`** _(number)_: Fidelity of storing a qubit. +- **`numParameters`** _(integer)_: Number of parameters for the shuttling unit. + +##### `decoherenceTimes` _(object)_: + +- **`t1`** _(number)_: Relaxation time (T1) in the specified time unit. +- **`t2`** _(number)_: Dephasing time (T2) in the specified time unit. + +##### `lengthUnit` and `timeUnit` _(objects)_: + +- **`value`** _(number)_: The scaling factor for the unit. +- **`unit`** _(string)_: The unit of measurement (e.g., "um" for micrometers, "us" for microseconds). diff --git a/include/mqt-core/na/device/Generator.hpp b/include/mqt-core/na/device/Generator.hpp new file mode 100644 index 000000000..d6889dede --- /dev/null +++ b/include/mqt-core/na/device/Generator.hpp @@ -0,0 +1,73 @@ +/* + * 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 "na/device/device.pb.h" + +#include +#include +#include + +namespace na { +/** + * @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 Protobuf message. + * @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 Protobuf message. + * @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 the protobuf 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. + */ +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 the protobuf 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. + */ +auto writeHeader(const Device& device, const std::string& path) -> void; + +} // namespace na diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp new file mode 100644 index 000000000..50b6270e6 --- /dev/null +++ b/include/mqt-core/qdmi/Driver.hpp @@ -0,0 +1,457 @@ +/* + * 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 "mqt_na_qdmi/device.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qdmi { + +/** + * @brief Definition of the device library. + * @details The device library contains function pointers to the QDMI + * device interface functions. + */ +struct DeviceLibrary { + // we keep the naming scheme of QDMI, i.e., snail_case for function names, + // here to ease the `LOAD_SYMBOL` macro later on. + // NOLINTBEGIN(readability-identifier-naming) + /// Function pointer to @ref QDMI_device_initialize. + decltype(QDMI_device_initialize)* device_initialize{}; + /// Function pointer to @ref QDMI_device_finalize. + decltype(QDMI_device_finalize)* device_finalize{}; + /// Function pointer to @ref QDMI_device_session_alloc. + decltype(QDMI_device_session_alloc)* device_session_alloc{}; + /// Function pointer to @ref QDMI_device_session_init. + decltype(QDMI_device_session_init)* device_session_init{}; + /// Function pointer to @ref QDMI_device_session_free. + decltype(QDMI_device_session_free)* device_session_free{}; + /// Function pointer to @ref QDMI_device_session_set_parameter. + decltype(QDMI_device_session_set_parameter)* device_session_set_parameter{}; + /// Function pointer to @ref QDMI_device_session_create_device_job. + decltype(QDMI_device_session_create_device_job)* + device_session_create_device_job{}; + /// Function pointer to @ref QDMI_device_job_free. + decltype(QDMI_device_job_free)* device_job_free{}; + /// Function pointer to @ref QDMI_device_job_set_parameter. + decltype(QDMI_device_job_set_parameter)* device_job_set_parameter{}; + /// Function pointer to @ref QDMI_device_job_query_property. + decltype(QDMI_device_job_query_property)* device_job_query_property{}; + /// Function pointer to @ref QDMI_device_job_submit. + decltype(QDMI_device_job_submit)* device_job_submit{}; + /// Function pointer to @ref QDMI_device_job_cancel. + decltype(QDMI_device_job_cancel)* device_job_cancel{}; + /// Function pointer to @ref QDMI_device_job_check. + decltype(QDMI_device_job_check)* device_job_check{}; + /// Function pointer to @ref QDMI_device_job_wait. + decltype(QDMI_device_job_wait)* device_job_wait{}; + /// Function pointer to @ref QDMI_device_job_get_results. + decltype(QDMI_device_job_get_results)* device_job_get_results{}; + /// Function pointer to @ref QDMI_device_session_query_device_property. + decltype(QDMI_device_session_query_device_property)* + device_session_query_device_property{}; + /// Function pointer to @ref QDMI_device_session_query_site_property. + decltype(QDMI_device_session_query_site_property)* + device_session_query_site_property{}; + /// Function pointer to @ref QDMI_device_session_query_operation_property. + decltype(QDMI_device_session_query_operation_property)* + device_session_query_operation_property{}; + // NOLINTEND(readability-identifier-naming) + + // Default constructor + DeviceLibrary() = default; + // delete copy constructor and copy assignment operator + DeviceLibrary(const DeviceLibrary&) = delete; + DeviceLibrary& operator=(const DeviceLibrary&) = delete; + // define move constructor and move assignment operator + DeviceLibrary(DeviceLibrary&&) = default; + DeviceLibrary& operator=(DeviceLibrary&&) = default; + // destructor should be virtual to allow for polymorphic deletion + virtual ~DeviceLibrary() = default; +}; + +/** + * @brief Definition of the dynamic device library. + * @details This class is used to load the QDMI device interface functions + * from a dynamic library at runtime. It inherits from DeviceLibrary and + * overrides the constructor and destructor to open and close the library. + */ +class DynamicDeviceLibrary : public DeviceLibrary { + /// @brief Handle to the dynamic library returned by `dlopen`. + void* libHandle; + + /** + * @brief Returns a reference to the set of open library handles. + * @details This set can be used to check whether a newly opened library + * is already open. + */ + static auto openLibHandles() -> std::unordered_set& { + static std::unordered_set libHandles; + return libHandles; + } + +public: + /** + * @brief Constructs a DynamicDeviceLibrary object. + * @details This constructor loads the QDMI device interface functions + * from the dynamic library specified by `libName` and `prefix`. + * @param libName is the name of the dynamic library to load. + * @param prefix is the prefix used for the function names in the library. + */ + DynamicDeviceLibrary(const std::string& libName, const std::string& prefix); + + /** + * @brief Destructor for the DynamicDeviceLibrary. + * @details This destructor calls the @ref QDMI_device_finalize function if it + * is not null and closes the dynamic library. + */ + ~DynamicDeviceLibrary() override; +}; + +// Macro to load a static symbol from a statically linked library. +// @param prefix is the prefix used for the function names in the library. +// @param symbol is the name of the symbol to load. +#define LOAD_STATIC_SYMBOL(prefix, symbol) \ + { \ + (symbol) = reinterpret_cast(prefix##_QDMI_##symbol); \ + } + +// Macro to define a static library class that inherits from DeviceLibrary. +// It binds all device library functions to the functions of the static library. +// @param prefix is the prefix used for the function names in the library. +#define ADD_STATIC_LIBRARY(prefix) \ + class prefix##DeviceLibrary : public DeviceLibrary { \ + public: \ + prefix##DeviceLibrary() { \ + /* NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) */ \ + /* load the function symbols from the static library */ \ + LOAD_STATIC_SYMBOL(prefix, device_initialize) \ + LOAD_STATIC_SYMBOL(prefix, device_finalize) \ + /* device session interface */ \ + LOAD_STATIC_SYMBOL(prefix, device_session_alloc) \ + LOAD_STATIC_SYMBOL(prefix, device_session_init) \ + LOAD_STATIC_SYMBOL(prefix, device_session_free) \ + LOAD_STATIC_SYMBOL(prefix, device_session_set_parameter) \ + /* device job interface */ \ + LOAD_STATIC_SYMBOL(prefix, device_session_create_device_job) \ + LOAD_STATIC_SYMBOL(prefix, device_job_free) \ + LOAD_STATIC_SYMBOL(prefix, device_job_set_parameter) \ + LOAD_STATIC_SYMBOL(prefix, device_job_query_property) \ + LOAD_STATIC_SYMBOL(prefix, device_job_submit) \ + LOAD_STATIC_SYMBOL(prefix, device_job_cancel) \ + LOAD_STATIC_SYMBOL(prefix, device_job_check) \ + LOAD_STATIC_SYMBOL(prefix, device_job_wait) \ + LOAD_STATIC_SYMBOL(prefix, device_job_get_results) \ + /* device query interface */ \ + LOAD_STATIC_SYMBOL(prefix, device_session_query_device_property) \ + LOAD_STATIC_SYMBOL(prefix, device_session_query_site_property) \ + LOAD_STATIC_SYMBOL(prefix, device_session_query_operation_property) \ + /* NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) */ \ + /* initialize the device */ \ + device_initialize(); \ + } \ + \ + ~prefix##DeviceLibrary() override { \ + /* Check if QDMI_device_finalize is not NULL before calling it. */ \ + if (device_finalize != nullptr) { \ + device_finalize(); \ + } \ + } \ + }; + +// Call the above macro for all static libraries that we want to support. +ADD_STATIC_LIBRARY(MQT_NA) + +/** + * @brief The status of a session. + * @details This enum defines the possible states of a session in the QDMI + * library. A session can be either allocated or initialized. + */ +enum class SessionStatus : uint8_t { + ALLOCATED, ///< The session has been allocated but not initialized + INITIALIZED ///< The session has been initialized and is ready for use +}; +} // namespace qdmi + +/** + * @brief Definition of the QDMI Device. + */ +struct QDMI_Device_impl_d { +private: + /** + * @brief The device library that provides the device interface functions. + * @note This must be a pointer type as we need access to dynamic and static + * libraries that are subclasses of qdmi::DeviceLibrary. + */ + std::unique_ptr library; + /// @brief The device session handle. + QDMI_Device_Session deviceSession = nullptr; + /** + * @brief Map of jobs to their corresponding unique pointers of + * QDMI_Job_impl_d objects. + */ + std::unordered_map> jobs; + +public: + /** + * @brief Constructor for the QDMI device. + * @details This constructor initializes the device session and allocates + * the device session handle. + * @param lib is a unique pointer to the device library that provides the + * device interface functions. + */ + explicit QDMI_Device_impl_d(std::unique_ptr&& lib); + + /** + * @brief Destructor for the QDMI device. + * @details This destructor frees the device session and clears the jobs map. + */ + ~QDMI_Device_impl_d() { + jobs.clear(); + if (deviceSession != nullptr) { + library->device_session_free(deviceSession); + } + } + + /** + * @brief Creates a job for the device. + * @see QDMI_device_create_job + */ + auto createJob(QDMI_Job* job) -> int; + + /** + * @brief Frees the job associated with the device. + * @see QDMI_job_free + */ + auto jobFree(QDMI_Job job) -> void; + + /** + * @brief Queries a device property. + * @see QDMI_device_query_device_property + */ + auto queryDeviceProperty(QDMI_Device_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; + + /** + * @brief Queries a site property. + * @see QDMI_device_query_site_property + */ + auto querySiteProperty(QDMI_Site site, QDMI_Site_Property prop, size_t size, + void* value, size_t* sizeRet) const -> int; + + /** + * @brief Queries an operation property. + * @see QDMI_device_query_operation_property + */ + auto queryOperationProperty(QDMI_Operation operation, size_t numSites, + const QDMI_Site* sites, size_t numParams, + const double* params, + QDMI_Operation_Property prop, size_t size, + void* value, size_t* sizeRet) const -> int; +}; + +/** + * @brief Definition of the QDMI Job. + */ +struct QDMI_Job_impl_d { +private: + /// @brief The device library that provides the device interface functions. + const qdmi::DeviceLibrary* library = nullptr; + /// @brief The device job handle. + QDMI_Device_Job deviceJob = nullptr; + /// @brief The device associated with the job. + QDMI_Device device = nullptr; + +public: + /** + * @brief Constructor for the QDMI job. + * @details This constructor initializes the job with the device job handle + * and the device library. + * @param library is a pointer to the device library that provides the + * device interface functions. + * @param deviceJob is the handle to the device job. + * @param device is the device associated with the job. + */ + explicit QDMI_Job_impl_d(const qdmi::DeviceLibrary* library, + QDMI_Device_Job deviceJob, QDMI_Device device) + : library(library), deviceJob(deviceJob), device(device) {} + + /** + * @brief Destructor for the QDMI job. + * @details This destructor frees the device job handle using the + * @ref QDMI_device_job_free function from the device library. + */ + ~QDMI_Job_impl_d(); + + /** + * @brief Sets a parameter for the job. + * @see QDMI_job_set_parameter + */ + auto setParameter(QDMI_Job_Parameter param, size_t size, + const void* value) const -> int; + + /** + * @brief Queries a property of the job. + * @see QDMI_job_query_property + */ + auto queryProperty(QDMI_Job_Property prop, size_t size, void* value, + size_t* sizeRet) const -> int; + + /** + * @brief Submits the job to the device. + * @see QDMI_job_submit + */ + [[nodiscard]] auto submit() const -> int; + + /** + * @brief Cancels the job. + * @see QDMI_job_cancel + */ + [[nodiscard]] auto cancel() const -> int; + + /** + * @brief Checks the status of the job. + * @see QDMI_job_check + */ + auto check(QDMI_Job_Status* status) const -> int; + + /** + * @brief Waits for the job to complete but at most for the specified + * timeout. + * @see QDMI_job_wait + */ + [[nodiscard]] auto wait(size_t timeout) const -> int; + + /** + * @brief Gets the results of the job. + * @see QDMI_job_get_results + */ + auto getResults(QDMI_Job_Result result, size_t size, void* data, + size_t* sizeRet) const -> int; + + /** + * @brief Frees the job. + * @note This function just forwards to the device's jobFree function. This + * function is needed because the interface only provides the job handle to + * the @ref QDMI_job_free function and the job's device handle is private. + * @see QDMI_job_free + */ + auto free() -> void; +}; + +/** + * @brief Definition of the QDMI Session. + */ +struct QDMI_Session_impl_d { +private: + /// @brief The status of the session. + qdmi::SessionStatus status = qdmi::SessionStatus::ALLOCATED; + /// @brief A pointer to the list of all devices. + const std::vector>* devices; + +public: + /// @brief Constructor for the QDMI session. + explicit QDMI_Session_impl_d( + const std::vector>& devices) + : devices(&devices) {} + + /** + * @brief Initializes the session. + * @see QDMI_session_init + */ + auto init() -> int; + + /** + * @brief Sets a parameter for the session. + * @see QDMI_session_set_parameter + */ + auto setParameter(QDMI_Session_Parameter param, size_t size, + const void* value) const -> int; + + /** + * @brief Queries a session property. + * @see QDMI_session_query_session_property + */ + auto querySessionProperty(QDMI_Session_Property prop, size_t size, + void* value, size_t* sizeRet) const -> int; +}; + +namespace qdmi { +/** + * @brief The MQT QDMI driver class. + * @details This driver loads all statically known and linked QDMI device + * libraries. Additional devices can be added dynamically. + * @note This class is a singleton that manages the QDMI libraries and + * sessions. It is responsible for loading the libraries, allocating sessions, + * and providing access to the devices. + */ +class Driver final { + /// @brief Private constructor to enforce the singleton pattern. + Driver(); + + /** + * @brief Map of sessions to their corresponding unique pointers to + * QDMI_Session_impl_d objects. + */ + std::unordered_map> + sessions; + + /** + * @brief Vector of unique pointers to QDMI_Device_impl_d objects. + */ + std::vector> devices; + +public: + // Delete copy constructors and assignment operators to prevent copying the + // singleton instance. + Driver(const Driver&) = delete; + Driver& operator=(const Driver&) = delete; + + /// @brief Returns the singleton instance. + static auto get() -> Driver& { + static Driver instance; + return instance; + } + + /// @brief Destructor for the Driver class. + ~Driver(); + + /** + * @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. + */ + auto addDynamicDeviceLibrary(const std::string& libName, + const std::string& prefix) -> void; + + /** + * @brief Allocates a new session. + * @see QDMI_session_alloc + */ + auto sessionAlloc(QDMI_Session* session) -> int; + + /** + * @brief Frees a session. + * @see QDMI_session_free + */ + auto sessionFree(QDMI_Session session) -> void; +}; + +} // namespace qdmi diff --git a/json/na/device.json b/json/na/device.json new file mode 100644 index 000000000..ad0cd8d15 --- /dev/null +++ b/json/na/device.json @@ -0,0 +1,151 @@ +{ + "name": "MQT NA QDMI Default Device", + "num_qubits": 100, + "traps": [ + { + "latticeOrigin": { + "x": 0, + "y": 0 + }, + "latticeVector1": { + "vector": { + "x": 1, + "y": 0 + }, + "repeat": 10 + }, + "latticeVector2": { + "vector": { + "x": 0, + "y": 1 + }, + "repeat": 10 + }, + "sublatticeOffsets": [ + { + "x": 0, + "y": 0 + } + ] + } + ], + "minAtomDistance": 1, + "globalSingleQubitOperations": [ + { + "name": "RY", + "region": { + "origin": { + "x": 0, + "y": 0 + }, + "size": { + "width": 10, + "height": 10 + } + }, + "duration": 100, + "fidelity": 0.99, + "numParameters": 1 + } + ], + "globalMultiQubitOperations": [ + { + "name": "CZ", + "region": { + "origin": { + "x": 0, + "y": 0 + }, + "size": { + "width": 10, + "height": 2 + } + }, + "interactionRadius": 2, + "blockingRadius": 4, + "duration": 1, + "fidelity": 0.995, + "idlingFidelity": 0.998, + "numQubits": 2, + "numParameters": 0 + } + ], + "localSingleQubitOperations": [ + { + "name": "RZ", + "region": { + "origin": { + "x": 0, + "y": 0 + }, + "size": { + "width": 10, + "height": 10 + } + }, + "numRows": 10, + "numColumns": 10, + "duration": 2, + "fidelity": 0.999, + "numParameters": 1 + } + ], + "localMultiQubitOperations": [ + { + "name": "CZ", + "region": { + "origin": { + "x": 0, + "y": 0 + }, + "size": { + "width": 10, + "height": 10 + } + }, + "interactionRadius": 2, + "blockingRadius": 4, + "numRows": 10, + "numColumns": 10, + "duration": 1, + "fidelity": 0.994, + "numQubits": 2, + "numParameters": 0 + } + ], + "shuttlingUnits": [ + { + "name": "AOD", + "region": { + "origin": { + "x": 0, + "y": 0 + }, + "size": { + "width": 10, + "height": 10 + } + }, + "numRows": 10, + "numColumns": 10, + "movingSpeed": 55, + "loadDuration": 20, + "storeDuration": 20, + "loadFidelity": 0.99, + "storeFidelity": 0.99, + "numParameters": 0 + } + ], + "decoherenceTimes": { + "t1": 100000000, + "t2": 1500000 + }, + "lengthUnit": { + "value": 1, + "unit": "um" + }, + "timeUnit": { + "value": 1, + "unit": "us" + } +} diff --git a/proto/na/device.proto b/proto/na/device.proto new file mode 100644 index 000000000..05076953e --- /dev/null +++ b/proto/na/device.proto @@ -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 + +syntax = "proto3"; + +package na; + +//===----------------------------------------------------------------------===// +// INFORMATION +//===----------------------------------------------------------------------===// +// For documentation of the fields, see the documentation page on +// the neutral atom QDMI device at `docs/na_qdmi_device.md`. +//===----------------------------------------------------------------------===// + +message Device { + string name = 1; + uint64 num_qubits = 2; + + message Vector { + int64 x = 1; + int64 y = 2; + } + message Lattice { + Vector lattice_origin = 1; + message LatticeVector { + Vector vector = 1; + uint64 repeat = 2; + } + LatticeVector lattice_vector_1 = 2; + LatticeVector lattice_vector_2 = 3; + repeated Vector sublattice_offsets = 4; + } + repeated Lattice traps = 3; + uint64 min_atom_distance = 4; + + message Region { + Vector origin = 1; + + message Size { + uint64 width = 1; + uint64 height = 2; + } + Size size = 2; + } + message GlobalSingleQubitOperation { + string name = 1; + Region region = 2; + uint64 duration = 3; + double fidelity = 4; + uint64 num_parameters = 5; + } + repeated GlobalSingleQubitOperation global_single_qubit_operations = 5; + + message GlobalMultiQubitOperation { + string name = 1; + Region region = 2; + double interaction_radius = 3; + double blocking_radius = 4; + uint64 duration = 5; + double fidelity = 6; + double idling_fidelity = 7; + uint64 num_qubits = 8; + uint64 num_parameters = 9; + } + repeated GlobalMultiQubitOperation global_multi_qubit_operations = 6; + + message LocalSingleQubitOperation { + string name = 1; + Region region = 2; + uint64 num_rows = 3; + uint64 num_columns = 4; + uint64 duration = 5; + double fidelity = 6; + uint64 num_parameters = 7; + } + repeated LocalSingleQubitOperation local_single_qubit_operations = 7; + + message LocalMultiQubitOperation { + string name = 1; + Region region = 2; + double interaction_radius = 3; + double blocking_radius = 4; + uint64 num_rows = 5; + uint64 num_columns = 6; + uint64 duration = 7; + double fidelity = 8; + uint64 num_qubits = 9; + uint64 num_parameters = 10; + } + repeated LocalMultiQubitOperation local_multi_qubit_operations = 8; + + message ShuttlingUnit { + string name = 1; + Region region = 2; + uint64 num_rows = 3; + uint64 num_columns = 4; + double moving_speed = 5; + uint64 load_duration = 6; + uint64 store_duration = 7; + double load_fidelity = 8; + double store_fidelity = 9; + uint64 num_parameters = 10; + } + repeated ShuttlingUnit shuttling_units = 9; + + message DecoherenceTimes { + uint64 t1 = 1; + uint64 t2 = 2; + } + DecoherenceTimes decoherence_times = 10; + + message Unit { + uint64 value = 1; + string unit = 2; + } + Unit length_unit = 11; + Unit time_unit = 12; +} diff --git a/pyproject.toml b/pyproject.toml index 2d0048635..3a9cc12ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -92,6 +92,9 @@ build.targets = [ "mqt-core-dd-bindings", ] +# Only install the Python package component +install.components = ["mqt-core_Python", "mqt-core_Development", "mqt-core_Runtime"] + metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" sdist.include = ["python/mqt/core/_version.py"] sdist.exclude = [ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d585ce242..b325d5532 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -80,6 +80,9 @@ add_subdirectory(zx) # add NA library add_subdirectory(na) +# add QDMI package library +add_subdirectory(qdmi) + # Installation instructions for the main library if(MQT_CORE_INSTALL) include(CMakePackageConfigHelpers) @@ -93,9 +96,13 @@ if(MQT_CORE_INSTALL) VERSION ${PROJECT_VERSION} COMPATIBILITY SameMinorVersion) - install(FILES ${MQT_CORE_CMAKE_PROJECT_CONFIG_FILE} ${MQT_CORE_CMAKE_VERSION_CONFIG_FILE} - DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR}) + install( + FILES ${MQT_CORE_CMAKE_PROJECT_CONFIG_FILE} ${MQT_CORE_CMAKE_VERSION_CONFIG_FILE} + DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} + COMPONENT ${MQT_CORE_TARGET_NAME}_Development) + # FILE_SET should be aligned with LIBRARY and ARCHIVE, so we turn cmake-format off + # cmake-format: off install( TARGETS ${MQT_CORE_TARGETS} EXPORT ${MQT_CORE_TARGETS_EXPORT_NAME} @@ -105,8 +112,10 @@ if(MQT_CORE_INSTALL) NAMELINK_COMPONENT ${MQT_CORE_TARGET_NAME}_Development ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT ${MQT_CORE_TARGET_NAME}_Development - FILE_SET HEADERS - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mqt-core) + FILE_SET HEADERS + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mqt-core + COMPONENT ${MQT_CORE_TARGET_NAME}_Development) + # cmake-format: on install( EXPORT ${MQT_CORE_TARGETS_EXPORT_NAME} @@ -122,5 +131,6 @@ if(MQT_CORE_INSTALL) ${PROJECT_SOURCE_DIR}/cmake/PackageAddTest.cmake ${PROJECT_SOURCE_DIR}/cmake/PreventInSourceBuilds.cmake ${PROJECT_SOURCE_DIR}/cmake/StandardProjectSettings.cmake - DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR}) + DESTINATION ${MQT_CORE_CONFIG_INSTALL_DIR} + COMPONENT ${MQT_CORE_TARGET_NAME}_Development) endif() diff --git a/src/na/CMakeLists.txt b/src/na/CMakeLists.txt index 7d79a575f..9778f9019 100644 --- a/src/na/CMakeLists.txt +++ b/src/na/CMakeLists.txt @@ -8,8 +8,10 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-na) # collect headers and source files - file(GLOB_RECURSE NA_HEADERS ${MQT_CORE_INCLUDE_BUILD_DIR}/na/**.hpp) - file(GLOB_RECURSE NA_SOURCES **.cpp) + file(GLOB NA_HEADERS ${MQT_CORE_INCLUDE_BUILD_DIR}/na/*.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/na/entities/*.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/na/operations/*.hpp) + file(GLOB NA_SOURCES *.cpp entities/*.cpp operations/*.cpp) # create the library target (initially empty) add_mqt_core_library(${MQT_CORE_TARGET_NAME}-na ALIAS_NAME NA) @@ -50,3 +52,6 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-na) ${MQT_CORE_TARGETS} ${MQT_CORE_TARGET_NAME}-na PARENT_SCOPE) endif() + +# add subdirectories +add_subdirectory(device) diff --git a/src/na/device/App.cpp b/src/na/device/App.cpp new file mode 100644 index 000000000..8962960a2 --- /dev/null +++ b/src/na/device/App.cpp @@ -0,0 +1,352 @@ +/* + * 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 "na/device/Generator.hpp" +#include "spdlog/spdlog.h" + +#include +#include +#include + +namespace { +/** + * Prints the usage information for the command line tool. + * @param programName is the name of the program executable. + */ +auto printUsage(const std::string& programName) -> void { + std::cout + << "Generator of a header file from a JSON file for neutral atom QDMI\n" + "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 no output " + " file is " + " specified, the schema is printed 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"; +} + +/** + * Prints the version information for the command line tool. + */ +auto printVersion() -> void { + std::cout << "MQT QDMI NA Device Generator 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; +}; + +/** + * Parses the command line arguments and returns an Arguments struct. + * @param args is the vector of command line arguments. + * @returns the parsed arguments as an Arguments struct and an index indicating + * the position of the first sub-command argument. + * @throws std::invalid_argument if the value after an option is missing. + */ +auto parseArguments(const std::vector& args) + -> std::pair { + Arguments arguments; + arguments.programName = + args.empty() ? "mqt-core-na-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; // No more arguments for schema command + } else if (arg == "validate") { + arguments.command = Command::Validate; + break; // No more arguments for validate command + } else if (arg == "generate") { + arguments.command = Command::Generate; + break; // No more arguments for generate command + } else { + throw std::invalid_argument("Unknown argument: " + arg); + } + ++i; + } + return {arguments, i + 1}; +} + +/** + * Parses the command line arguments for the schema command and returns a + * SchemaArguments struct. + * @param args is the vector of command line arguments. + * @param i is the index to the first sub-command argument within @p args + * @return Parsed schema arguments as a SchemaArguments struct. + */ +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; +} + +/** + * Parses the command line arguments for the validate command and returns a + * ValidateArguments struct. + * @param args is the vector of command line arguments. + * @param i is the index to the first sub-command argument within @p args + * @return Parsed validate arguments as a ValidateArguments struct. + */ +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 (arg == "-o" || arg == "--output") { + if (++i >= args.size()) { + throw std::invalid_argument("Missing value for output option."); + } + validateArgs.jsonFile = args.at(i); + } else { + validateArgs.jsonFile = arg; + } + ++i; + } + return validateArgs; +} + +/** + * Parses the command line arguments for the generate command and returns a + * GenerateArguments struct. + * @param args is the vector of command line arguments. + * @param i is the index to the first sub-command argument within @p args + * @return Parsed generate arguments as a GenerateArguments struct. + */ +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 { + generateArgs.jsonFile = arg; + } + ++i; + } + return generateArgs; +} +} // namespace + +/** + * @brief Main function that parses command-line-arguments and processes the + * JSON. + * @details This function handles the command line arguments, checks for help + * and version flags, and processes the JSON file or schema file as specified by + * the user. Either a JSON file or a schema file must be provided. If no output + * file is specified, the JSON file is parsed but no header file is generated. + * + * @param argc is the number of command line arguments. + * @param argv is the array of command line arguments. + */ +int main(int argc, char* argv[]) { + const std::vector argVec(argv, argv + argc); + const auto& [args, i] = parseArguments(argVec); + if (args.help) { + printUsage(args.programName); + return 0; + } + if (args.version) { + printVersion(); + return 0; + } + if (!args.command.has_value()) { + printUsage(args.programName); + return 1; + } + switch (args.command.value()) { + case Command::Schema: { + const auto& schemaArgs = parseSchemaArguments(argVec, i); + if (schemaArgs.help) { + printSchemaUsage(args.programName); + return 0; + } + try { + if (schemaArgs.outputFile.has_value()) { + na::writeJSONSchema(schemaArgs.outputFile.value()); + } else { + na::writeJSONSchema(std::cout); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error generating JSON schema: {}", e.what()); + return 1; + } + break; + } + case Command::Validate: { + const auto& validateArgs = parseValidateArguments(argVec, i); + if (validateArgs.help) { + printValidateUsage(args.programName); + return 0; + } + try { + if (validateArgs.jsonFile.has_value()) { + std::ignore = na::readJSON(validateArgs.jsonFile.value()); + } else { + std::ignore = na::readJSON(std::cin); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error validating JSON: {}", e.what()); + return 1; + } + break; + } + case Command::Generate: { + const auto& generateArgs = parseGenerateArguments(argVec, i); + if (generateArgs.help) { + printGenerateUsage(args.programName); + return 0; + } + try { + na::Device device; + if (generateArgs.jsonFile.has_value()) { + device = na::readJSON(generateArgs.jsonFile.value()); + } else { + device = na::readJSON(std::cin); + } + if (generateArgs.outputFile.has_value()) { + na::writeHeader(device, generateArgs.outputFile.value()); + } else { + na::writeHeader(device, std::cout); + } + } catch (const std::exception& e) { + SPDLOG_ERROR("Error generating header file: {}", e.what()); + return 1; + } + } + } + return 0; +} diff --git a/src/na/device/CMakeLists.txt b/src/na/device/CMakeLists.txt new file mode 100644 index 000000000..354808fd1 --- /dev/null +++ b/src/na/device/CMakeLists.txt @@ -0,0 +1,148 @@ +# 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}-na-device-gen) + +# If the target is not already defined +if(NOT TARGET ${TARGET_NAME}) + # ------------------------------------------------------------------------------ + # Generate C++ source files from .proto + # ------------------------------------------------------------------------------ + + # Proto files + set(PROTO_FILE ${PROJECT_SOURCE_DIR}/proto/na/device.proto) + + # Set paths + set(PROTO_SRC ${CMAKE_CURRENT_BINARY_DIR}/device.pb.cc) + set(PROTO_HDR ${CMAKE_CURRENT_BINARY_DIR}/device.pb.h) + + # Generate .pb.cc and .pb.h + add_custom_command( + OUTPUT ${PROTO_SRC} ${PROTO_HDR} + COMMAND protobuf::protoc ARGS --proto_path=${PROJECT_SOURCE_DIR}/proto/na + --cpp_out=${CMAKE_CURRENT_BINARY_DIR} ${PROTO_FILE} + DEPENDS ${PROTO_FILE} + COMMENT "Generating C++ source from ${PROTO_FILE}") + add_custom_target(generate_proto_files DEPENDS ${PROTO_SRC} ${PROTO_HDR}) + + # Add executable for device generation + add_executable(${TARGET_NAME}) + add_dependencies(${TARGET_NAME} generate_proto_files) + + # add sources to target + target_sources(${TARGET_NAME} PRIVATE Generator.cpp App.cpp ${PROTO_SRC}) + + # add headers using file sets + target_sources( + ${TARGET_NAME} + PUBLIC FILE_SET + HEADERS + BASE_DIRS + ${MQT_CORE_INCLUDE_BUILD_DIR} + ${CMAKE_BINARY_DIR}/src + FILES + ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Generator.hpp + ${PROTO_HDR}) + + # Link Protobuf, QDMI, spdlog, and project warnings + target_link_libraries( + ${TARGET_NAME} + PUBLIC protobuf::libprotobuf spdlog::spdlog + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) + + # set versioning information + set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${PROJECT_VERSION} EXPORT_NAME + CoreNaDeviceGen) + # place in the bin directory + set_target_properties(${TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${CMAKE_BINARY_DIR}/bin") + + # Make version available + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_VERSION="${MQT_CORE_VERSION}") + + # add to list of MQT core targets + set(MQT_CORE_TARGETS + ${MQT_CORE_TARGETS} ${TARGET_NAME} + PARENT_SCOPE) + + # Create an alias for the target + add_executable(MQT::CoreNADeviceGen ALIAS ${TARGET_NAME}) +endif() + +# Set target name +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-na-device) + +# Set prefix for QDMI +set(QDMI_PREFIX "MQT_NA") + +# If the target is not already defined +if(NOT TARGET ${TARGET_NAME}) + + # Set paths + set(JSON_FILE ${PROJECT_SOURCE_DIR}/json/na/device.json) + set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/Device.hpp) + + # Generate definitions for device + add_custom_command( + OUTPUT ${DEVICE_HDR} + COMMAND MQT::CoreNADeviceGen ARGS generate --output ${DEVICE_HDR} ${JSON_FILE} + DEPENDS ${JSON_FILE} + COMMENT "Generating C++ header from ${JSON_FILE}") + add_custom_target(generate_na_device_header DEPENDS ${DEVICE_HDR}) + + # Add library + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME NaQDMI) + add_dependencies(${TARGET_NAME} generate_na_device_header) + + # Generate prefixed QDMI headers + generate_prefixed_qdmi_headers(${QDMI_PREFIX}) + file(GLOB_RECURSE QDMI_HDRS ${CMAKE_CURRENT_BINARY_DIR}/include/mqt_na_qdmi/**.hpp) + + # 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 + ${CMAKE_CURRENT_BINARY_DIR}/include + FILES + ${DEVICE_HDR} + ${QDMI_HDRS}) + + # add link libraries + target_link_libraries( + ${TARGET_NAME} + PUBLIC qdmi::qdmi spdlog::spdlog + PRIVATE MQT::ProjectOptions MQT::ProjectWarnings) + + # set c++ standard + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_17) + + # set versioning information + set_target_properties( + ${TARGET_NAME} + PROPERTIES VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + EXPORT_NAME CoreNaQDMI) + + # 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}") + target_compile_definitions(${TARGET_NAME} PRIVATE MQT_CORE_VERSION="${MQT_CORE_VERSION}") + + # Generate additional alias for the target + add_library(qdmi::mqt_na_device ALIAS ${TARGET_NAME}) +endif() diff --git a/src/na/device/Device.cpp b/src/na/device/Device.cpp new file mode 100644 index 000000000..63c11e76d --- /dev/null +++ b/src/na/device/Device.cpp @@ -0,0 +1,443 @@ +/* + * 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 neutral atom devices. + */ + +#include "mqt_na_qdmi/device.h" + +#include "Device.hpp" + +#include +#include + +namespace { +/// The status of the session. +enum class SessionStatus : uint8_t { + ALLOCATED, ///< The session has been allocated but not initialized + INITIALIZED, ///< The session has been initialized and is ready for use +}; + +/// The type of operation. +enum class OperationType : uint8_t { + GLOBAL_SINGLE_QUBIT, ///< Global single-qubit operation + GLOBAL_MULTI_QUBIT, ///< Global multi-qubit operation + LOCAL_SINGLE_QUBIT, ///< Local single-qubit operation + LOCAL_MULTI_QUBIT, ///< Local multi-qubit operation + SHUTTLING_LOAD, ///< Shuttling load operation + SHUTTLING_MOVE, ///< Shuttling move operation + SHUTTLING_STORE, ///< Shuttling store operation +}; + +/** + * @brief Checks if the operation type is a shuttling operation. + * @param type The operation type to check. + * @return true if the operation type is a shuttling operation, false + * otherwise. + */ +[[nodiscard]] auto isShuttling(const OperationType type) -> bool { + switch (type) { + case OperationType::SHUTTLING_LOAD: + case OperationType::SHUTTLING_MOVE: + case OperationType::SHUTTLING_STORE: + return true; + default: + return false; + } +} +} // namespace + +/** + * @brief Implementation of the MQT_NA_QDMI_Device_Session structure. + */ +struct MQT_NA_QDMI_Device_Session_impl_d { + SessionStatus status = SessionStatus::ALLOCATED; +}; + +/** + * @brief Implementation of the MQT_NA_QDMI_Device_Job structure. + */ +struct MQT_NA_QDMI_Device_Job_impl_d {}; + +/** + * @brief Implementation of the MQT_NA_QDMI_Device_Site structure. + */ +struct MQT_NA_QDMI_Site_impl_d { + size_t id; ///< Unique identifier of the site + int64_t x; ///< X coordinate of the site in the lattice + int64_t y; ///< Y coordinate of the site in the lattice +}; + +/** + * @brief Implementation of the MQT_NA_QDMI_Device_Operation structure. + */ +struct MQT_NA_QDMI_Operation_impl_d { + std::string name; ///< Name of the operation + OperationType type; ///< Type of the operation + size_t numParameters; ///< Number of parameters for the operation + /** + * @brief Number of qubits involved in the operation + * @note This number is only valid if the operation is a multi-qubit + * operation. + */ + size_t numQubits; + double duration; ///< Duration of the operation in microseconds + double fidelity; ///< Fidelity of the operation +}; + +namespace { + +/** + * @brief Provides access to the device name. + * @returns a reference to a static variables that stores the + * device name. + */ +[[nodiscard]] auto name() -> std::string& { + static std::string name; + return name; +} + +/** + * @brief Provides access to the number of qubits in the device. + * @returns a reference to a static variable that stores the number of qubits. + */ +[[nodiscard]] auto qubitsNum() -> size_t& { + static size_t qubitsNum = 0; + return qubitsNum; +} + +/** + * @brief Provides access to the lists of sites and operations. + * @returns a reference to a static vector of unique pointers to + * @ref MQT_NA_QDMI_Site_impl_d. + */ +[[nodiscard]] auto sites() + -> std::vector>& { + static std::vector> sites; + return sites; +} + +/** + * @brief Provides access to the list of operations. + * @returns a reference to a static vector of unique pointers to + * @ref MQT_NA_QDMI_Operation_impl_d. + */ +[[nodiscard]] auto operations() + -> std::vector>& { + static std::vector> operations; + return operations; +} + +/// Collects decoherence times for the device. +struct DecoherenceTimes { + double t1; ///< T1 time in microseconds + double t2; ///< T2 time in microseconds +}; + +/** + * @brief Provides access to the decoherence times of the device. + * @details This function returns a reference to a static instance of + * @ref DecoherenceTimes, which is used to store the decoherence times for the + * device. + * @returns A reference to a static instance of @ref DecoherenceTimes. + */ +[[nodiscard]] auto decoherenceTimes() -> DecoherenceTimes& { + static DecoherenceTimes decoherenceTimes; + return decoherenceTimes; +} + +/** + * @brief Provides access to the list of device sessions. + * @return a reference to a static vector of unique pointers to + * MQT_NA_QDMI_Device_Session_impl_d. + */ +[[nodiscard]] auto sessions() + -> std::unordered_map>& { + static std::unordered_map> + sessions; + return sessions; +} +} // namespace + +// NOLINTBEGIN(bugprone-macro-parentheses) +#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ + size, value, size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < sizeof(prop_type)) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + *static_cast(value) = prop_value; \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = sizeof(prop_type); \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#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(static_cast(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) + +int MQT_NA_QDMI_device_initialize() { + INITIALIZE_NAME(name()); + INITIALIZE_QUBITSNUM(qubitsNum()); + INITIALIZE_SITES(sites()); + INITIALIZE_OPERATIONS(operations()); + INITIALIZE_T1(decoherenceTimes().t1); + INITIALIZE_T2(decoherenceTimes().t2); + return QDMI_SUCCESS; +} + +int MQT_NA_QDMI_device_finalize() { + while (!sessions().empty()) { + MQT_NA_QDMI_device_session_free(sessions().begin()->first); + } + return QDMI_SUCCESS; +} + +int MQT_NA_QDMI_device_session_alloc(MQT_NA_QDMI_Device_Session* session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto uniqueSession = std::make_unique(); + *session = sessions() + .emplace(uniqueSession.get(), std::move(uniqueSession)) + .first->first; + return QDMI_SUCCESS; +} + +int MQT_NA_QDMI_device_session_init(MQT_NA_QDMI_Device_Session session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->status != SessionStatus::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + session->status = SessionStatus::INITIALIZED; + return QDMI_SUCCESS; +} + +void MQT_NA_QDMI_device_session_free(MQT_NA_QDMI_Device_Session session) { + sessions().erase(session); +} + +int MQT_NA_QDMI_device_session_set_parameter( + MQT_NA_QDMI_Device_Session session, QDMI_Device_Session_Parameter param, + const size_t size, const void* value) { + if (session == nullptr || (value != nullptr && size == 0) || + param >= QDMI_DEVICE_SESSION_PARAMETER_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->status != SessionStatus::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_session_create_device_job( + MQT_NA_QDMI_Device_Session session, MQT_NA_QDMI_Device_Job* job) { + if (session == nullptr || job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->status != SessionStatus::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +void MQT_NA_QDMI_device_job_free(MQT_NA_QDMI_Device_Job /* unused */) {} + +int MQT_NA_QDMI_device_job_set_parameter(MQT_NA_QDMI_Device_Job job, + const QDMI_Device_Job_Parameter param, + const size_t size, const void* value) { + if (job == nullptr || (value != nullptr && size == 0) || + param >= QDMI_DEVICE_JOB_PARAMETER_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_query_property( + MQT_NA_QDMI_Device_Job job, const QDMI_Device_Job_Property prop, + const size_t size, + // NOLINTNEXTLINE(readability-non-const-parameter) + void* value, size_t* /* unused */) { + if (job == nullptr || (value != nullptr && size == 0) || + prop >= QDMI_DEVICE_JOB_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_submit(MQT_NA_QDMI_Device_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_cancel(MQT_NA_QDMI_Device_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_check( + MQT_NA_QDMI_Device_Job job, + // NOLINTNEXTLINE(readability-non-const-parameter) + QDMI_Job_Status* status) { + if (job == nullptr || status == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_wait(MQT_NA_QDMI_Device_Job job, + const size_t /* unused */) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_job_get_results(MQT_NA_QDMI_Device_Job job, + QDMI_Job_Result result, + const size_t size, void* data, + [[maybe_unused]] size_t* sizeRet) { + if (job == nullptr || (data != nullptr && size == 0) || + result >= QDMI_JOB_RESULT_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_session_query_device_property( + MQT_NA_QDMI_Device_Session session, const QDMI_Device_Property prop, + const size_t size, void* value, size_t* sizeRet) { + if (session == nullptr || (value != nullptr && size == 0) || + prop >= QDMI_DEVICE_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (session->status != SessionStatus::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name().c_str(), prop, size, + value, sizeRet) + ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_VERSION, MQT_CORE_VERSION, prop, + size, value, sizeRet) + 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) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_SITES, MQT_NA_QDMI_Site, sites(), prop, + size, value, sizeRet) + ADD_LIST_PROPERTY(QDMI_DEVICE_PROPERTY_OPERATIONS, MQT_NA_QDMI_Operation, + operations(), prop, size, value, sizeRet) + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_session_query_site_property( + MQT_NA_QDMI_Device_Session session, MQT_NA_QDMI_Site site, + const QDMI_Site_Property prop, const size_t size, void* value, + size_t* sizeRet) { + if (session == nullptr || site == nullptr || + (value != nullptr && size == 0) || prop >= QDMI_SITE_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, site->id, prop, + size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_T1, double, + decoherenceTimes().t1, prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_T2, double, + decoherenceTimes().t2, prop, size, value, sizeRet) + return QDMI_ERROR_NOTSUPPORTED; +} + +int MQT_NA_QDMI_device_session_query_operation_property( + MQT_NA_QDMI_Device_Session session, MQT_NA_QDMI_Operation operation, + const size_t numSites, const MQT_NA_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 || operation == nullptr || + (sites != nullptr && numSites == 0) || + (params != nullptr && numParams == 0) || + (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + ADD_STRING_PROPERTY(QDMI_OPERATION_PROPERTY_NAME, operation->name.c_str(), + prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_PARAMETERSNUM, size_t, + operation->numParameters, prop, size, value, + sizeRet) + if (operation->type != OperationType::SHUTTLING_MOVE) { + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_DURATION, double, + operation->duration, prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_FIDELITY, double, + operation->fidelity, prop, size, value, sizeRet) + } else { + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_DURATION, double, 0.0, + prop, size, value, sizeRet) + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_FIDELITY, double, 1.0, + prop, size, value, sizeRet) + } + if (!isShuttling(operation->type)) { + ADD_SINGLE_VALUE_PROPERTY(QDMI_OPERATION_PROPERTY_QUBITSNUM, size_t, + operation->numQubits, prop, size, value, sizeRet) + } + return QDMI_ERROR_NOTSUPPORTED; +} diff --git a/src/na/device/Generator.cpp b/src/na/device/Generator.cpp new file mode 100644 index 000000000..38f5f1e34 --- /dev/null +++ b/src/na/device/Generator.cpp @@ -0,0 +1,364 @@ +/* + * 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 neutral atom devices. + */ + +#include "na/device/Generator.hpp" + +#include "na/device/device.pb.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace na { +namespace { +/** + * @brief Populates all repeated fields of the message type in the given + * Protobuf message with empty messages. + * @param message The Protobuf message to populate. + * @throws std::runtime_error if a repeated field has an unsupported type, i.e., + * not a message type. + * @note This is a recursive auxiliary function used by @ref writeJSONSchema + */ +auto populateRepeatedFields(google::protobuf::Message* message) -> void { + const google::protobuf::Descriptor* descriptor = message->GetDescriptor(); + const google::protobuf::Reflection* reflection = message->GetReflection(); + + for (int i = 0; i < descriptor->field_count(); ++i) { + const google::protobuf::FieldDescriptor* field = descriptor->field(i); + if (field->is_repeated()) { + if (field->type() == google::protobuf::FieldDescriptor::TYPE_MESSAGE) { + populateRepeatedFields(reflection->AddMessage(message, field)); + } else { + std::stringstream ss; + ss << "Unsupported repeated field type in device configuration: " + << field->cpp_type(); + throw std::runtime_error(ss.str()); + } + } else if (field->type() == + google::protobuf::FieldDescriptor::TYPE_MESSAGE) { + // Message fields must be explicitly initialized such that they appear in + // the written JSON schema, primitive fields are automatically + // initialized + populateRepeatedFields(reflection->MutableMessage(message, field)); + } + } +} + +/** + * @brief Increments the indices in lexicographic order. + * @details This function increments the first index that is less than its + * limit, resets all previous indices to zero. + * @param indices The vector of indices to increment. + * @param limits The limits for each index. + * @returns true if the increment was successful, false if all indices have + * reached their limits. + */ +[[nodiscard]] auto increment(std::vector& indices, + const std::vector& limits) -> bool { + size_t i = 0; + for (; i < indices.size() && indices[i] == limits[i]; ++i) { + } + if (i == indices.size()) { + // all indices are at their limits + return false; + } + for (size_t j = 0; j < i; ++j) { + indices[j] = 0; // Reset all previous indices + } + ++indices[i]; // Increment the next index + return true; +} + +/** + * Computes the time unit factor based on the device configuration. + * @param device is the Protobuf message containing the device configuration. + * @returns a factor every time value must be multiplied with to convert it to + * microseconds. + */ +[[nodiscard]] auto getTimeUnit(const Device& device) -> double { + if (device.time_unit().unit() == "us") { + return static_cast(device.time_unit().value()); + } + if (device.time_unit().unit() == "ns") { + return static_cast(device.time_unit().value()) * 1e-3; + } + std::stringstream ss; + ss << "Unsupported time unit: " << device.time_unit().unit(); + throw std::runtime_error(ss.str()); +} + +/** + * Computes the length unit factor based on the device configuration. + * @param device is the Protobuf message containing the device configuration. + * @returns a factor every length value must be multiplied with to convert it to + * micrometers. + */ +[[nodiscard]] auto getLengthUnit(const Device& device) -> double { + if (device.length_unit().unit() == "um") { + return static_cast(device.length_unit().value()); + } + if (device.length_unit().unit() == "nm") { + return static_cast(device.length_unit().value()) * 1e-3; + } + std::stringstream ss; + ss << "Unsupported length unit: " << device.length_unit().unit(); + throw std::runtime_error(ss.str()); +} + +/** + * @brief Writes the name from the Protobuf message. + * @param device The Protobuf message containing the device configuration. + * @param os The output stream to write the sites to. + */ +auto writeName(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_NAME(var) var = \"" << device.name() << "\"\n"; +} + +/** + * @brief Writes the qubits number from the Protobuf message. + * @param device The Protobuf message containing the device configuration. + * @param os The output stream to write the sites to. + */ +auto writeQubitsNum(const Device& device, std::ostream& os) -> void { + os << "#define INITIALIZE_QUBITSNUM(var) var = " << device.num_qubits() + << "UL\n"; +} + +/** + * @brief Writes the sites from the Protobuf message. + * @param device The Protobuf message containing the device configuration. + * @param os The output stream to write the sites to. + */ +auto writeSites(const Device& device, std::ostream& os) -> void { + size_t count = 0; + os << "#define INITIALIZE_SITES(var) var.clear();"; + for (const auto& lattice : device.traps()) { + const auto originX = lattice.lattice_origin().x(); + const auto originY = lattice.lattice_origin().y(); + const std::vector limits{ + static_cast(lattice.lattice_vector_1().repeat()), + static_cast(lattice.lattice_vector_2().repeat())}; + std::vector indices(2, 0UL); + for (bool loop = true; loop; loop = increment(indices, limits)) { + // For every sublattice offset, add a site for repetition indices + for (const auto& offset : lattice.sublattice_offsets()) { + const auto id = count++; + auto x = originX + offset.x(); + auto y = originY + offset.y(); + x += static_cast(indices[0]) * + lattice.lattice_vector_1().vector().x(); + y += static_cast(indices[0]) * + lattice.lattice_vector_1().vector().y(); + x += static_cast(indices[1]) * + lattice.lattice_vector_2().vector().x(); + y += static_cast(indices[1]) * + lattice.lattice_vector_2().vector().y(); + os << "\\\n " + "var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Site_impl_d{" + << id << ", " << x << ", " << y << "}));"; + } + } + } + os << "\n"; +} + +/** + * @brief Imports the operations from the Protobuf message into the device. + * @param device The Protobuf message containing the device configuration. + * @param timeUnit The time unit to use for the operations. + * @param os The output stream to write the sites to. + */ +auto writeOperations(const Device& device, const double timeUnit, + std::ostream& os) -> void { + os << "#define INITIALIZE_OPERATIONS(var) var.clear();"; + for (const auto& operation : device.global_single_qubit_operations()) { + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << "\", OperationType::GLOBAL_SINGLE_QUBIT, " + << operation.num_parameters() << ", 1, " + << static_cast(operation.duration()) * timeUnit << ", " + << operation.fidelity() << "}));"; + } + for (const auto& operation : device.global_multi_qubit_operations()) { + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << "\", OperationType::GLOBAL_MULTI_QUBIT, " + << operation.num_parameters() << ", " << operation.num_qubits() << ", " + << static_cast(operation.duration()) * timeUnit << ", " + << operation.fidelity() << "}));"; + } + for (const auto& operation : device.local_single_qubit_operations()) { + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << "\", OperationType::LOCAL_SINGLE_QUBIT, " + << operation.num_parameters() << ", 1, " + << static_cast(operation.duration()) * timeUnit << ", " + << operation.fidelity() << "}));"; + } + for (const auto& operation : device.local_multi_qubit_operations()) { + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << "\", OperationType::LOCAL_MULTI_QUBIT, " + << operation.num_parameters() << ", " << operation.num_qubits() << ", " + << static_cast(operation.duration()) * timeUnit << ", " + << operation.fidelity() << "}));"; + } + for (const auto& operation : device.shuttling_units()) { + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << " (Load)\", OperationType::SHUTTLING_LOAD, " + << operation.num_parameters() << ", 0, " + << static_cast(operation.load_duration()) * timeUnit << ", " + << operation.load_fidelity() << "}));"; + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << " (Move)\", OperationType::SHUTTLING_MOVE, " + << operation.num_parameters() << ", 0, 0, 0}));"; + os << "\\\n" + " var.emplace_back(std::make_unique(" + "MQT_NA_QDMI_Operation_impl_d{\"" + << operation.name() << " (Store)\", OperationType::SHUTTLING_STORE, " + << operation.num_parameters() << ", 0, " + << static_cast(operation.store_duration()) * timeUnit << ", " + << operation.store_fidelity() << "}));"; + } + os << "\n"; +} + +/** + * @brief Writes the decoherence times from the Protobuf message. + * @param device The Protobuf message containing the device configuration. + * @param timeUnit The time unit to use for the decoherence times. + * @param os The output stream to write the sites to. + */ +auto writeDecoherenceTimes(const Device& device, const double timeUnit, + std::ostream& os) -> void { + os << "#define INITIALIZE_T1(var) var = " + << static_cast(device.decoherence_times().t1()) * timeUnit + << ";\n"; + os << "#define INITIALIZE_T2(var) var = " + << static_cast(device.decoherence_times().t2()) * timeUnit + << ";\n"; +} +} // namespace + +auto writeJSONSchema(std::ostream& os) -> void { + // Create a default device configuration + Device device; + + // Fill each repeated field with an empty message + populateRepeatedFields(&device); + + // Set print options + google::protobuf::util::JsonPrintOptions options; + options.always_print_fields_with_no_presence = true; + + // Convert to JSON + std::string json; + const auto status = + google::protobuf::util::MessageToJsonString(device, &json, options); + if (!status.ok()) { + std::stringstream ss; + ss << "Failed to convert Protobuf message to JSON: " << status.ToString(); + throw std::runtime_error(ss.str()); + } + + // Write to output stream + os << json; +} + +auto writeJSONSchema(const std::string& path) -> void { + // Write to file + std::ofstream ofs(path); + if (ofs.is_open()) { + writeJSONSchema(ofs); + ofs.close(); + SPDLOG_INFO("JSON template written to {}", path); + } else { + std::stringstream ss; + ss << "Failed to open file for writing: " << path; + throw std::runtime_error(ss.str()); + } +} + +[[nodiscard]] auto readJSON(std::istream& is) -> Device { + // Read the device configuration from the input stream + std::stringstream buffer; + buffer << is.rdbuf(); + const std::string json = buffer.str(); + // Parse the JSON string into the protobuf message + google::protobuf::util::JsonParseOptions options; + options.ignore_unknown_fields = true; + Device device; + const auto status = + google::protobuf::util::JsonStringToMessage(json, &device); + if (!status.ok()) { + std::stringstream ss; + ss << "Failed to parse JSON string into Protobuf message: " + << status.ToString(); + throw std::runtime_error(ss.str()); + } + return device; +} + +[[nodiscard]] auto readJSON(const std::string& path) -> Device { + // Read the device configuration from a JSON file + std::ifstream ifs(path); + if (!ifs.is_open()) { + throw std::runtime_error("Failed to open JSON file: " + std::string(path)); + } + const auto& device = readJSON(ifs); + ifs.close(); + return device; +} + +auto writeHeader(const Device& device, std::ostream& os) -> void { + os << "#pragma once\n\n"; + const auto timeUnit = getTimeUnit(device); + writeName(device, os); + writeQubitsNum(device, os); + writeSites(device, os); + writeOperations(device, timeUnit, os); + writeDecoherenceTimes(device, timeUnit, os); +} + +auto writeHeader(const Device& device, const std::string& path) -> void { + std::ofstream ofs(path); + if (!ofs.is_open()) { + 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 na diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt new file mode 100644 index 000000000..5a5c859d9 --- /dev/null +++ b/src/qdmi/CMakeLists.txt @@ -0,0 +1,25 @@ +# 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 ${MQT_CORE_TARGET_NAME}-qdmi-driver) + +if(NOT TARGET ${TARGET_NAME}) + + add_mqt_core_library(${TARGET_NAME}) + target_sources(${TARGET_NAME} PRIVATE Driver.cpp) + # todo: should the header actually be public? + target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} + FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/Driver.hpp) + target_link_libraries( + ${TARGET_NAME} + PUBLIC qdmi::qdmi MQT::CoreNaQDMI + PRIVATE qdmi::project_warnings MQT::ProjectOptions MQT::ProjectWarnings) + + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_17) + +endif() diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp new file mode 100644 index 000000000..e5313ba01 --- /dev/null +++ b/src/qdmi/Driver.cpp @@ -0,0 +1,426 @@ +/* + * 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/Driver.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qdmi { +DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, + const std::string& prefix) + : libHandle(dlopen(libName.c_str(), RTLD_NOW | RTLD_LOCAL)) { + if (libHandle == nullptr) { + throw std::runtime_error("Couldn't open the device library: " + libName); + } + if (openLibHandles().find(libHandle) != openLibHandles().end()) { + // dlopen uses reference counting, so we need to decrement the reference + // count that was increased by dlopen. + dlclose(libHandle); + throw std::runtime_error("Device library already loaded: " + libName); + } + openLibHandles().emplace(libHandle); + // Macro for loading a symbol from the dynamic library. + // @param symbol is the name of the symbol to load. +#define LOAD_DYNAMIC_SYMBOL(symbol) \ + { \ + const std::string symbolName = std::string(prefix) + "_QDMI_" + #symbol; \ + (symbol) = reinterpret_cast( \ + dlsym(libHandle, symbolName.c_str())); \ + if ((symbol) == nullptr) { \ + throw std::runtime_error("Failed to load symbol: " + symbolName); \ + } \ + } + try { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + // load the function symbols from the dynamic library + LOAD_DYNAMIC_SYMBOL(device_initialize) + LOAD_DYNAMIC_SYMBOL(device_finalize) + // device session interface + LOAD_DYNAMIC_SYMBOL(device_session_alloc) + LOAD_DYNAMIC_SYMBOL(device_session_init) + LOAD_DYNAMIC_SYMBOL(device_session_free) + LOAD_DYNAMIC_SYMBOL(device_session_set_parameter) + // device job interface + LOAD_DYNAMIC_SYMBOL(device_session_create_device_job) + LOAD_DYNAMIC_SYMBOL(device_job_free) + LOAD_DYNAMIC_SYMBOL(device_job_set_parameter) + LOAD_DYNAMIC_SYMBOL(device_job_query_property) + LOAD_DYNAMIC_SYMBOL(device_job_submit) + LOAD_DYNAMIC_SYMBOL(device_job_cancel) + LOAD_DYNAMIC_SYMBOL(device_job_check) + LOAD_DYNAMIC_SYMBOL(device_job_wait) + LOAD_DYNAMIC_SYMBOL(device_job_get_results) + // device query interface + LOAD_DYNAMIC_SYMBOL(device_session_query_device_property) + LOAD_DYNAMIC_SYMBOL(device_session_query_site_property) + LOAD_DYNAMIC_SYMBOL(device_session_query_operation_property) + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + } catch (const std::exception&) { + dlclose(libHandle); + throw; + } + // initialize the device + device_initialize(); +} + +DynamicDeviceLibrary::~DynamicDeviceLibrary() { + // Check if QDMI_device_finalize is not NULL before calling it. + if (device_finalize != nullptr) { + device_finalize(); + } + // close the dynamic library + if (libHandle != nullptr) { + dlclose(libHandle); + } +} +} // namespace qdmi + +QDMI_Device_impl_d::QDMI_Device_impl_d( + std::unique_ptr&& lib) + : library(std::move(lib)) { + if (library->device_session_alloc(&deviceSession) != QDMI_SUCCESS) { + throw std::runtime_error("Failed to allocate device session"); + } + if (library->device_session_init(deviceSession) != QDMI_SUCCESS) { + library->device_session_free(deviceSession); + throw std::runtime_error("Failed to initialize device session"); + } +} + +auto QDMI_Device_impl_d::createJob(QDMI_Job* job) -> int { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + QDMI_Device_Job deviceJob = nullptr; + auto result = + library->device_session_create_device_job(deviceSession, &deviceJob); + if (result != QDMI_SUCCESS) { + return result; + } + auto uniqueJob = + std::make_unique(library.get(), deviceJob, this); + *job = jobs.emplace(uniqueJob.get(), std::move(uniqueJob)).first->first; + return QDMI_SUCCESS; +} + +auto QDMI_Device_impl_d::jobFree(QDMI_Job job) -> void { + if (job != nullptr) { + jobs.erase(job); + } +} + +auto QDMI_Device_impl_d::queryDeviceProperty(QDMI_Device_Property prop, + const size_t size, void* value, + size_t* sizeRet) const -> int { + return library->device_session_query_device_property(deviceSession, prop, + size, value, sizeRet); +} + +auto QDMI_Device_impl_d::querySiteProperty(QDMI_Site site, + QDMI_Site_Property prop, + const size_t size, void* value, + size_t* sizeRet) const -> int { + return library->device_session_query_site_property(deviceSession, site, prop, + size, value, sizeRet); +} + +auto QDMI_Device_impl_d::queryOperationProperty( + QDMI_Operation operation, const size_t numSites, const QDMI_Site* sites, + const size_t numParams, const double* params, QDMI_Operation_Property prop, + const size_t size, void* value, size_t* sizeRet) const -> int { + return library->device_session_query_operation_property( + deviceSession, operation, numSites, sites, numParams, params, prop, size, + value, sizeRet); +} + +QDMI_Job_impl_d::~QDMI_Job_impl_d() { library->device_job_free(deviceJob); } +auto QDMI_Job_impl_d::setParameter(QDMI_Job_Parameter param, const size_t size, + const void* value) const -> int { + switch (param) { + case QDMI_JOB_PARAMETER_PROGRAM: + return library->device_job_set_parameter( + deviceJob, QDMI_DEVICE_JOB_PARAMETER_PROGRAM, size, value); + case QDMI_JOB_PARAMETER_PROGRAMFORMAT: + return library->device_job_set_parameter( + deviceJob, QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT, size, value); + case QDMI_JOB_PARAMETER_SHOTSNUM: + return library->device_job_set_parameter( + deviceJob, QDMI_DEVICE_JOB_PARAMETER_SHOTSNUM, size, value); + default: + return QDMI_ERROR_NOTSUPPORTED; + } +} + +auto QDMI_Job_impl_d::queryProperty(QDMI_Job_Property prop, const size_t size, + void* value, size_t* sizeRet) const -> int { + switch (prop) { + case QDMI_JOB_PROPERTY_ID: + return library->device_job_query_property( + deviceJob, QDMI_DEVICE_JOB_PROPERTY_ID, size, value, sizeRet); + case QDMI_JOB_PROPERTY_PROGRAM: + return library->device_job_query_property( + deviceJob, QDMI_DEVICE_JOB_PROPERTY_PROGRAM, size, value, sizeRet); + case QDMI_JOB_PROPERTY_PROGRAMFORMAT: + return library->device_job_query_property( + deviceJob, QDMI_DEVICE_JOB_PROPERTY_PROGRAMFORMAT, size, value, + sizeRet); + case QDMI_JOB_PROPERTY_SHOTSNUM: + return library->device_job_query_property( + deviceJob, QDMI_DEVICE_JOB_PROPERTY_SHOTSNUM, size, value, sizeRet); + default: + return QDMI_ERROR_NOTSUPPORTED; + } +} + +auto QDMI_Job_impl_d::submit() const -> int { + return library->device_job_submit(deviceJob); +} + +auto QDMI_Job_impl_d::cancel() const -> int { + return library->device_job_cancel(deviceJob); +} + +auto QDMI_Job_impl_d::check(QDMI_Job_Status* status) const -> int { + return library->device_job_check(deviceJob, status); +} + +auto QDMI_Job_impl_d::wait(size_t timeout) const -> int { + return library->device_job_wait(deviceJob, timeout); +} + +auto QDMI_Job_impl_d::getResults(QDMI_Job_Result result, const size_t size, + void* data, size_t* sizeRet) const -> int { + return library->device_job_get_results(deviceJob, result, size, data, + sizeRet); +} + +auto QDMI_Job_impl_d::free() -> void { device->jobFree(this); } + +auto QDMI_Session_impl_d::init() -> int { + if (status != qdmi::SessionStatus::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + status = qdmi::SessionStatus::INITIALIZED; + return QDMI_SUCCESS; +} + +auto QDMI_Session_impl_d::setParameter(QDMI_Session_Parameter param, + const size_t size, + const void* value) const -> int { + if ((value != nullptr && size == 0) || param >= QDMI_SESSION_PARAMETER_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status != qdmi::SessionStatus::ALLOCATED) { + return QDMI_ERROR_BADSTATE; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +auto QDMI_Session_impl_d::querySessionProperty(QDMI_Session_Property prop, + size_t size, void* value, + size_t* sizeRet) const -> int { + if ((value != nullptr && size == 0) || prop >= QDMI_SESSION_PROPERTY_MAX) { + return QDMI_ERROR_INVALIDARGUMENT; + } + if (status != qdmi::SessionStatus::INITIALIZED) { + return QDMI_ERROR_BADSTATE; + } + if (prop == QDMI_SESSION_PROPERTY_DEVICES) { + if (value != nullptr) { + if (size < devices->size() * sizeof(QDMI_Device)) { + return QDMI_ERROR_INVALIDARGUMENT; + } + memcpy(value, static_cast(devices->data()), + devices->size() * sizeof(QDMI_Device)); + } + if (sizeRet != nullptr) { + *sizeRet = devices->size() * sizeof(QDMI_Device); + } + return QDMI_SUCCESS; + } + return QDMI_ERROR_NOTSUPPORTED; +} + +namespace qdmi { +Driver::Driver() { + devices.emplace_back(std::make_unique( + std::make_unique())); +} + +Driver::~Driver() { + sessions.clear(); + devices.clear(); +} + +auto Driver::addDynamicDeviceLibrary(const std::string& libName, + const std::string& prefix) -> void { + devices.emplace_back(std::make_unique( + std::make_unique(libName, prefix))); +} + +auto Driver::sessionAlloc(QDMI_Session* session) -> int { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + auto uniqueSession = std::make_unique(devices); + *session = sessions.emplace(uniqueSession.get(), std::move(uniqueSession)) + .first->first; + return QDMI_SUCCESS; +} + +auto Driver::sessionFree(QDMI_Session session) -> void { + if (session != nullptr) { + if (const auto& it = sessions.find(session); it != sessions.end()) { + sessions.erase(it); + } + } +} +} // namespace qdmi + +int QDMI_session_alloc(QDMI_Session* session) { + return qdmi::Driver::get().sessionAlloc(session); +} + +int QDMI_session_init(QDMI_Session session) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->init(); +} + +void QDMI_session_free(QDMI_Session session) { + qdmi::Driver::get().sessionFree(session); +} + +int QDMI_session_set_parameter(QDMI_Session session, + QDMI_Session_Parameter param, const size_t size, + const void* value) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->setParameter(param, size, value); +} + +int QDMI_session_query_session_property(QDMI_Session session, + QDMI_Session_Property prop, size_t size, + void* value, size_t* sizeRet) { + if (session == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return session->querySessionProperty(prop, size, value, sizeRet); +} + +int QDMI_device_create_job(QDMI_Device dev, QDMI_Job* job) { + if (dev == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return dev->createJob(job); +} + +void QDMI_job_free(QDMI_Job job) { + if (job != nullptr) { + job->free(); + } +} + +int QDMI_job_set_parameter(QDMI_Job job, QDMI_Job_Parameter param, + const size_t size, const void* value) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->setParameter(param, size, value); +} + +int QDMI_job_query_property(QDMI_Job job, QDMI_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 QDMI_job_submit(QDMI_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->submit(); +} + +int QDMI_job_cancel(QDMI_Job job) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->cancel(); +} + +int QDMI_job_check(QDMI_Job job, QDMI_Job_Status* status) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->check(status); +} + +int QDMI_job_wait(QDMI_Job job, size_t timeout) { + if (job == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return job->wait(timeout); +} + +int QDMI_job_get_results(QDMI_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 QDMI_device_query_device_property(QDMI_Device device, + QDMI_Device_Property prop, + const size_t size, void* value, + size_t* sizeRet) { + if (device == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return device->queryDeviceProperty(prop, size, value, sizeRet); +} + +int QDMI_device_query_site_property(QDMI_Device device, QDMI_Site site, + QDMI_Site_Property prop, const size_t size, + void* value, size_t* sizeRet) { + if (device == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return device->querySiteProperty(site, prop, size, value, sizeRet); +} + +int QDMI_device_query_operation_property( + QDMI_Device device, QDMI_Operation operation, const size_t numSites, + const QDMI_Site* sites, const size_t numParams, const double* params, + QDMI_Operation_Property prop, const size_t size, void* value, + size_t* sizeRet) { + if (device == nullptr) { + return QDMI_ERROR_INVALIDARGUMENT; + } + return device->queryOperationProperty(operation, numSites, sites, numParams, + params, prop, size, value, sizeRet); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0a7fc69d7..b9c4fd97b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(dd) add_subdirectory(ir) add_subdirectory(na) add_subdirectory(zx) +add_subdirectory(qdmi) # copy test circuits to build directory file(COPY ${PROJECT_SOURCE_DIR}/test/circuits DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/test/na/CMakeLists.txt b/test/na/CMakeLists.txt index 54b7f816c..3515b538e 100644 --- a/test/na/CMakeLists.txt +++ b/test/na/CMakeLists.txt @@ -7,7 +7,9 @@ # Licensed under the MIT License if(TARGET MQT::CoreNA) - file(GLOB_RECURSE NA_TEST_SOURCES *.cpp) + file(GLOB NA_TEST_SOURCES *.cpp) package_add_test(mqt-core-na-test MQT::CoreNA ${NA_TEST_SOURCES}) target_link_libraries(mqt-core-na-test PRIVATE MQT::CoreQASM) endif() + +add_subdirectory(device) diff --git a/test/na/device/CMakeLists.txt b/test/na/device/CMakeLists.txt new file mode 100644 index 000000000..157a171c2 --- /dev/null +++ b/test/na/device/CMakeLists.txt @@ -0,0 +1,32 @@ +# 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 test target name +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-na-device-test) +# Set QDMI prefix again for the tests +set(QDMI_PREFIX "MQT_NA") + +if(TARGET qdmi::mqt_na_device) + + # ------------------------------------------------------------------------------ + # Non-Functional Tests + # ------------------------------------------------------------------------------ + + generate_device_defs_executable(${QDMI_PREFIX}) + + # ------------------------------------------------------------------------------ + # Functional Tests + # ------------------------------------------------------------------------------ + + package_add_test(${TARGET_NAME} qdmi::mqt_na_device test_interface.cpp) + add_dependencies(${TARGET_NAME} qdmi_test_mqt_na_device_defs) + + # set c++ standard + target_compile_features(${TARGET_NAME} PRIVATE cxx_std_17) + +endif() diff --git a/test/na/device/test_interface.cpp b/test/na/device/test_interface.cpp new file mode 100644 index 000000000..6684e82e8 --- /dev/null +++ b/test/na/device/test_interface.cpp @@ -0,0 +1,209 @@ +/* + * 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_na_qdmi/device.h" + +#include +#include +#include +#include + +class QDMISpecificationTest : public ::testing::Test { +protected: + MQT_NA_QDMI_Device_Session session = nullptr; + + void SetUp() override { + ASSERT_EQ(MQT_NA_QDMI_device_initialize(), QDMI_SUCCESS) + << "Failed to initialize the device"; + + ASSERT_EQ(MQT_NA_QDMI_device_session_alloc(&session), QDMI_SUCCESS) + << "Failed to allocate a session"; + + ASSERT_EQ(MQT_NA_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 { MQT_NA_QDMI_device_finalize(); } +}; + +TEST_F(QDMISpecificationTest, SessionSetParameter) { + ASSERT_EQ(MQT_NA_QDMI_device_session_set_parameter( + session, QDMI_DEVICE_SESSION_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(QDMISpecificationTest, JobCreate) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_NE(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTIMPLEMENTED); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobSetParameter) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_EQ(MQT_NA_QDMI_device_job_set_parameter( + job, QDMI_DEVICE_JOB_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobQueryProperty) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_EQ(MQT_NA_QDMI_device_job_query_property( + job, QDMI_DEVICE_JOB_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobSubmit) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_NE(MQT_NA_QDMI_device_job_submit(job), QDMI_ERROR_NOTIMPLEMENTED); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobCancel) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_NE(MQT_NA_QDMI_device_job_cancel(job), QDMI_ERROR_NOTIMPLEMENTED); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobCheck) { + MQT_NA_QDMI_Device_Job job = nullptr; + QDMI_Job_Status status = QDMI_JOB_STATUS_RUNNING; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_NE(MQT_NA_QDMI_device_job_check(job, &status), + QDMI_ERROR_NOTIMPLEMENTED); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobWait) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_NE(MQT_NA_QDMI_device_job_wait(job, 0), QDMI_ERROR_NOTIMPLEMENTED); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, JobGetResults) { + MQT_NA_QDMI_Device_Job job = nullptr; + ASSERT_EQ(MQT_NA_QDMI_device_session_create_device_job(session, &job), + QDMI_ERROR_NOTSUPPORTED); + ASSERT_EQ(MQT_NA_QDMI_device_job_get_results(job, QDMI_JOB_RESULT_MAX, 0, + nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); + MQT_NA_QDMI_device_job_free(job); +} + +TEST_F(QDMISpecificationTest, QueryDeviceProperty) { + ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( + nullptr, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(QDMISpecificationTest, QuerySiteProperty) { + ASSERT_EQ(MQT_NA_QDMI_device_session_query_site_property( + nullptr, nullptr, QDMI_SITE_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(QDMISpecificationTest, QueryOperationProperty) { + ASSERT_EQ(MQT_NA_QDMI_device_session_query_operation_property( + nullptr, nullptr, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT); +} + +TEST_F(QDMISpecificationTest, QueryDeviceName) { + size_t size = 0; + ASSERT_EQ(MQT_NA_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_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_NAME, size, value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a name"; + ASSERT_FALSE(value.empty()) << "Devices must provide a name"; +} + +TEST_F(QDMISpecificationTest, QueryDeviceVersion) { + size_t size = 0; + ASSERT_EQ(MQT_NA_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_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_VERSION, size, value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a version"; + ASSERT_FALSE(value.empty()) << "Devices must provide a version"; +} + +TEST_F(QDMISpecificationTest, QueryDeviceLibraryVersion) { + size_t size = 0; + ASSERT_EQ( + MQT_NA_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_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_LIBRARYVERSION, size, + value.data(), nullptr), + QDMI_SUCCESS) + << "Devices must provide a library version"; + ASSERT_FALSE(value.empty()) << "Devices must provide a library version"; +} + +TEST_F(QDMISpecificationTest, QuerySiteIndex) { + size_t size = 0; + ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_SITES, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a list of sites"; + std::vector sites(size / sizeof(MQT_NA_QDMI_Site)); + ASSERT_EQ(MQT_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_SITES, size, + static_cast(sites.data()), nullptr), + QDMI_SUCCESS) + << "Devices must provide a list of sites"; + size_t id = 0; + for (auto* site : sites) { + ASSERT_EQ(MQT_NA_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"; + } +} + +TEST_F(QDMISpecificationTest, QueryDeviceQubitNum) { + size_t numQubits = 0; + EXPECT_EQ(MQT_NA_QDMI_device_session_query_device_property( + session, QDMI_DEVICE_PROPERTY_QUBITSNUM, sizeof(size_t), + &numQubits, nullptr), + QDMI_SUCCESS); +} diff --git a/test/qdmi/CMakeLists.txt b/test/qdmi/CMakeLists.txt new file mode 100644 index 000000000..bff2e7dfb --- /dev/null +++ b/test/qdmi/CMakeLists.txt @@ -0,0 +1,13 @@ +# 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 mqt-core-qdmi-driver-test) + +if(TARGET MQT::CoreQdmiDriver) + package_add_test(${TARGET_NAME} MQT::CoreQdmiDriver test_driver.cpp) +endif() diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp new file mode 100644 index 000000000..3dd9ffcab --- /dev/null +++ b/test/qdmi/test_driver.cpp @@ -0,0 +1,373 @@ +/* + * 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 "gmock/gmock-matchers.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qc { +class DriverTest : public ::testing::TestWithParam { +protected: + QDMI_Session session = nullptr; + QDMI_Device device = nullptr; + + void SetUp() override { + const auto& deviceName = GetParam(); + + ASSERT_EQ(QDMI_session_alloc(&session), QDMI_SUCCESS) + << "Failed to allocate session."; + + ASSERT_EQ(QDMI_session_init(session), QDMI_SUCCESS) + << "Failed to initialize session."; + + size_t devicesSize = 0; + ASSERT_EQ(QDMI_session_query_session_property(session, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &devicesSize), + QDMI_SUCCESS) + << "Failed to retrieve number of devices."; + std::vector devices(devicesSize / sizeof(QDMI_Device)); + ASSERT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, devicesSize, + static_cast(devices.data()), nullptr), + QDMI_SUCCESS) + << "Failed to retrieve devices."; + + for (auto* const dev : devices) { + size_t namesSize = 0; + ASSERT_EQ(QDMI_device_query_device_property( + dev, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &namesSize), + QDMI_SUCCESS) + << "Failed to retrieve the length of the device's name."; + std::string name(namesSize - 1, '\0'); + ASSERT_EQ( + QDMI_device_query_device_property(dev, QDMI_DEVICE_PROPERTY_NAME, + namesSize, name.data(), nullptr), + QDMI_SUCCESS) + << "Failed to retrieve the device's name."; + + ASSERT_FALSE(name.empty()) << "Device must provide a non-empty name."; + + if (name == deviceName) { + device = dev; + break; + } + } + } + + void TearDown() override { QDMI_session_free(session); } +}; + +TEST_P(DriverTest, SessionSetParameterImplemented) { + EXPECT_EQ(QDMI_session_set_parameter(session, QDMI_SESSION_PARAMETER_MAX, 0, + nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_session_set_parameter`."; +} + +TEST_P(DriverTest, JobCreateImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_device_create_job(device, &job), QDMI_ERROR_NOTSUPPORTED) + << "Devices must implement `QDMI_device_create_job`."; + QDMI_job_free(job); +} + +TEST_P(DriverTest, JobSetParameterImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_job_set_parameter(job, QDMI_JOB_PARAMETER_MAX, 0, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_set_parameter`."; +} + +TEST_P(DriverTest, JobQueryPropertyImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ( + QDMI_job_query_property(job, QDMI_JOB_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_set_parameter`."; +} + +TEST_P(DriverTest, JobSubmitImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_job_submit(job), QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_submit`."; +} + +TEST_P(DriverTest, JobCancelImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_job_cancel(job), QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_cancel`."; +} + +TEST_P(DriverTest, JobCheckImplemented) { + QDMI_Job job = nullptr; + QDMI_Job_Status status = QDMI_JOB_STATUS_RUNNING; + EXPECT_EQ(QDMI_job_check(job, &status), QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_check`."; +} + +TEST_P(DriverTest, JobWaitImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_job_wait(job, 0), QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_wait`."; +} + +TEST_P(DriverTest, JobGetResultsImplemented) { + QDMI_Job job = nullptr; + EXPECT_EQ(QDMI_job_get_results(job, QDMI_JOB_RESULT_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_job_get_results`."; +} + +TEST_P(DriverTest, QueryDevicePropertyImplemented) { + EXPECT_EQ(QDMI_device_query_device_property(device, QDMI_DEVICE_PROPERTY_MAX, + 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_device_query_device_property`."; +} + +TEST_P(DriverTest, QuerySitePropertyImplemented) { + EXPECT_EQ(QDMI_device_query_site_property( + device, nullptr, QDMI_SITE_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_device_query_site_property`."; +} + +TEST_P(DriverTest, QueryOperationPropertyImplemented) { + EXPECT_EQ(QDMI_device_query_operation_property( + device, nullptr, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Devices must implement `QDMI_device_query_operation_property`."; +} + +TEST_P(DriverTest, QueryDeviceVersion) { + size_t size = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_VERSION, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a version."; + std::string value(size - 1, '\0'); + ASSERT_EQ(QDMI_device_query_device_property(device, + 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_P(DriverTest, QueryDeviceLibraryVersion) { + size_t size = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_LIBRARYVERSION, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a library version."; + std::string value(size - 1, '\0'); + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_LIBRARYVERSION, size, value.data(), + nullptr), + QDMI_SUCCESS) + << "Devices must provide a library version."; + ASSERT_FALSE(value.empty()) << "Devices must provide a library version."; +} + +TEST_P(DriverTest, QueryNumQubits) { + size_t numQubits = 0; + ASSERT_EQ( + QDMI_device_query_device_property(device, QDMI_DEVICE_PROPERTY_QUBITSNUM, + sizeof(size_t), &numQubits, nullptr), + QDMI_SUCCESS) + << "Devices must provide the number of qubits."; + EXPECT_GT(numQubits, 0) << "Number of qubits must be greater than 0."; +} + +TEST_P(DriverTest, QueryDeviceProperties) { + EXPECT_EQ(QDMI_device_query_device_property(device, QDMI_DEVICE_PROPERTY_MAX, + 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "The MAX property is not a valid value for any device."; +} + +TEST_P(DriverTest, QuerySites) { + size_t size = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_SITES, 0, nullptr, &size), + QDMI_SUCCESS) + << "Devices must provide a list of sites."; + std::vector sites(size / sizeof(QDMI_Site)); + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_SITES, size, + static_cast(sites.data()), nullptr), + QDMI_SUCCESS) + << "Failed to get sites."; + std::unordered_set ids; + for (auto* site : sites) { + uint64_t index = 0; + EXPECT_EQ( + QDMI_device_query_site_property(device, site, QDMI_SITE_PROPERTY_INDEX, + sizeof(uint64_t), &index, nullptr), + QDMI_SUCCESS) + << "Devices must provide a site id"; + EXPECT_TRUE(ids.emplace(index).second) + << "Device must provide unique site ids. Found duplicate id: " << index + << "."; + double t1 = 0; + double t2 = 0; + EXPECT_EQ(QDMI_device_query_site_property(device, site, + QDMI_SITE_PROPERTY_T1, + sizeof(double), &t1, nullptr), + QDMI_SUCCESS) + << "Devices must provide a site T1 time."; + EXPECT_GT(t1, 0) << "Devices must provide a site T1 time larger than 0."; + EXPECT_EQ(QDMI_device_query_site_property(device, site, + QDMI_SITE_PROPERTY_T2, + sizeof(double), &t2, nullptr), + QDMI_SUCCESS) + << "Devices must provide a site T2 time."; + EXPECT_GT(t2, 0) << "Devices must provide a site T2 time larger than 0."; + EXPECT_EQ(QDMI_device_query_site_property( + device, site, QDMI_SITE_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "The MAX property is not a valid value for any device."; + } +} + +TEST_P(DriverTest, QueryOperations) { + size_t operationsSize = 0; + ASSERT_EQ(QDMI_device_query_device_property(device, + QDMI_DEVICE_PROPERTY_OPERATIONS, + 0, nullptr, &operationsSize), + QDMI_SUCCESS) + << "Failed to get the size to retrieve the operations."; + std::vector operations(operationsSize / + sizeof(QDMI_Operation)); + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_OPERATIONS, operationsSize, + static_cast(operations.data()), nullptr), + QDMI_SUCCESS) + << "Failed to retrieve the operations."; + for (auto* const op : operations) { + size_t namesSize = 0; + ASSERT_EQ(QDMI_device_query_operation_property( + device, op, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_NAME, 0, nullptr, &namesSize), + QDMI_SUCCESS) + << "Failed to get the length of the operation's name."; + std::string name(namesSize - 1, '\0'); + ASSERT_EQ( + QDMI_device_query_operation_property(device, op, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_NAME, + namesSize, name.data(), nullptr), + QDMI_SUCCESS) + << "Failed to retrieve the operation's name."; + EXPECT_FALSE(name.empty()) + << "Device must provide a non-empty name for every operation."; + + size_t numParams = 0; + ASSERT_EQ(QDMI_device_query_operation_property( + device, op, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_PARAMETERSNUM, sizeof(size_t), + &numParams, nullptr), + QDMI_SUCCESS) + << "Failed to query number of parameters for operation."; + + double duration = 0; + double fidelity = 0; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dis(0.0, 1.0); + std::vector params(numParams); + for (auto& param : params) { + param = dis(gen); + } + EXPECT_EQ(QDMI_device_query_operation_property( + device, op, 0, nullptr, numParams, params.data(), + QDMI_OPERATION_PROPERTY_DURATION, sizeof(double), &duration, + nullptr), + QDMI_SUCCESS) + << "Failed to query duration for operation " << name << "."; + EXPECT_THAT(QDMI_device_query_operation_property( + device, op, 0, nullptr, numParams, params.data(), + QDMI_OPERATION_PROPERTY_FIDELITY, sizeof(double), &fidelity, + nullptr), + ::testing::AnyOf(QDMI_SUCCESS, QDMI_ERROR_NOTSUPPORTED)) + << "Failed to query fidelity for operation " << name << "."; + + EXPECT_EQ(QDMI_device_query_operation_property( + device, op, 0, nullptr, 0, nullptr, + QDMI_OPERATION_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "The MAX property is not a valid value for any device."; + } +} + +TEST_P(DriverTest, SessionInit) { + EXPECT_EQ(QDMI_session_init(nullptr), QDMI_ERROR_INVALIDARGUMENT) + << "`session == nullptr` is not a valid argument."; + EXPECT_EQ(QDMI_session_init(session), QDMI_ERROR_BADSTATE) + << "Session must return `BADSTATE` if it is initialized again."; +} + +TEST_P(DriverTest, QuerySessionProperties) { + EXPECT_EQ(QDMI_session_query_session_property( + nullptr, QDMI_SESSION_PROPERTY_DEVICES, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "`session == nullptr` is not a valid argument."; + EXPECT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_MAX, 0, nullptr, nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "`prop >= QDMI_SESSION_PROPERTY_MAX` is not a valid argument."; + + // Must not query on an uninitialized session + QDMI_Session uninitializedSession = nullptr; + ASSERT_EQ(QDMI_session_alloc(&uninitializedSession), QDMI_SUCCESS); + EXPECT_EQ(QDMI_session_query_session_property(uninitializedSession, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, nullptr), + QDMI_ERROR_BADSTATE); + + constexpr size_t size = sizeof(QDMI_Device) - 1; + std::array devices{}; + EXPECT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, size, + static_cast(devices.data()), nullptr), + QDMI_ERROR_INVALIDARGUMENT) + << "Device must return `INVALIDARGUMENT` if the buffer is too small."; +} + +TEST_P(DriverTest, QueryNeedsCalibration) { + size_t needsCalibration = 0; + const auto ret = QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION, sizeof(size_t), + &needsCalibration, nullptr); + EXPECT_EQ(ret, QDMI_SUCCESS); + EXPECT_THAT(needsCalibration, ::testing::AnyOf(0, 1)); +} + +// Instantiate the test suite with different parameters +INSTANTIATE_TEST_SUITE_P( + // Custom instantiation name + DefaultDevices, + // Test suite name + DriverTest, + // Parameters to test with + ::testing::Values("MQT NA QDMI Default Device"), + [](const testing::TestParamInfo& info) { + std::string name = info.param; + std::replace(name.begin(), name.end(), ' ', '_'); + return name; + }); +} // namespace qc