diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d22217ab4..f80b289c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add authentication support for QDMI sessions with token, username/password, auth file, auth URL, and project ID parameters ([#1355]) ([**@marcelwa**]) - ✨ Add a new QDMI device that represents a superconducting architecture featuring a coupling map ([#1328]) ([**@ystade**]) - ✨ Add bi-directional iterator that traverses the def-use chain of a qubit value ([#1310]) ([**@MatthiasReumann**]) - ✨ Add `OptionalDependencyTester` to lazily handle optional Python dependencies like Qiskit ([#1243]) ([**@marcelwa**], [**@burgholzer**]) @@ -21,6 +22,11 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed +- ✨ Add common definitions and utilities for QDMI ([#1355]) ([**@burgholzer**]) +- 🚚 Move `NA` QDMI device in its right place next to other QDMI devices ([#1355]) ([**@burgholzer**]) +- ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**]) +- ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**]) +- ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**]) - 👷 Stop testing on `ubuntu-22.04` and `ubuntu-22.04-arm` runners ([#1359]) ([**@denialhaag**], [**@burgholzer**]) - 👷 Stop testing with `clang-19` and start testing with `clang-21` ([#1359]) ([**@denialhaag**], [**@burgholzer**]) - 👷 Fix macOS tests with Homebrew Clang via new `munich-quantum-toolkit/workflows` version ([#1359]) ([**@denialhaag**], [**@burgholzer**]) @@ -33,6 +39,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Fixed +- 🐛 Fix custom QDMI property and parameter handling in SC and NA devices ([#1355]) ([**@burgholzer**]) - 🚨 Fix argument naming of `QuantumComputation` and `CompoundOperation` dunder methods for properly implementing the `MutableSequence` protocol ([#1338]) ([**@burgholzer**]) - 🐛 Fix memory management in dynamic QDMI device by making it explicit ([#1336]) ([**@ystade**]) @@ -273,6 +280,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool [#1371]: https://github.com/munich-quantum-toolkit/core/pull/1371 [#1359]: https://github.com/munich-quantum-toolkit/core/pull/1359 +[#1355]: https://github.com/munich-quantum-toolkit/core/pull/1355 [#1338]: https://github.com/munich-quantum-toolkit/core/pull/1338 [#1336]: https://github.com/munich-quantum-toolkit/core/pull/1336 [#1328]: https://github.com/munich-quantum-toolkit/core/pull/1328 diff --git a/bindings/fomac/fomac.cpp b/bindings/fomac/fomac.cpp index 1e97739e65..03de2ca837 100644 --- a/bindings/fomac/fomac.cpp +++ b/bindings/fomac/fomac.cpp @@ -10,6 +10,9 @@ #include "fomac/FoMaC.hpp" +#include "qdmi/Driver.hpp" + +#include #include #include // NOLINT(misc-include-cleaner) #include @@ -26,23 +29,61 @@ namespace py = pybind11; using namespace py::literals; PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { + auto session = py::class_(m, "Session"); + + session.def( + py::init([](const std::optional& token = std::nullopt, + const std::optional& authFile = std::nullopt, + const std::optional& authUrl = std::nullopt, + const std::optional& username = std::nullopt, + const std::optional& password = std::nullopt, + const std::optional& projectId = std::nullopt, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = + std::nullopt) -> fomac::Session { + const fomac::SessionConfig config{.token = token, + .authFile = authFile, + .authUrl = authUrl, + .username = username, + .password = password, + .projectId = projectId, + .custom1 = custom1, + .custom2 = custom2, + .custom3 = custom3, + .custom4 = custom4, + .custom5 = custom5}; + return fomac::Session{config}; + }), + "token"_a = std::nullopt, "auth_file"_a = std::nullopt, + "auth_url"_a = std::nullopt, "username"_a = std::nullopt, + "password"_a = std::nullopt, "project_id"_a = std::nullopt, + "custom1"_a = std::nullopt, "custom2"_a = std::nullopt, + "custom3"_a = std::nullopt, "custom4"_a = std::nullopt, + "custom5"_a = std::nullopt); + + session.def("get_devices", &fomac::Session::getDevices); + // Job class - auto job = py::class_(m, "Job"); - job.def("check", &fomac::FoMaC::Job::check); - job.def("wait", &fomac::FoMaC::Job::wait, "timeout"_a = 0); - job.def("cancel", &fomac::FoMaC::Job::cancel); - job.def("get_shots", &fomac::FoMaC::Job::getShots); - job.def("get_counts", &fomac::FoMaC::Job::getCounts); - job.def("get_dense_statevector", &fomac::FoMaC::Job::getDenseStateVector); - job.def("get_dense_probabilities", &fomac::FoMaC::Job::getDenseProbabilities); - job.def("get_sparse_statevector", &fomac::FoMaC::Job::getSparseStateVector); + auto job = py::class_(m, "Job"); + job.def("check", &fomac::Session::Job::check); + job.def("wait", &fomac::Session::Job::wait, "timeout"_a = 0); + job.def("cancel", &fomac::Session::Job::cancel); + job.def("get_shots", &fomac::Session::Job::getShots); + job.def("get_counts", &fomac::Session::Job::getCounts); + job.def("get_dense_statevector", &fomac::Session::Job::getDenseStateVector); + job.def("get_dense_probabilities", + &fomac::Session::Job::getDenseProbabilities); + job.def("get_sparse_statevector", &fomac::Session::Job::getSparseStateVector); job.def("get_sparse_probabilities", - &fomac::FoMaC::Job::getSparseProbabilities); - job.def_property_readonly("id", &fomac::FoMaC::Job::getId); + &fomac::Session::Job::getSparseProbabilities); + job.def_property_readonly("id", &fomac::Session::Job::getId); job.def_property_readonly("program_format", - &fomac::FoMaC::Job::getProgramFormat); - job.def_property_readonly("program", &fomac::FoMaC::Job::getProgram); - job.def_property_readonly("num_shots", &fomac::FoMaC::Job::getNumShots); + &fomac::Session::Job::getProgramFormat); + job.def_property_readonly("program", &fomac::Session::Job::getProgram); + job.def_property_readonly("num_shots", &fomac::Session::Job::getNumShots); job.def(py::self == py::self); // NOLINT(misc-redundant-expression) job.def(py::self != py::self); // NOLINT(misc-redundant-expression) @@ -79,7 +120,7 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .export_values() .finalize(); - auto device = py::class_(m, "Device"); + auto device = py::class_(m, "Device"); py::native_enum(device, "Status", "enum.Enum", "Enumeration of device status.") @@ -92,98 +133,135 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { .export_values() .finalize(); - auto site = py::class_(device, "Site"); - site.def("index", &fomac::FoMaC::Device::Site::getIndex); - site.def("t1", &fomac::FoMaC::Device::Site::getT1); - site.def("t2", &fomac::FoMaC::Device::Site::getT2); - site.def("name", &fomac::FoMaC::Device::Site::getName); - site.def("x_coordinate", &fomac::FoMaC::Device::Site::getXCoordinate); - site.def("y_coordinate", &fomac::FoMaC::Device::Site::getYCoordinate); - site.def("z_coordinate", &fomac::FoMaC::Device::Site::getZCoordinate); - site.def("is_zone", &fomac::FoMaC::Device::Site::isZone); - site.def("x_extent", &fomac::FoMaC::Device::Site::getXExtent); - site.def("y_extent", &fomac::FoMaC::Device::Site::getYExtent); - site.def("z_extent", &fomac::FoMaC::Device::Site::getZExtent); - site.def("module_index", &fomac::FoMaC::Device::Site::getModuleIndex); - site.def("submodule_index", &fomac::FoMaC::Device::Site::getSubmoduleIndex); - site.def("__repr__", [](const fomac::FoMaC::Device::Site& s) { + auto site = py::class_(device, "Site"); + site.def("index", &fomac::Session::Device::Site::getIndex); + site.def("t1", &fomac::Session::Device::Site::getT1); + site.def("t2", &fomac::Session::Device::Site::getT2); + site.def("name", &fomac::Session::Device::Site::getName); + site.def("x_coordinate", &fomac::Session::Device::Site::getXCoordinate); + site.def("y_coordinate", &fomac::Session::Device::Site::getYCoordinate); + site.def("z_coordinate", &fomac::Session::Device::Site::getZCoordinate); + site.def("is_zone", &fomac::Session::Device::Site::isZone); + site.def("x_extent", &fomac::Session::Device::Site::getXExtent); + site.def("y_extent", &fomac::Session::Device::Site::getYExtent); + site.def("z_extent", &fomac::Session::Device::Site::getZExtent); + site.def("module_index", &fomac::Session::Device::Site::getModuleIndex); + site.def("submodule_index", &fomac::Session::Device::Site::getSubmoduleIndex); + site.def("__repr__", [](const fomac::Session::Device::Site& s) { return ""; }); site.def(py::self == py::self); // NOLINT(misc-redundant-expression) site.def(py::self != py::self); // NOLINT(misc-redundant-expression) auto operation = - py::class_(device, "Operation"); - operation.def("name", &fomac::FoMaC::Device::Operation::getName, - "sites"_a = std::vector{}, + py::class_(device, "Operation"); + operation.def("name", &fomac::Session::Device::Operation::getName, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("qubits_num", &fomac::FoMaC::Device::Operation::getQubitsNum, - "sites"_a = std::vector{}, + operation.def("qubits_num", &fomac::Session::Device::Operation::getQubitsNum, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("parameters_num", - &fomac::FoMaC::Device::Operation::getParametersNum, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getParametersNum, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("duration", &fomac::FoMaC::Device::Operation::getDuration, - "sites"_a = std::vector{}, + operation.def("duration", &fomac::Session::Device::Operation::getDuration, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("fidelity", &fomac::FoMaC::Device::Operation::getFidelity, - "sites"_a = std::vector{}, + operation.def("fidelity", &fomac::Session::Device::Operation::getFidelity, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("interaction_radius", - &fomac::FoMaC::Device::Operation::getInteractionRadius, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getInteractionRadius, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("blocking_radius", - &fomac::FoMaC::Device::Operation::getBlockingRadius, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getBlockingRadius, + "sites"_a = std::vector{}, "params"_a = std::vector{}); operation.def("idling_fidelity", - &fomac::FoMaC::Device::Operation::getIdlingFidelity, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getIdlingFidelity, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("is_zoned", &fomac::FoMaC::Device::Operation::isZoned); - operation.def("sites", &fomac::FoMaC::Device::Operation::getSites); - operation.def("site_pairs", &fomac::FoMaC::Device::Operation::getSitePairs); + operation.def("is_zoned", &fomac::Session::Device::Operation::isZoned); + operation.def("sites", &fomac::Session::Device::Operation::getSites); + operation.def("site_pairs", &fomac::Session::Device::Operation::getSitePairs); operation.def("mean_shuttling_speed", - &fomac::FoMaC::Device::Operation::getMeanShuttlingSpeed, - "sites"_a = std::vector{}, + &fomac::Session::Device::Operation::getMeanShuttlingSpeed, + "sites"_a = std::vector{}, "params"_a = std::vector{}); - operation.def("__repr__", [](const fomac::FoMaC::Device::Operation& op) { + operation.def("__repr__", [](const fomac::Session::Device::Operation& op) { return ""; }); operation.def(py::self == py::self); // NOLINT(misc-redundant-expression) operation.def(py::self != py::self); // NOLINT(misc-redundant-expression) - device.def("name", &fomac::FoMaC::Device::getName); - device.def("version", &fomac::FoMaC::Device::getVersion); - device.def("status", &fomac::FoMaC::Device::getStatus); - device.def("library_version", &fomac::FoMaC::Device::getLibraryVersion); - device.def("qubits_num", &fomac::FoMaC::Device::getQubitsNum); - device.def("sites", &fomac::FoMaC::Device::getSites); - device.def("regular_sites", &fomac::FoMaC::Device::getRegularSites); - device.def("zones", &fomac::FoMaC::Device::getZones); - device.def("operations", &fomac::FoMaC::Device::getOperations); - device.def("coupling_map", &fomac::FoMaC::Device::getCouplingMap); - device.def("needs_calibration", &fomac::FoMaC::Device::getNeedsCalibration); - device.def("length_unit", &fomac::FoMaC::Device::getLengthUnit); + device.def("name", &fomac::Session::Device::getName); + device.def("version", &fomac::Session::Device::getVersion); + device.def("status", &fomac::Session::Device::getStatus); + device.def("library_version", &fomac::Session::Device::getLibraryVersion); + device.def("qubits_num", &fomac::Session::Device::getQubitsNum); + device.def("sites", &fomac::Session::Device::getSites); + device.def("regular_sites", &fomac::Session::Device::getRegularSites); + device.def("zones", &fomac::Session::Device::getZones); + device.def("operations", &fomac::Session::Device::getOperations); + device.def("coupling_map", &fomac::Session::Device::getCouplingMap); + device.def("needs_calibration", &fomac::Session::Device::getNeedsCalibration); + device.def("length_unit", &fomac::Session::Device::getLengthUnit); device.def("length_scale_factor", - &fomac::FoMaC::Device::getLengthScaleFactor); - device.def("duration_unit", &fomac::FoMaC::Device::getDurationUnit); + &fomac::Session::Device::getLengthScaleFactor); + device.def("duration_unit", &fomac::Session::Device::getDurationUnit); device.def("duration_scale_factor", - &fomac::FoMaC::Device::getDurationScaleFactor); - device.def("min_atom_distance", &fomac::FoMaC::Device::getMinAtomDistance); + &fomac::Session::Device::getDurationScaleFactor); + device.def("min_atom_distance", &fomac::Session::Device::getMinAtomDistance); device.def("supported_program_formats", - &fomac::FoMaC::Device::getSupportedProgramFormats); - device.def("submit_job", &fomac::FoMaC::Device::submitJob, "program"_a, + &fomac::Session::Device::getSupportedProgramFormats); + device.def("submit_job", &fomac::Session::Device::submitJob, "program"_a, "program_format"_a, "num_shots"_a); - device.def("__repr__", [](const fomac::FoMaC::Device& dev) { + device.def("__repr__", [](const fomac::Session::Device& dev) { return ""; }); device.def(py::self == py::self); // NOLINT(misc-redundant-expression) device.def(py::self != py::self); // NOLINT(misc-redundant-expression) - m.def("devices", &fomac::FoMaC::getDevices); +#ifndef _WIN32 + // Module-level function to add dynamic device libraries on non-Windows + // systems + m.def( + "add_dynamic_device_library", + [](const std::string& libraryPath, const std::string& prefix, + const std::optional& baseUrl = std::nullopt, + const std::optional& token = std::nullopt, + const std::optional& authFile = std::nullopt, + const std::optional& authUrl = std::nullopt, + const std::optional& username = std::nullopt, + const std::optional& password = std::nullopt, + const std::optional& custom1 = std::nullopt, + const std::optional& custom2 = std::nullopt, + const std::optional& custom3 = std::nullopt, + const std::optional& custom4 = std::nullopt, + const std::optional& custom5 = std::nullopt) -> void { + const qdmi::DeviceSessionConfig config{.baseUrl = baseUrl, + .token = token, + .authFile = authFile, + .authUrl = authUrl, + .username = username, + .password = password, + .custom1 = custom1, + .custom2 = custom2, + .custom3 = custom3, + .custom4 = custom4, + .custom5 = custom5}; + qdmi::Driver::get().addDynamicDeviceLibrary(libraryPath, prefix, + config); + }, + "library_path"_a, "prefix"_a, "base_url"_a = std::nullopt, + "token"_a = std::nullopt, "auth_file"_a = std::nullopt, + "auth_url"_a = std::nullopt, "username"_a = std::nullopt, + "password"_a = std::nullopt, "custom1"_a = std::nullopt, + "custom2"_a = std::nullopt, "custom3"_a = std::nullopt, + "custom4"_a = std::nullopt, "custom5"_a = std::nullopt); +#endif // _WIN32 } } // namespace mqt diff --git a/bindings/na/fomac/fomac.cpp b/bindings/na/fomac/fomac.cpp index f2082ffd42..c292f1cf14 100644 --- a/bindings/na/fomac/fomac.cpp +++ b/bindings/na/fomac/fomac.cpp @@ -12,7 +12,7 @@ // clang-format off #include "fomac/FoMaC.hpp" #include "na/fomac/Device.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include @@ -42,7 +42,7 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { pybind11::module_::import("mqt.core.fomac"); auto device = - py::class_(m, "Device"); + py::class_(m, "Device"); auto lattice = py::class_(device, "Lattice"); @@ -91,22 +91,22 @@ PYBIND11_MODULE(MQT_CORE_MODULE_NAME, m, py::mod_gil_not_used()) { lattice.def(py::self == py::self); lattice.def(py::self != py::self); - device.def_property_readonly("traps", &na::FoMaC::Device::getTraps); - device.def_property_readonly("t1", [](const na::FoMaC::Device& dev) { + device.def_property_readonly("traps", &na::Session::Device::getTraps); + device.def_property_readonly("t1", [](const na::Session::Device& dev) { return dev.getDecoherenceTimes().t1; }); - device.def_property_readonly("t2", [](const na::FoMaC::Device& dev) { + device.def_property_readonly("t2", [](const na::Session::Device& dev) { return dev.getDecoherenceTimes().t2; }); - device.def("__repr__", [](const fomac::FoMaC::Device& dev) { + device.def("__repr__", [](const fomac::Session::Device& dev) { return ""; }); device.def(py::self == py::self); device.def(py::self != py::self); - m.def("devices", &na::FoMaC::getDevices); + m.def("devices", &na::Session::getDevices); device.def_static("try_create_from_device", - &na::FoMaC::Device::tryCreateFromDevice, "device"_a); + &na::Session::Device::tryCreateFromDevice, "device"_a); } // NOLINTEND(misc-redundant-expression) } // namespace mqt diff --git a/docs/qdmi/driver.md b/docs/qdmi/driver.md index 3085fbce38..ed836dae88 100644 --- a/docs/qdmi/driver.md +++ b/docs/qdmi/driver.md @@ -19,20 +19,23 @@ Other devices can be loaded dynamically at runtime via {cpp:func}`qdmi::Driver:: The QDMI Driver is implemented in C++ and exposed to Python via [pybind11](https://pybind11.readthedocs.io). Direct binding of the QDMI Client interface functions is not feasible due to technical limitations. -Instead, a FoMaC (Figure of Merits and Constraints) library defines wrapper classes ({cpp:class}`~fomac::FoMaC::Device`, {cpp:class}`~fomac::FoMaC::Device::Site`, {cpp:class}`~fomac::FoMaC::Device::Operation`) for the QDMI entities. -These classes together with their methods are then exposed to Python, see {py:class}`~mqt.core.fomac.Device`, {py:class}`~mqt.core.fomac.Device.Site`, {py:class}`~mqt.core.fomac.Device.Operation`. +Instead, a FoMaC (Figure of Merits and Constraints) library defines wrapper classes ({cpp:class}`~fomac::Session`, {cpp:class}`~fomac::Session::Device`, {cpp:class}`~fomac::Session::Device::Site`, {cpp:class}`~fomac::Session::Device::Operation`, {cpp:class}`~fomac::Session::Job`) for the QDMI entities. +These classes together with their methods are then exposed to Python, see {py:class}`~mqt.core.fomac.Session`, {py:class}`~mqt.core.fomac.Device`, {py:class}`~mqt.core.fomac.Device.Site`, {py:class}`~mqt.core.fomac.Device.Operation`, {py:class}`~mqt.core.fomac.Job`. ## Usage -The following example shows how to get a device from the QDMI driver and access its name. +The following example shows how to create a session and get devices from the QDMI driver. ```{code-cell} ipython3 -from mqt.core.fomac import devices +from mqt.core.fomac import Session -# get a list of all available devices -available_devices = devices() +# Create a session to interact with QDMI devices +session = Session() -# print the name of every device (by default there is only the NA device) +# Get a list of all available devices +available_devices = session.get_devices() + +# Print the name of every device for device in available_devices: print(device.name()) ``` diff --git a/docs/qdmi/qdmi_backend.md b/docs/qdmi/qdmi_backend.md index 76b8e756d5..e6d0517e8c 100644 --- a/docs/qdmi/qdmi_backend.md +++ b/docs/qdmi/qdmi_backend.md @@ -102,6 +102,112 @@ filtered_ddsim = provider.backends(name="DDSIM") # Matches "MQT Core DDSIM QDMI exact = provider.backends(name="MQT Core DDSIM QDMI Device") ``` +## Authentication + +The {py:class}`~mqt.core.plugins.qiskit.QDMIProvider` supports authentication for accessing QDMI devices that require credentials. +Authentication parameters are passed to the provider constructor and forwarded to the underlying session. + +:::{note} +The default local devices (MQT Core DDSIM QDMI Device, MQT NA Default QDMI Device) do not require authentication. +Authentication is primarily used when connecting to remote quantum hardware. +::: + +### Supported Authentication Methods + +The provider supports multiple authentication methods: + +- **Token-based authentication**: Using an API token or access token +- **Username/password authentication**: Traditional credential-based authentication +- **File-based authentication**: Reading credentials from a file +- **URL-based authentication**: Connecting to an authentication server +- **Project-based authentication**: Associating sessions with specific projects, e.g., for accounting or quota management + +### Using Authentication Tokens + +The most common authentication method is using an API token: + +```python +from mqt.core.plugins.qiskit import QDMIProvider + +# Authenticate with a token +provider = QDMIProvider(token="your_api_token_here") + +# Get backends +backends = provider.backends() +for backend in backends: + print(f"{backend.name}: {backend.target.num_qubits} qubits") +``` + +### Username and Password Authentication + +For services that use traditional username/password authentication: + +```python +# Authenticate with username and password +provider = QDMIProvider(username="your_username", password="your_password") + +# Access backend +backend = provider.get_backend("RemoteQuantumDevice") +``` + +### File-Based Authentication + +Store credentials in a secure file for better security: + +```python +# Authenticate using a credentials file +# The file should contain authentication information in the format expected by the service +provider = QDMIProvider(auth_file="/path/to/credentials.txt") +``` + +### Authentication Server URL + +Connect to a custom authentication server: + +```python +# Use a custom authentication URL +provider = QDMIProvider(auth_url="https://auth.quantum-service.com/api/v1/auth") +``` + +### Project-Based Authentication + +Associate your session with a specific project or organization: + +```python +# Specify a project ID +provider = QDMIProvider( + token="your_api_token", project_id="quantum-research-project-2024" +) +``` + +### Combining Authentication Parameters + +Multiple authentication parameters can be combined for services that require multiple credentials: + +```python +# Use multiple authentication parameters +provider = QDMIProvider( + token="your_api_token", + username="your_username", + password="your_password", + project_id="your_project_id", + auth_url="https://custom-auth.example.com", +) +``` + +### Authentication Error Handling + +When authentication fails, the provider raises a `RuntimeError`: + +```python +try: + provider = QDMIProvider(token="invalid_token") + backends = provider.backends() +except RuntimeError as e: + print(f"Authentication failed: {e}") + # Handle authentication error (e.g., prompt for valid credentials) +``` + ## Device Capabilities and Target The backend automatically introspects the FoMaC (QDMI) device and constructs a Qiskit {py:class}`~qiskit.transpiler.Target` diff --git a/include/mqt-core/fomac/FoMaC.hpp b/include/mqt-core/fomac/FoMaC.hpp index 55304bfdf3..2a99cf0f11 100644 --- a/include/mqt-core/fomac/FoMaC.hpp +++ b/include/mqt-core/fomac/FoMaC.hpp @@ -10,6 +10,8 @@ #pragma once +#include "qdmi/Common.hpp" + #include #include #include @@ -18,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -140,53 +143,47 @@ template concept maybe_optional_value_or_string_or_vector = value_or_string_or_vector>; -/// @returns the string representation of the given QDMI_STATUS. -auto toString(QDMI_STATUS result) -> std::string; - -/// @returns the string representation of the given QDMI_Site_Property. -auto toString(QDMI_Site_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Operation_Property. -auto toString(QDMI_Operation_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Device_Property. -auto toString(QDMI_Device_Property prop) -> std::string; - -/// @returns the string representation of the given QDMI_Session_Property. -constexpr auto toString(QDMI_Session_Property prop) -> std::string { - if (prop == QDMI_SESSION_PROPERTY_DEVICES) { - return "QDMI_SESSION_PROPERTY_DEVICES"; - } - return "QDMI_SESSION_PROPERTY_UNKNOWN"; -} - -/// Throws an exception corresponding to the given QDMI_STATUS code. -[[noreturn]] auto throwError(int result, const std::string& msg) -> void; - -/// Throws an exception if the result indicates an error. -inline auto throwIfError(int result, const std::string& msg) -> void { - switch (result) { - case QDMI_SUCCESS: - break; - case QDMI_WARN_GENERAL: - std::cerr << "Warning: " << msg << "\n"; - break; - default: - throwError(result, msg); - } -} +/** + * @brief Configuration structure for session authentication parameters. + * @details All parameters are optional. Only set the parameters needed for + * your authentication method. Parameters are validated when the session is + * constructed. + */ +struct SessionConfig { + /// Authentication token + std::optional token; + /// Path to file containing authentication information + std::optional authFile; + /// URL to authentication server + std::optional authUrl; + /// Username for authentication + std::optional username; + /// Password for authentication + std::optional password; + /// Project ID for session + std::optional projectId; + /// Custom configuration parameter 1 + std::optional custom1; + /// Custom configuration parameter 2 + std::optional custom2; + /// Custom configuration parameter 3 + std::optional custom3; + /// Custom configuration parameter 4 + std::optional custom4; + /// Custom configuration parameter 5 + std::optional custom5; +}; /** - * @brief Class representing the FoMaC library. + * @brief Class representing the Session library. * @details This class provides methods to query available devices and * manage the QDMI session. - * @note This class is a singleton. * @see QDMI_Session */ -class FoMaC { +class Session { /** * @brief Private token class. - * @details Only the FoMaC class can create instances of this class. + * @details Only the Session class can create instances of this class. */ class Token { public: @@ -326,21 +323,24 @@ class FoMaC { QDMI_Site site_; template - [[nodiscard]] auto queryProperty(QDMI_Site_Property prop) const -> T { + [[nodiscard]] auto queryProperty(const QDMI_Site_Property prop) const + -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = QDMI_device_query_site_property( - device_, site_, prop, 0, nullptr, &size); + auto result = QDMI_device_query_site_property(device_, site_, prop, 0, + nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_site_property( - device_, site_, prop, size, value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_site_property(device_, site_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -352,7 +352,7 @@ class FoMaC { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -367,7 +367,7 @@ class FoMaC { : device_(device), site_(site) {} /// @returns the underlying QDMI_Site object. [[nodiscard]] auto getQDMISite() const -> QDMI_Site { return site_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Site() const { return site_; } auto operator<=>(const Site&) const = default; /// @see QDMI_SITE_PROPERTY_INDEX @@ -410,10 +410,12 @@ class FoMaC { QDMI_Operation operation_; template - [[nodiscard]] auto queryProperty(QDMI_Operation_Property prop, + [[nodiscard]] auto queryProperty(const QDMI_Operation_Property prop, const std::vector& sites, const std::vector& params) const -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); std::vector qdmiSites; qdmiSites.reserve(sites.size()); std::ranges::transform( @@ -421,7 +423,7 @@ class FoMaC { [](const Site& site) -> QDMI_Site { return site; }); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = QDMI_device_query_operation_property( + auto result = QDMI_device_query_operation_property( device_, operation_, sites.size(), qdmiSites.data(), params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { @@ -429,18 +431,17 @@ class FoMaC { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_operation_property( + device_, operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; - const auto result = QDMI_device_query_operation_property( + auto result = QDMI_device_query_operation_property( device_, operation_, sites.size(), qdmiSites.data(), params.size(), params.data(), prop, 0, nullptr, &size); if constexpr (is_optional) { @@ -448,14 +449,13 @@ class FoMaC { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_device_query_operation_property( - device_, operation_, sites.size(), qdmiSites.data(), - params.size(), params.data(), prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_operation_property( + device_, operation_, sites.size(), qdmiSites.data(), + params.size(), params.data(), prop, size, value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -468,7 +468,7 @@ class FoMaC { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -486,7 +486,7 @@ class FoMaC { [[nodiscard]] auto getQDMIOperation() const -> QDMI_Operation { return operation_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Operation() const { return operation_; } auto operator<=>(const Operation&) const = default; /// @see QDMI_OPERATION_PROPERTY_NAME @@ -556,38 +556,41 @@ class FoMaC { QDMI_Device device_; template - [[nodiscard]] auto queryProperty(QDMI_Device_Property prop) const -> T { + [[nodiscard]] auto queryProperty(const QDMI_Device_Property prop) const + -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); if constexpr (string_or_optional_string) { size_t size = 0; - const auto result = + auto result = QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); std::string value(size - 1, '\0'); - throwIfError(QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else if constexpr (maybe_optional_size_constructible_contiguous_range< T>) { size_t size = 0; - const auto result = + auto result = QDMI_device_query_device_property(device_, prop, 0, nullptr, &size); if constexpr (is_optional) { if (result == QDMI_ERROR_NOTSUPPORTED) { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_device_query_device_property(device_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_device_query_device_property(device_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } else { remove_optional_t value{}; @@ -598,7 +601,7 @@ class FoMaC { return std::nullopt; } } - throwIfError(result, "Querying " + toString(prop)); + qdmi::throwIfError(result, msg); return value; } } @@ -608,10 +611,10 @@ class FoMaC { * @brief Constructs a Device object from a QDMI_Device handle. * @param device The QDMI_Device handle to wrap. */ - Device(FoMaC::Token /* unused */, QDMI_Device device) : device_(device) {} + Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {} /// @returns the underlying QDMI_Device object. [[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; } - // NOLINTNEXTLINE(google-explicit-constructor) + // NOLINTNEXTLINE(google-explicit-constructor, *-explicit-conversions) operator QDMI_Device() const { return device_; } auto operator<=>(const Device&) const = default; /// @see QDMI_DEVICE_PROPERTY_NAME @@ -675,35 +678,46 @@ class FoMaC { private: QDMI_Session session_ = nullptr; - FoMaC(); - static auto get() -> FoMaC& { - static FoMaC instance; - return instance; - } template [[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const -> T { + std::string msg = "Querying "; + msg += qdmi::toString(prop); size_t size = 0; - throwIfError( - QDMI_session_query_session_property(session_, prop, 0, nullptr, &size), - "Querying " + toString(prop)); + auto result = + QDMI_session_query_session_property(session_, prop, 0, nullptr, &size); + qdmi::throwIfError(result, msg); remove_optional_t value( size / sizeof(typename remove_optional_t::value_type)); - throwIfError(QDMI_session_query_session_property(session_, prop, size, - value.data(), nullptr), - "Querying " + toString(prop)); + result = QDMI_session_query_session_property(session_, prop, size, + value.data(), nullptr); + qdmi::throwIfError(result, msg); return value; } public: - virtual ~FoMaC(); - // Delete copy constructors and assignment operators to prevent copying the - // singleton instance. - FoMaC(const FoMaC&) = delete; - FoMaC& operator=(const FoMaC&) = delete; - FoMaC(FoMaC&&) = default; - FoMaC& operator=(FoMaC&&) = default; + /** + * @brief Constructs a new QDMI Session with optional authentication. + * @param config Optional session configuration containing authentication + * parameters. If not provided, uses default (no authentication). + * @details Creates, allocates, and initializes a new QDMI session. + */ + explicit Session(const SessionConfig& config = {}); + + /** + * @brief Destructor that releases the QDMI session. + */ + ~Session(); + + // Delete copy constructors and assignment operators + Session(const Session&) = delete; + Session& operator=(const Session&) = delete; + + // Allow move semantics + Session(Session&&) noexcept; + Session& operator=(Session&&) noexcept; + /// @see QDMI_SESSION_PROPERTY_DEVICES - [[nodiscard]] static auto getDevices() -> std::vector; + [[nodiscard]] auto getDevices() -> std::vector; }; } // namespace fomac diff --git a/include/mqt-core/na/fomac/Device.hpp b/include/mqt-core/na/fomac/Device.hpp index 5d5626cf55..58049436c3 100644 --- a/include/mqt-core/na/fomac/Device.hpp +++ b/include/mqt-core/na/fomac/Device.hpp @@ -11,7 +11,7 @@ #pragma once #include "fomac/FoMaC.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" // NOLINTNEXTLINE(misc-include-cleaner) #include @@ -20,18 +20,18 @@ namespace na { /** - * @brief Class representing the FoMaC library with neutral atom extensions. - * @see fomac::FoMaC + * @brief Class representing the Session library with neutral atom extensions. + * @see fomac::Session */ -class FoMaC : public fomac::FoMaC { +class Session : public fomac::Session { public: /** * @brief Class representing a quantum device with neutral atom extensions. - * @see fomac::FoMaC::Device + * @see fomac::Session::Device * @note Since it inherits from @ref na::Device, Device objects can be * converted to `nlohmann::json` objects. */ - class Device : public fomac::FoMaC::Device, na::Device { + class Device : public fomac::Session::Device, na::Device { /** * @brief Initializes the name from the underlying QDMI device. @@ -79,13 +79,13 @@ class FoMaC : public fomac::FoMaC { auto initOperationsFromDevice() -> bool; /** - * @brief Constructs a Device object from a fomac::FoMaC::Device object. - * @param device The fomac::FoMaC::Device object to wrap. + * @brief Constructs a Device object from a fomac::Session::Device object. + * @param device The fomac::Session::Device object to wrap. * @note The constructor does not initialize the additional fields of this * class. For their initialization, the corresponding `init*FromDevice` * methods must be called, see @ref tryCreateFromDevice. */ - explicit Device(const fomac::FoMaC::Device& device); + explicit Device(const fomac::Session::Device& device); public: /// @returns the length unit of the device. @@ -109,17 +109,18 @@ class FoMaC : public fomac::FoMaC { } /** - * @brief Try to create a Device object from a fomac::FoMaC::Device object. + * @brief Try to create a Device object from a fomac::Session::Device + * object. * @details This method attempts to create a Device object by initializing - * all necessary fields from the provided fomac::FoMaC::Device object. If + * all necessary fields from the provided fomac::Session::Device object. If * any required information is missing or invalid, the method returns * `std::nullopt`. - * @param device is the fomac::FoMaC::Device object to wrap. + * @param device is the fomac::Session::Device object to wrap. * @return An optional containing the instantiated device if compatible, * std::nullopt otherwise. */ [[nodiscard]] static auto - tryCreateFromDevice(const fomac::FoMaC::Device& device) + tryCreateFromDevice(const fomac::Session::Device& device) -> std::optional { Device d(device); // The sequence of the following method calls does not matter. @@ -160,7 +161,7 @@ class FoMaC : public fomac::FoMaC { }; /// @brief Deleted default constructor to prevent instantiation. - FoMaC() = delete; + Session() = delete; /// @see QDMI_SESSION_PROPERTY_DEVICES [[nodiscard]] static auto getDevices() -> std::vector; diff --git a/include/mqt-core/qdmi/Common.hpp b/include/mqt-core/qdmi/Common.hpp new file mode 100644 index 0000000000..c3a75cd73f --- /dev/null +++ b/include/mqt-core/qdmi/Common.hpp @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +/// @file Common.hpp +/// @brief Common definitions and utilities for working with QDMI in C++. +/// @note This header will be upstreamed to the QDMI core library in the future. + +#pragma once + +#include +#include +#include + +namespace qdmi { +/** + * @brief Function used to mark unreachable code + * @details Uses compiler specific extensions if possible. Even if no extension + * is used, undefined behavior is still raised by an empty function body and the + * noreturn attribute. + */ +[[noreturn]] inline void unreachable() { +#ifdef __GNUC__ // GCC, Clang, ICC + __builtin_unreachable(); +#elif defined(_MSC_VER) // MSVC + __assume(false); +#endif +} + +// NOLINTBEGIN(bugprone-macro-parentheses) +#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ + size, value, size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < sizeof(prop_type)) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + *static_cast(value) = prop_value; \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = sizeof(prop_type); \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +// STRNCPY wrapper: strncpy_s on Windows (auto null-terminates), +// strncpy on other platforms (requires manual null-termination - see usage). +#ifdef _WIN32 +#define STRNCPY(dest, src, size) \ + strncpy_s(static_cast(dest), size, src, size); +#else +#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); +#endif + +#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ + size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < strlen(prop_value) + 1) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + STRNCPY(value, prop_value, size); \ + /* Ensure null-termination: strncpy doesn't guarantee it on non-Win */ \ + /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ + static_cast(value)[size - 1] = '\0'; \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = strlen(prop_value) + 1; \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ + value, size_ret) \ + { \ + if ((prop) == (prop_name)) { \ + if ((value) != nullptr) { \ + if ((size) < (prop_values).size() * sizeof(prop_type)) { \ + return QDMI_ERROR_INVALIDARGUMENT; \ + } \ + memcpy(static_cast(value), \ + static_cast((prop_values).data()), \ + (prop_values).size() * sizeof(prop_type)); \ + } \ + if ((size_ret) != nullptr) { \ + *size_ret = (prop_values).size() * sizeof(prop_type); \ + } \ + return QDMI_SUCCESS; \ + } \ + } + +#define IS_INVALID_ARGUMENT(prop, prefix) \ + ((prop) >= prefix##_MAX && (prop) != prefix##_CUSTOM1 && \ + (prop) != prefix##_CUSTOM2 && (prop) != prefix##_CUSTOM3 && \ + (prop) != prefix##_CUSTOM4 && (prop) != prefix##_CUSTOM5) +// NOLINTEND(bugprone-macro-parentheses) + +/// Returns the string representation of the given status code @p result. +constexpr auto toString(const QDMI_STATUS result) -> const char* { + switch (result) { + case QDMI_WARN_GENERAL: + return "General warning"; + case QDMI_SUCCESS: + return "Success"; + case QDMI_ERROR_FATAL: + return "A fatal error"; + case QDMI_ERROR_OUTOFMEM: + return "Out of memory"; + case QDMI_ERROR_NOTIMPLEMENTED: + return "Not implemented"; + case QDMI_ERROR_LIBNOTFOUND: + return "Library not found"; + case QDMI_ERROR_NOTFOUND: + return "Element not found"; + case QDMI_ERROR_OUTOFRANGE: + return "Out of range"; + case QDMI_ERROR_INVALIDARGUMENT: + return "Invalid argument"; + case QDMI_ERROR_PERMISSIONDENIED: + return "Permission denied"; + case QDMI_ERROR_NOTSUPPORTED: + return "Not supported"; + case QDMI_ERROR_BADSTATE: + return "Bad state"; + case QDMI_ERROR_TIMEOUT: + return "Timeout"; + } + unreachable(); +} + +/** + * @brief Throws an exception if the result indicates an error. + * @param result The result of a QDMI operation + * @param msg The error message to include in the exception + * @throws std::bad_alloc if the result is QDMI_ERROR_OUTOFMEM + * @throws std::out_of_range if the result is QDMI_ERROR_OUTOFRANGE + * @throws std::invalid_argument if the result is QDMI_ERROR_INVALIDARGUMENT + * @throws std::runtime_error for all other error results + */ +auto throwIfError(int result, const std::string& msg) -> void; + +/// Returns the string representation of the given session parameter @p param. +constexpr auto toString(const QDMI_Session_Parameter param) -> const char* { + switch (param) { + case QDMI_SESSION_PARAMETER_TOKEN: + return "TOKEN"; + case QDMI_SESSION_PARAMETER_AUTHFILE: + return "AUTH FILE"; + case QDMI_SESSION_PARAMETER_AUTHURL: + return "AUTH URL"; + case QDMI_SESSION_PARAMETER_USERNAME: + return "USERNAME"; + case QDMI_SESSION_PARAMETER_PASSWORD: + return "PASSWORD"; + case QDMI_SESSION_PARAMETER_PROJECTID: + return "PROJECT ID"; + case QDMI_SESSION_PARAMETER_MAX: + return "MAX"; + case QDMI_SESSION_PARAMETER_CUSTOM1: + return "CUSTOM1"; + case QDMI_SESSION_PARAMETER_CUSTOM2: + return "CUSTOM2"; + case QDMI_SESSION_PARAMETER_CUSTOM3: + return "CUSTOM3"; + case QDMI_SESSION_PARAMETER_CUSTOM4: + return "CUSTOM4"; + case QDMI_SESSION_PARAMETER_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given session property @p prop. +constexpr auto toString(const QDMI_Session_Property prop) -> const char* { + switch (prop) { + case QDMI_SESSION_PROPERTY_DEVICES: + return "DEVICES"; + case QDMI_SESSION_PROPERTY_MAX: + return "MAX"; + case QDMI_SESSION_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_SESSION_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_SESSION_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_SESSION_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_SESSION_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given device session parameter +/// @p param. +constexpr auto toString(const QDMI_Device_Session_Parameter param) -> const + char* { + switch (param) { + case QDMI_DEVICE_SESSION_PARAMETER_BASEURL: + return "BASE URL"; + case QDMI_DEVICE_SESSION_PARAMETER_TOKEN: + return "TOKEN"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE: + return "AUTH FILE"; + case QDMI_DEVICE_SESSION_PARAMETER_AUTHURL: + return "AUTH URL"; + case QDMI_DEVICE_SESSION_PARAMETER_USERNAME: + return "USERNAME"; + case QDMI_DEVICE_SESSION_PARAMETER_PASSWORD: + return "PASSWORD"; + case QDMI_DEVICE_SESSION_PARAMETER_MAX: + return "MAX"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1: + return "CUSTOM1"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2: + return "CUSTOM2"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3: + return "CUSTOM3"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4: + return "CUSTOM4"; + case QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given site property @p prop. +constexpr auto toString(const QDMI_Site_Property prop) -> const char* { + switch (prop) { + case QDMI_SITE_PROPERTY_INDEX: + return "INDEX"; + case QDMI_SITE_PROPERTY_T1: + return "T1"; + case QDMI_SITE_PROPERTY_T2: + return "T2"; + case QDMI_SITE_PROPERTY_NAME: + return "NAME"; + case QDMI_SITE_PROPERTY_XCOORDINATE: + return "X COORDINATE"; + case QDMI_SITE_PROPERTY_YCOORDINATE: + return "Y COORDINATE"; + case QDMI_SITE_PROPERTY_ZCOORDINATE: + return "Z COORDINATE"; + case QDMI_SITE_PROPERTY_ISZONE: + return "IS ZONE"; + case QDMI_SITE_PROPERTY_XEXTENT: + return "X EXTENT"; + case QDMI_SITE_PROPERTY_YEXTENT: + return "Y EXTENT"; + case QDMI_SITE_PROPERTY_ZEXTENT: + return "Z EXTENT"; + case QDMI_SITE_PROPERTY_MODULEINDEX: + return "MODULE INDEX"; + case QDMI_SITE_PROPERTY_SUBMODULEINDEX: + return "SUBMODULE INDEX"; + case QDMI_SITE_PROPERTY_MAX: + return "MAX"; + case QDMI_SITE_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_SITE_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_SITE_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_SITE_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_SITE_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given operation property @p prop. +constexpr auto toString(const QDMI_Operation_Property prop) -> const char* { + switch (prop) { + case QDMI_OPERATION_PROPERTY_NAME: + return "NAME"; + case QDMI_OPERATION_PROPERTY_QUBITSNUM: + return "QUBITS NUM"; + case QDMI_OPERATION_PROPERTY_PARAMETERSNUM: + return "PARAMETERS NUM"; + case QDMI_OPERATION_PROPERTY_DURATION: + return "DURATION"; + case QDMI_OPERATION_PROPERTY_FIDELITY: + return "FIDELITY"; + case QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS: + return "INTERACTION RADIUS"; + case QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS: + return "BLOCKING RADIUS"; + case QDMI_OPERATION_PROPERTY_IDLINGFIDELITY: + return "IDLING FIDELITY"; + case QDMI_OPERATION_PROPERTY_ISZONED: + return "IS ZONED"; + case QDMI_OPERATION_PROPERTY_SITES: + return "SITES"; + case QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED: + return "MEAN SHUTTLING SPEED"; + case QDMI_OPERATION_PROPERTY_MAX: + return "MAX"; + case QDMI_OPERATION_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_OPERATION_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_OPERATION_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_OPERATION_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_OPERATION_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +/// Returns the string representation of the given device property @p prop. +constexpr auto toString(const QDMI_Device_Property prop) -> const char* { + switch (prop) { + case QDMI_DEVICE_PROPERTY_NAME: + return "NAME"; + case QDMI_DEVICE_PROPERTY_VERSION: + return "VERSION"; + case QDMI_DEVICE_PROPERTY_STATUS: + return "STATUS"; + case QDMI_DEVICE_PROPERTY_LIBRARYVERSION: + return "LIBRARY VERSION"; + case QDMI_DEVICE_PROPERTY_QUBITSNUM: + return "QUBITS NUM"; + case QDMI_DEVICE_PROPERTY_SITES: + return "SITES"; + case QDMI_DEVICE_PROPERTY_OPERATIONS: + return "OPERATIONS"; + case QDMI_DEVICE_PROPERTY_COUPLINGMAP: + return "COUPLING MAP"; + case QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION: + return "NEEDS CALIBRATION"; + case QDMI_DEVICE_PROPERTY_LENGTHUNIT: + return "LENGTH UNIT"; + case QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR: + return "LENGTH SCALE FACTOR"; + case QDMI_DEVICE_PROPERTY_DURATIONUNIT: + return "DURATION UNIT"; + case QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR: + return "DURATION SCALE FACTOR"; + case QDMI_DEVICE_PROPERTY_MINATOMDISTANCE: + return "MIN ATOM DISTANCE"; + case QDMI_DEVICE_PROPERTY_PULSESUPPORT: + return "PULSE SUPPORT"; + case QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS: + return "SUPPORTED PROGRAM FORMATS"; + case QDMI_DEVICE_PROPERTY_MAX: + return "MAX"; + case QDMI_DEVICE_PROPERTY_CUSTOM1: + return "CUSTOM1"; + case QDMI_DEVICE_PROPERTY_CUSTOM2: + return "CUSTOM2"; + case QDMI_DEVICE_PROPERTY_CUSTOM3: + return "CUSTOM3"; + case QDMI_DEVICE_PROPERTY_CUSTOM4: + return "CUSTOM4"; + case QDMI_DEVICE_PROPERTY_CUSTOM5: + return "CUSTOM5"; + } + unreachable(); +} + +} // namespace qdmi diff --git a/include/mqt-core/qdmi/Driver.hpp b/include/mqt-core/qdmi/Driver.hpp index 9a8735a3ce..34fd17f124 100644 --- a/include/mqt-core/qdmi/Driver.hpp +++ b/include/mqt-core/qdmi/Driver.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,36 @@ namespace qdmi { +/** + * @brief Configuration for device session parameters. + * @details This struct holds optional parameters that can be set on a device + * session before initialization. All parameters are optional. + */ +struct DeviceSessionConfig { + /// Base URL for API endpoint + std::optional baseUrl; + /// Authentication token + std::optional token; + /// Path to file containing authentication information + std::optional authFile; + /// URL to authentication server + std::optional authUrl; + /// Username for authentication + std::optional username; + /// Password for authentication + std::optional password; + /// Custom configuration parameter 1 + std::optional custom1; + /// Custom configuration parameter 2 + std::optional custom2; + /// Custom configuration parameter 3 + std::optional custom3; + /// Custom configuration parameter 4 + std::optional custom4; + /// Custom configuration parameter 5 + std::optional custom5; +}; + /** * @brief Definition of the device library. * @details The device library contains function pointers to the QDMI @@ -97,16 +128,6 @@ class DynamicDeviceLibrary final : public DeviceLibrary { /// @brief Handle to the dynamic library returned by `dlopen`. void* libHandle_; - /** - * @brief Returns a reference to the set of open library handles. - * @details This set can be used to check whether a newly opened library - * is already open. - */ - static auto openLibHandles() -> std::unordered_set& { - static std::unordered_set libHandles; - return libHandles; - } - public: /** * @brief Constructs a DynamicDeviceLibrary object. @@ -182,8 +203,10 @@ struct QDMI_Device_impl_d { * the device session handle. * @param lib is a unique pointer to the device library that provides the * device interface functions. + * @param config is the configuration for device session parameters. */ - explicit QDMI_Device_impl_d(std::unique_ptr&& lib); + explicit QDMI_Device_impl_d(std::unique_ptr&& lib, + const qdmi::DeviceSessionConfig& config = {}); /** * @brief Destructor for the QDMI device. @@ -407,26 +430,21 @@ class Driver final { ~Driver(); #ifndef _WIN32 /** - * @brief Adds a dynamic device library to the driver. - * @details This function attempts to load a dynamic library containing - * QDMI device interface functions. If the library is already loaded, the - * function returns `false`. Otherwise, it loads the library, initializes - * the device, and adds it to the list of devices. + * @brief Loads a dynamic device library and adds it to the driver. * * @param libName The path to the dynamic library to load. * @param prefix The prefix used for the device interface functions in the * library. - * @returns `true` if the library was successfully loaded, `false` if it was - * already loaded. + * @param config Configuration for device session parameters. * * @note This function is only available on non-Windows platforms. * - * @throws std::runtime_error If the library fails to load or the device - * cannot be initialized. + * @throws std::runtime_error If the device cannot be initialized. * @throws std::bad_alloc If memory allocation fails during the process. */ auto addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix) -> bool; + const std::string& prefix, + const DeviceSessionConfig& config = {}) -> void; #endif // _WIN32 /** * @brief Allocates a new session. diff --git a/include/mqt-core/qdmi/dd/Device.hpp b/include/mqt-core/qdmi/dd/Device.hpp index 0b1307ef97..4247306e27 100644 --- a/include/mqt-core/qdmi/dd/Device.hpp +++ b/include/mqt-core/qdmi/dd/Device.hpp @@ -46,12 +46,12 @@ class Device final { std::unique_ptr> sessions_; /// Mutex protecting access to sessions_. - mutable std::mutex sessionsMutex_{}; + mutable std::mutex sessionsMutex_; /// RNG for generating unique IDs. std::mt19937_64 rng_{std::random_device{}()}; /// Mutex protecting RNG usage. - mutable std::mutex rngMutex_{}; + mutable std::mutex rngMutex_; /// Distribution for generating unique IDs. std::uniform_int_distribution<> dis_ = @@ -63,9 +63,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - static std::atomic instance; - public: // Default move constructor and move assignment operator. Device(Device&&) = delete; @@ -77,19 +74,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device() = default; - /** - * @brief Initializes the singleton instance. - * @details Must be called before `get()`. - */ - static void initialize(); - - /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; @@ -143,7 +127,7 @@ struct MQT_DDSIM_QDMI_Device_Session_impl_d { std::unique_ptr> jobs_; /// @brief Mutex protecting access to jobs_. - mutable std::mutex jobsMutex_{}; + mutable std::mutex jobsMutex_; public: /** diff --git a/include/mqt-core/na/device/Device.hpp b/include/mqt-core/qdmi/na/Device.hpp similarity index 97% rename from include/mqt-core/na/device/Device.hpp rename to include/mqt-core/qdmi/na/Device.hpp index 7f1ed958df..71990cd35f 100644 --- a/include/mqt-core/na/device/Device.hpp +++ b/include/mqt-core/qdmi/na/Device.hpp @@ -16,7 +16,6 @@ #include "mqt_na_qdmi/device.h" -#include #include #include #include @@ -69,9 +68,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - static std::atomic instance; - public: // Default move constructor and move assignment operator. Device(Device&&) = default; @@ -83,19 +79,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device() = default; - /** - * @brief Initializes the singleton instance. - * @details Must be called before `get()`. - */ - static void initialize(); - - /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; diff --git a/include/mqt-core/na/device/Generator.hpp b/include/mqt-core/qdmi/na/Generator.hpp similarity index 100% rename from include/mqt-core/na/device/Generator.hpp rename to include/mqt-core/qdmi/na/Generator.hpp diff --git a/include/mqt-core/qdmi/sc/Device.hpp b/include/mqt-core/qdmi/sc/Device.hpp index 3376b6a06e..937168538a 100644 --- a/include/mqt-core/qdmi/sc/Device.hpp +++ b/include/mqt-core/qdmi/sc/Device.hpp @@ -16,11 +16,9 @@ #include "mqt_sc_qdmi/device.h" -#include #include #include #include -#include #include #include #include @@ -52,9 +50,6 @@ class Device final { /// @brief Private constructor to enforce the singleton pattern. Device(); - /// @brief The singleton instance. - static std::atomic instance; - public: // Delete move constructor and move assignment operator. Device(Device&&) = delete; @@ -66,19 +61,6 @@ class Device final { /// @brief Destructor for the Device class. ~Device(); - /** - * @brief Initializes the singleton instance. - * @details Must be called before `get()`. - */ - static void initialize(); - - /** - * @brief Destroys the singleton instance. - * @details After this call, `get()` must not be called until a new - * `initialize()` call. - */ - static void finalize(); - /// @returns the singleton instance of the Device class. [[nodiscard]] static auto get() -> Device&; diff --git a/python/mqt/core/fomac.pyi b/python/mqt/core/fomac.pyi index c81cda9415..e2d6ad8d93 100644 --- a/python/mqt/core/fomac.pyi +++ b/python/mqt/core/fomac.pyi @@ -6,6 +6,7 @@ # # Licensed under the MIT License +import sys from collections.abc import Iterable from enum import Enum @@ -13,9 +14,13 @@ __all__ = [ "Device", "Job", "ProgramFormat", - "devices", + "Session", ] +# add_dynamic_device_library is only available on non-Windows platforms +if sys.platform != "win32": + __all__ += ["add_dynamic_device_library"] + class ProgramFormat(Enum): """Enumeration of program formats.""" @@ -34,6 +39,76 @@ class ProgramFormat(Enum): CUSTOM4 = ... CUSTOM5 = ... +class Session: + """A FoMaC session for managing QDMI devices. + + Allows creating isolated sessions with independent authentication settings. + All authentication parameters are optional and can be provided as keyword + arguments to the constructor. + """ + + def __init__( + self, + *, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + project_id: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, + ) -> None: + """Create a new FoMaC session with optional authentication. + + Args: + token: Authentication token + auth_file: Path to file containing authentication information + auth_url: URL to authentication server + username: Username for authentication + password: Password for authentication + project_id: Project ID for session + custom1: Custom configuration parameter 1 + custom2: Custom configuration parameter 2 + custom3: Custom configuration parameter 3 + custom4: Custom configuration parameter 4 + custom5: Custom configuration parameter 5 + + Raises: + RuntimeError: If auth_file does not exist + RuntimeError: If auth_url has invalid format + + Example: + >>> from mqt.core.fomac import Session + >>> # Session without authentication + >>> session = Session() + >>> devices = session.get_devices() + >>> + >>> # Session with token authentication + >>> session = Session(token="my_secret_token") + >>> devices = session.get_devices() + >>> + >>> # Session with file-based authentication + >>> session = Session(auth_file="/path/to/auth.json") + >>> devices = session.get_devices() + >>> + >>> # Session with multiple parameters + >>> session = Session( + ... auth_url="https://auth.example.com", username="user", password="pass", project_id="project-123" + ... ) + >>> devices = session.get_devices() + """ + + def get_devices(self) -> list[Device]: + """Get available devices from this session. + + Returns: + List of available devices + """ + class Job: """A job represents a submitted quantum program execution.""" @@ -205,5 +280,59 @@ class Device: def __ne__(self, other: object) -> bool: """Checks if two devices are not equal.""" -def devices() -> list[Device]: - """Returns a list of available devices.""" +# Dynamic library loading is only available on non-Windows platforms +if sys.platform != "win32": + def add_dynamic_device_library( + library_path: str, + prefix: str, + *, + base_url: str | None = None, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + custom1: str | None = None, + custom2: str | None = None, + custom3: str | None = None, + custom4: str | None = None, + custom5: str | None = None, + ) -> None: + """Load a dynamic device library into the QDMI driver. + + This function loads a shared library (.so, .dll, or .dylib) that implements + a QDMI device interface and makes it available for use in sessions. + + Note: This function is only available on non-Windows platforms. + + Args: + library_path: Path to the shared library file to load. + prefix: Function prefix used by the library (e.g., "MY_DEVICE"). + base_url: Optional base URL for the device API endpoint. + token: Optional authentication token. + auth_file: Optional path to authentication file. + auth_url: Optional authentication server URL. + username: Optional username for authentication. + password: Optional password for authentication. + custom1: Optional custom configuration parameter 1. + custom2: Optional custom configuration parameter 2. + custom3: Optional custom configuration parameter 3. + custom4: Optional custom configuration parameter 4. + custom5: Optional custom configuration parameter 5. + + Raises: + RuntimeError: If library loading fails or configuration is invalid. + + Examples: + Load a device library with configuration: + + >>> import mqt.core.fomac as fomac + >>> fomac.add_dynamic_device_library( + ... "/path/to/libmy_device.so", "MY_DEVICE", base_url="http://localhost:8080", custom1="API_V2" + ... ) + + Now the device is available in sessions: + + >>> session = fomac.Session() + >>> devices = session.get_devices() + """ diff --git a/python/mqt/core/plugins/qiskit/provider.py b/python/mqt/core/plugins/qiskit/provider.py index e3a5147928..bc673a9f62 100644 --- a/python/mqt/core/plugins/qiskit/provider.py +++ b/python/mqt/core/plugins/qiskit/provider.py @@ -42,12 +42,50 @@ class QDMIProvider: Get a specific backend by name: >>> backend = provider.get_backend("MQT Core DDSIM QDMI Device") + + Create a provider with authentication: + + >>> provider = QDMIProvider(token="my_token") + >>> # or with username and password + >>> provider = QDMIProvider(username="user", password="pass") """ - def __init__(self) -> None: - """Initialize the QDMI provider.""" + def __init__( + self, + *, + token: str | None = None, + auth_file: str | None = None, + auth_url: str | None = None, + username: str | None = None, + password: str | None = None, + project_id: str | None = None, + **session_kwargs: str, + ) -> None: + """Initialize the QDMI provider. + + Args: + token: Authentication token for the session. + auth_file: Path to file containing authentication information. + auth_url: URL to authentication server. + username: Username for authentication. + password: Password for authentication. + project_id: Project ID for the session. + session_kwargs: Optional additional keyword arguments for Session initialization. + """ + kwargs = { + "token": token, + "auth_file": auth_file, + "auth_url": auth_url, + "username": username, + "password": password, + "project_id": project_id, + } + if session_kwargs: + kwargs.update(session_kwargs) + + self._session = fomac.Session(**kwargs) self._backends = [ - QDMIBackend(device=d, provider=self) for d in fomac.devices() if QDMIBackend.is_convertible(d) + QDMIBackend(device=d, provider=self) for d in self._session.get_devices() if QDMIBackend.is_convertible(d) ] def backends(self, name: str | None = None) -> list[QDMIBackend]: diff --git a/src/fomac/CMakeLists.txt b/src/fomac/CMakeLists.txt index ebe79627f7..554e1e7959 100644 --- a/src/fomac/CMakeLists.txt +++ b/src/fomac/CMakeLists.txt @@ -22,7 +22,7 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries( ${TARGET_NAME} - PUBLIC qdmi::qdmi MQT::CoreQDMIDriver + PUBLIC qdmi::qdmi MQT::CoreQDMICommon MQT::CoreQDMIDriver PRIVATE spdlog::spdlog) # add to list of MQT core targets diff --git a/src/fomac/FoMaC.cpp b/src/fomac/FoMaC.cpp index 945884f0b0..d907ae3136 100644 --- a/src/fomac/FoMaC.cpp +++ b/src/fomac/FoMaC.cpp @@ -10,14 +10,19 @@ #include "fomac/FoMaC.hpp" +#include "qdmi/Common.hpp" + #include #include #include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -25,289 +30,102 @@ #include namespace fomac { - -namespace { -/** - * @brief Function used to mark unreachable code - * @details Uses compiler specific extensions if possible. Even if no extension - * is used, undefined behavior is still raised by an empty function body and the - * noreturn attribute. - */ -[[noreturn]] inline void unreachable() { -#ifdef __GNUC__ // GCC, Clang, ICC - __builtin_unreachable(); -#elif defined(_MSC_VER) // MSVC - __assume(false); -#endif -} -} // namespace - -auto toString(const QDMI_STATUS result) -> std::string { - switch (result) { - case QDMI_WARN_GENERAL: - return "General warning"; - case QDMI_SUCCESS: - return "Success"; - case QDMI_ERROR_FATAL: - return "A fatal error"; - case QDMI_ERROR_OUTOFMEM: - return "Out of memory"; - case QDMI_ERROR_NOTIMPLEMENTED: - return "Not implemented"; - case QDMI_ERROR_LIBNOTFOUND: - return "Library not found"; - case QDMI_ERROR_NOTFOUND: - return "Element not found"; - case QDMI_ERROR_OUTOFRANGE: - return "Out of range"; - case QDMI_ERROR_INVALIDARGUMENT: - return "Invalid argument"; - case QDMI_ERROR_PERMISSIONDENIED: - return "Permission denied"; - case QDMI_ERROR_NOTSUPPORTED: - return "Not supported"; - case QDMI_ERROR_BADSTATE: - return "Bad state"; - case QDMI_ERROR_TIMEOUT: - return "Timeout"; - } - unreachable(); -} -auto toString(QDMI_Site_Property prop) -> std::string { - switch (prop) { - case QDMI_SITE_PROPERTY_INDEX: - return "QDMI_SITE_PROPERTY_INDEX"; - case QDMI_SITE_PROPERTY_T1: - return "QDMI_SITE_PROPERTY_T1"; - case QDMI_SITE_PROPERTY_T2: - return "QDMI_SITE_PROPERTY_T2"; - case QDMI_SITE_PROPERTY_NAME: - return "QDMI_SITE_PROPERTY_NAME"; - case QDMI_SITE_PROPERTY_XCOORDINATE: - return "QDMI_SITE_PROPERTY_XCOORDINATE"; - case QDMI_SITE_PROPERTY_YCOORDINATE: - return "QDMI_SITE_PROPERTY_YCOORDINATE"; - case QDMI_SITE_PROPERTY_ZCOORDINATE: - return "QDMI_SITE_PROPERTY_ZCOORDINATE"; - case QDMI_SITE_PROPERTY_ISZONE: - return "QDMI_SITE_PROPERTY_ISZONE"; - case QDMI_SITE_PROPERTY_XEXTENT: - return "QDMI_SITE_PROPERTY_XEXTENT"; - case QDMI_SITE_PROPERTY_YEXTENT: - return "QDMI_SITE_PROPERTY_YEXTENT"; - case QDMI_SITE_PROPERTY_ZEXTENT: - return "QDMI_SITE_PROPERTY_ZEXTENT"; - case QDMI_SITE_PROPERTY_MODULEINDEX: - return "QDMI_SITE_PROPERTY_MODULEINDEX"; - case QDMI_SITE_PROPERTY_SUBMODULEINDEX: - return "QDMI_SITE_PROPERTY_SUBMODULEINDEX"; - case QDMI_SITE_PROPERTY_MAX: - case QDMI_SITE_PROPERTY_CUSTOM1: - case QDMI_SITE_PROPERTY_CUSTOM2: - case QDMI_SITE_PROPERTY_CUSTOM3: - case QDMI_SITE_PROPERTY_CUSTOM4: - case QDMI_SITE_PROPERTY_CUSTOM5: - return "QDMI_SITE_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto toString(QDMI_Operation_Property prop) -> std::string { - switch (prop) { - case QDMI_OPERATION_PROPERTY_NAME: - return "QDMI_OPERATION_PROPERTY_NAME"; - case QDMI_OPERATION_PROPERTY_QUBITSNUM: - return "QDMI_OPERATION_PROPERTY_QUBITSNUM"; - case QDMI_OPERATION_PROPERTY_PARAMETERSNUM: - return "QDMI_OPERATION_PROPERTY_PARAMETERSNUM"; - case QDMI_OPERATION_PROPERTY_DURATION: - return "QDMI_OPERATION_PROPERTY_DURATION"; - case QDMI_OPERATION_PROPERTY_FIDELITY: - return "QDMI_OPERATION_PROPERTY_FIDELITY"; - case QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS: - return "QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS"; - case QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS: - return "QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS"; - case QDMI_OPERATION_PROPERTY_IDLINGFIDELITY: - return "QDMI_OPERATION_PROPERTY_IDLINGFIDELITY"; - case QDMI_OPERATION_PROPERTY_ISZONED: - return "QDMI_OPERATION_PROPERTY_ISZONED"; - case QDMI_OPERATION_PROPERTY_SITES: - return "QDMI_OPERATION_PROPERTY_SITES"; - case QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED: - return "QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED"; - case QDMI_OPERATION_PROPERTY_MAX: - case QDMI_OPERATION_PROPERTY_CUSTOM1: - case QDMI_OPERATION_PROPERTY_CUSTOM2: - case QDMI_OPERATION_PROPERTY_CUSTOM3: - case QDMI_OPERATION_PROPERTY_CUSTOM4: - case QDMI_OPERATION_PROPERTY_CUSTOM5: - return "QDMI_OPERATION_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto toString(QDMI_Device_Property prop) -> std::string { - switch (prop) { - case QDMI_DEVICE_PROPERTY_NAME: - return "QDMI_DEVICE_PROPERTY_NAME"; - case QDMI_DEVICE_PROPERTY_VERSION: - return "QDMI_DEVICE_PROPERTY_VERSION"; - case QDMI_DEVICE_PROPERTY_STATUS: - return "QDMI_DEVICE_PROPERTY_STATUS"; - case QDMI_DEVICE_PROPERTY_LIBRARYVERSION: - return "QDMI_DEVICE_PROPERTY_LIBRARYVERSION"; - case QDMI_DEVICE_PROPERTY_QUBITSNUM: - return "QDMI_DEVICE_PROPERTY_QUBITSNUM"; - case QDMI_DEVICE_PROPERTY_SITES: - return "QDMI_DEVICE_PROPERTY_SITES"; - case QDMI_DEVICE_PROPERTY_OPERATIONS: - return "QDMI_DEVICE_PROPERTY_OPERATIONS"; - case QDMI_DEVICE_PROPERTY_COUPLINGMAP: - return "QDMI_DEVICE_PROPERTY_COUPLINGMAP"; - case QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION: - return "QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION"; - case QDMI_DEVICE_PROPERTY_LENGTHUNIT: - return "QDMI_DEVICE_PROPERTY_LENGTHUNIT"; - case QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR: - return "QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR"; - case QDMI_DEVICE_PROPERTY_DURATIONUNIT: - return "QDMI_DEVICE_PROPERTY_DURATIONUNIT"; - case QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR: - return "QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR"; - case QDMI_DEVICE_PROPERTY_MINATOMDISTANCE: - return "QDMI_DEVICE_PROPERTY_MINATOMDISTANCE"; - case QDMI_DEVICE_PROPERTY_PULSESUPPORT: - return "QDMI_DEVICE_PROPERTY_PULSESUPPORT"; - case QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS: - return "QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS"; - case QDMI_DEVICE_PROPERTY_MAX: - case QDMI_DEVICE_PROPERTY_CUSTOM1: - case QDMI_DEVICE_PROPERTY_CUSTOM2: - case QDMI_DEVICE_PROPERTY_CUSTOM3: - case QDMI_DEVICE_PROPERTY_CUSTOM4: - case QDMI_DEVICE_PROPERTY_CUSTOM5: - return "QDMI_DEVICE_PROPERTY_UNKNOWN"; - } - unreachable(); -} -auto throwError(int result, const std::string& msg) -> void { - std::ostringstream ss; - ss << msg << ": " << toString(static_cast(result)) << "."; - switch (result) { - case QDMI_ERROR_OUTOFMEM: - throw std::bad_alloc(); - case QDMI_ERROR_OUTOFRANGE: - throw std::out_of_range(ss.str()); - case QDMI_ERROR_INVALIDARGUMENT: - throw std::invalid_argument(ss.str()); - case QDMI_ERROR_FATAL: - case QDMI_ERROR_NOTIMPLEMENTED: - case QDMI_ERROR_LIBNOTFOUND: - case QDMI_ERROR_NOTFOUND: - case QDMI_ERROR_PERMISSIONDENIED: - case QDMI_ERROR_NOTSUPPORTED: - case QDMI_ERROR_BADSTATE: - case QDMI_ERROR_TIMEOUT: - throw std::runtime_error(ss.str()); - default: - throw std::runtime_error("Unknown error code: " + - toString(static_cast(result)) + "."); - } -} -auto FoMaC::Device::Site::getIndex() const -> size_t { +auto Session::Device::Site::getIndex() const -> size_t { return queryProperty(QDMI_SITE_PROPERTY_INDEX); } -auto FoMaC::Device::Site::getT1() const -> std::optional { +auto Session::Device::Site::getT1() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_T1); } -auto FoMaC::Device::Site::getT2() const -> std::optional { +auto Session::Device::Site::getT2() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_T2); } -auto FoMaC::Device::Site::getName() const -> std::optional { +auto Session::Device::Site::getName() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_NAME); } -auto FoMaC::Device::Site::getXCoordinate() const -> std::optional { +auto Session::Device::Site::getXCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_XCOORDINATE); } -auto FoMaC::Device::Site::getYCoordinate() const -> std::optional { +auto Session::Device::Site::getYCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_YCOORDINATE); } -auto FoMaC::Device::Site::getZCoordinate() const -> std::optional { +auto Session::Device::Site::getZCoordinate() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_ZCOORDINATE); } -auto FoMaC::Device::Site::isZone() const -> bool { +auto Session::Device::Site::isZone() const -> bool { return queryProperty>(QDMI_SITE_PROPERTY_ISZONE) .value_or(false); } -auto FoMaC::Device::Site::getXExtent() const -> std::optional { +auto Session::Device::Site::getXExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_XEXTENT); } -auto FoMaC::Device::Site::getYExtent() const -> std::optional { +auto Session::Device::Site::getYExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_YEXTENT); } -auto FoMaC::Device::Site::getZExtent() const -> std::optional { +auto Session::Device::Site::getZExtent() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_ZEXTENT); } -auto FoMaC::Device::Site::getModuleIndex() const -> std::optional { +auto Session::Device::Site::getModuleIndex() const -> std::optional { return queryProperty>(QDMI_SITE_PROPERTY_MODULEINDEX); } -auto FoMaC::Device::Site::getSubmoduleIndex() const -> std::optional { +auto Session::Device::Site::getSubmoduleIndex() const + -> std::optional { return queryProperty>( QDMI_SITE_PROPERTY_SUBMODULEINDEX); } -auto FoMaC::Device::Operation::getName(const std::vector& sites, - const std::vector& params) const +auto Session::Device::Operation::getName( + const std::vector& sites, const std::vector& params) const -> std::string { return queryProperty(QDMI_OPERATION_PROPERTY_NAME, sites, params); } -auto FoMaC::Device::Operation::getQubitsNum( +auto Session::Device::Operation::getQubitsNum( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>(QDMI_OPERATION_PROPERTY_QUBITSNUM, sites, params); } -auto FoMaC::Device::Operation::getParametersNum( +auto Session::Device::Operation::getParametersNum( const std::vector& sites, const std::vector& params) const -> size_t { return queryProperty(QDMI_OPERATION_PROPERTY_PARAMETERSNUM, sites, params); } -auto FoMaC::Device::Operation::getDuration( +auto Session::Device::Operation::getDuration( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_DURATION, sites, params); } -auto FoMaC::Device::Operation::getFidelity( +auto Session::Device::Operation::getFidelity( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>(QDMI_OPERATION_PROPERTY_FIDELITY, sites, params); } -auto FoMaC::Device::Operation::getInteractionRadius( +auto Session::Device::Operation::getInteractionRadius( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS, sites, params); } -auto FoMaC::Device::Operation::getBlockingRadius( +auto Session::Device::Operation::getBlockingRadius( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS, sites, params); } -auto FoMaC::Device::Operation::getIdlingFidelity( +auto Session::Device::Operation::getIdlingFidelity( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_IDLINGFIDELITY, sites, params); } -auto FoMaC::Device::Operation::isZoned() const -> bool { +auto Session::Device::Operation::isZoned() const -> bool { return queryProperty>(QDMI_OPERATION_PROPERTY_ISZONED, {}, {}) .value_or(false); } -auto FoMaC::Device::Operation::getSites() const +auto Session::Device::Operation::getSites() const -> std::optional> { const auto& qdmiSites = queryProperty>>( QDMI_OPERATION_PROPERTY_SITES, {}, {}); @@ -322,7 +140,7 @@ auto FoMaC::Device::Operation::getSites() const }); return returnedSites; } -auto FoMaC::Device::Operation::getSitePairs() const +auto Session::Device::Operation::getSitePairs() const -> std::optional>> { if (const auto qubitsNum = getQubitsNum({}, {}); !qubitsNum.has_value() || *qubitsNum != 2 || isZoned()) { @@ -348,28 +166,28 @@ auto FoMaC::Device::Operation::getSitePairs() const return pairs; } -auto FoMaC::Device::Operation::getMeanShuttlingSpeed( +auto Session::Device::Operation::getMeanShuttlingSpeed( const std::vector& sites, const std::vector& params) const -> std::optional { return queryProperty>( QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED, sites, params); } -auto FoMaC::Device::getName() const -> std::string { +auto Session::Device::getName() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_NAME); } -auto FoMaC::Device::getVersion() const -> std::string { +auto Session::Device::getVersion() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_VERSION); } -auto FoMaC::Device::getStatus() const -> QDMI_Device_Status { +auto Session::Device::getStatus() const -> QDMI_Device_Status { return queryProperty(QDMI_DEVICE_PROPERTY_STATUS); } -auto FoMaC::Device::getLibraryVersion() const -> std::string { +auto Session::Device::getLibraryVersion() const -> std::string { return queryProperty(QDMI_DEVICE_PROPERTY_LIBRARYVERSION); } -auto FoMaC::Device::getQubitsNum() const -> size_t { +auto Session::Device::getQubitsNum() const -> size_t { return queryProperty(QDMI_DEVICE_PROPERTY_QUBITSNUM); } -auto FoMaC::Device::getSites() const -> std::vector { +auto Session::Device::getSites() const -> std::vector { const auto& qdmiSites = queryProperty>(QDMI_DEVICE_PROPERTY_SITES); std::vector sites; @@ -380,14 +198,14 @@ auto FoMaC::Device::getSites() const -> std::vector { }); return sites; } -auto FoMaC::Device::getRegularSites() const -> std::vector { +auto Session::Device::getRegularSites() const -> std::vector { auto allSites = getSites(); const auto newEnd = std::ranges::remove_if( allSites, [](const Site& s) { return s.isZone(); }); allSites.erase(newEnd.begin(), newEnd.end()); return allSites; } -auto FoMaC::Device::getZones() const -> std::vector { +auto Session::Device::getZones() const -> std::vector { const auto& allSites = getSites(); std::vector zones; zones.reserve(3); // Reserve space for a typical max number of zones @@ -395,7 +213,7 @@ auto FoMaC::Device::getZones() const -> std::vector { [](const Site& s) { return s.isZone(); }); return zones; } -auto FoMaC::Device::getOperations() const -> std::vector { +auto Session::Device::getOperations() const -> std::vector { const auto& qdmiOperations = queryProperty>( QDMI_DEVICE_PROPERTY_OPERATIONS); std::vector operations; @@ -407,7 +225,7 @@ auto FoMaC::Device::getOperations() const -> std::vector { }); return operations; } -auto FoMaC::Device::getCouplingMap() const +auto Session::Device::getCouplingMap() const -> std::optional>> { const auto& qdmiCouplingMap = queryProperty< std::optional>>>( @@ -425,72 +243,74 @@ auto FoMaC::Device::getCouplingMap() const }); return couplingMap; } -auto FoMaC::Device::getNeedsCalibration() const -> std::optional { +auto Session::Device::getNeedsCalibration() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION); } -auto FoMaC::Device::getLengthUnit() const -> std::optional { +auto Session::Device::getLengthUnit() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHUNIT); } -auto FoMaC::Device::getLengthScaleFactor() const -> std::optional { +auto Session::Device::getLengthScaleFactor() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR); } -auto FoMaC::Device::getDurationUnit() const -> std::optional { +auto Session::Device::getDurationUnit() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONUNIT); } -auto FoMaC::Device::getDurationScaleFactor() const -> std::optional { +auto Session::Device::getDurationScaleFactor() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR); } -auto FoMaC::Device::getMinAtomDistance() const -> std::optional { +auto Session::Device::getMinAtomDistance() const -> std::optional { return queryProperty>( QDMI_DEVICE_PROPERTY_MINATOMDISTANCE); } -auto FoMaC::Device::getSupportedProgramFormats() const +auto Session::Device::getSupportedProgramFormats() const -> std::vector { return queryProperty>( QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS); } -auto FoMaC::Device::submitJob(const std::string& program, - const QDMI_Program_Format format, - const size_t numShots) const -> Job { +auto Session::Device::submitJob(const std::string& program, + const QDMI_Program_Format format, + const size_t numShots) const -> Job { QDMI_Job job = nullptr; - throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); + qdmi::throwIfError(QDMI_device_create_job(device_, &job), "Creating job"); Job jobWrapper{job}; // RAII wrapper to prevent leaks in case of exceptions // Set program format - throwIfError(QDMI_job_set_parameter(jobWrapper, - QDMI_JOB_PARAMETER_PROGRAMFORMAT, - sizeof(format), &format), - "Setting program format"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_PROGRAMFORMAT, + sizeof(format), &format), + "Setting program format"); // Set program - throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, - program.size() + 1, program.c_str()), - "Setting program"); + qdmi::throwIfError( + QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_PROGRAM, + program.size() + 1, program.c_str()), + "Setting program"); // Set number of shots - throwIfError(QDMI_job_set_parameter(jobWrapper, QDMI_JOB_PARAMETER_SHOTSNUM, - sizeof(numShots), &numShots), - "Setting number of shots"); + qdmi::throwIfError(QDMI_job_set_parameter(jobWrapper, + QDMI_JOB_PARAMETER_SHOTSNUM, + sizeof(numShots), &numShots), + "Setting number of shots"); // Submit the job - throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); + qdmi::throwIfError(QDMI_job_submit(jobWrapper), "Submitting job"); return jobWrapper; } -auto FoMaC::Job::check() const -> QDMI_Job_Status { +auto Session::Job::check() const -> QDMI_Job_Status { QDMI_Job_Status status{}; - throwIfError(QDMI_job_check(job_, &status), "Checking job status"); + qdmi::throwIfError(QDMI_job_check(job_, &status), "Checking job status"); return status; } -auto FoMaC::Job::wait(const size_t timeout) const -> bool { +auto Session::Job::wait(const size_t timeout) const -> bool { const auto ret = QDMI_job_wait(job_, timeout); if (ret == QDMI_SUCCESS) { return true; @@ -498,58 +318,60 @@ auto FoMaC::Job::wait(const size_t timeout) const -> bool { if (ret == QDMI_ERROR_TIMEOUT) { return false; } - throwIfError(ret, "Waiting for job"); - unreachable(); + qdmi::throwIfError(ret, "Waiting for job"); + qdmi::unreachable(); } -auto FoMaC::Job::cancel() const -> void { - throwIfError(QDMI_job_cancel(job_), "Cancelling job"); +auto Session::Job::cancel() const -> void { + qdmi::throwIfError(QDMI_job_cancel(job_), "Cancelling job"); } -auto FoMaC::Job::getId() const -> std::string { +auto Session::Job::getId() const -> std::string { size_t size = 0; - throwIfError( + qdmi::throwIfError( QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, 0, nullptr, &size), "Querying job ID size"); std::string id(size - 1, '\0'); - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, size, - id.data(), nullptr), - "Querying job ID"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_ID, size, + id.data(), nullptr), + "Querying job ID"); return id; } -auto FoMaC::Job::getProgramFormat() const -> QDMI_Program_Format { +auto Session::Job::getProgramFormat() const -> QDMI_Program_Format { QDMI_Program_Format format{}; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAMFORMAT, - sizeof(format), &format, nullptr), - "Querying program format"); + qdmi::throwIfError(QDMI_job_query_property(job_, + QDMI_JOB_PROPERTY_PROGRAMFORMAT, + sizeof(format), &format, nullptr), + "Querying program format"); return format; } -auto FoMaC::Job::getProgram() const -> std::string { +auto Session::Job::getProgram() const -> std::string { size_t size = 0; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, - nullptr, &size), - "Querying program size"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, 0, + nullptr, &size), + "Querying program size"); std::string program(size - 1, '\0'); - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, size, - program.data(), nullptr), - "Querying program"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_PROGRAM, + size, program.data(), nullptr), + "Querying program"); return program; } -auto FoMaC::Job::getNumShots() const -> size_t { +auto Session::Job::getNumShots() const -> size_t { size_t numShots = 0; - throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, - sizeof(numShots), &numShots, nullptr), - "Querying number of shots"); + qdmi::throwIfError(QDMI_job_query_property(job_, QDMI_JOB_PROPERTY_SHOTSNUM, + sizeof(numShots), &numShots, + nullptr), + "Querying number of shots"); return numShots; } -auto FoMaC::Job::getShots() const -> std::vector { +auto Session::Job::getShots() const -> std::vector { size_t shotsSize = 0; - throwIfError( + qdmi::throwIfError( QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, 0, nullptr, &shotsSize), "Querying shots size"); @@ -558,9 +380,9 @@ auto FoMaC::Job::getShots() const -> std::vector { } std::string shots(shotsSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, shotsSize, - shots.data(), nullptr), - "Querying shots"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_SHOTS, + shotsSize, shots.data(), nullptr), + "Querying shots"); // Parse the shots (comma-separated) std::vector shotsVec; @@ -578,27 +400,27 @@ auto FoMaC::Job::getShots() const -> std::vector { return shotsVec; } -auto FoMaC::Job::getCounts() const -> std::map { +auto Session::Job::getCounts() const -> std::map { // Get the histogram keys size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, nullptr, - &keysSize), - "Querying histogram keys size"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, 0, + nullptr, &keysSize), + "Querying histogram keys size"); if (keysSize == 0) { return {}; // Empty histogram } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, keysSize, - keys.data(), nullptr), - "Querying histogram keys"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_KEYS, + keysSize, keys.data(), nullptr), + "Querying histogram keys"); // Get the histogram values size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, 0, - nullptr, &valuesSize), - "Querying histogram values size"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, 0, + nullptr, &valuesSize), + "Querying histogram values size"); if (valuesSize % sizeof(size_t) != 0) { throw std::runtime_error( @@ -606,9 +428,9 @@ auto FoMaC::Job::getCounts() const -> std::map { } std::vector values(valuesSize / sizeof(size_t)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, - valuesSize, values.data(), nullptr), - "Querying histogram values"); + qdmi::throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_HIST_VALUES, + valuesSize, values.data(), nullptr), + "Querying histogram values"); // Parse the keys (comma-separated) std::map counts; @@ -629,12 +451,13 @@ auto FoMaC::Job::getCounts() const -> std::map { return counts; } -auto FoMaC::Job::getDenseStateVector() const +auto Session::Job::getDenseStateVector() const -> std::vector> { size_t size = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, - nullptr, &size), - "Querying dense state vector size"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_STATEVECTOR_DENSE, 0, + nullptr, &size), + "Querying dense state vector size"); if (size % sizeof(std::complex) != 0) { throw std::runtime_error( @@ -643,17 +466,19 @@ auto FoMaC::Job::getDenseStateVector() const std::vector> stateVector(size / sizeof(std::complex)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_DENSE, - size, stateVector.data(), nullptr), - "Querying dense state vector"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_STATEVECTOR_DENSE, + size, stateVector.data(), nullptr), + "Querying dense state vector"); return stateVector; } -auto FoMaC::Job::getDenseProbabilities() const -> std::vector { +auto Session::Job::getDenseProbabilities() const -> std::vector { size_t size = 0; - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_DENSE, - 0, nullptr, &size), - "Querying dense probabilities size"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_PROBABILITIES_DENSE, + 0, nullptr, &size), + "Querying dense probabilities size"); if (size % sizeof(double) != 0) { throw std::runtime_error( @@ -661,35 +486,36 @@ auto FoMaC::Job::getDenseProbabilities() const -> std::vector { } std::vector probabilities(size / sizeof(double)); - throwIfError(QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_DENSE, - size, probabilities.data(), nullptr), - "Querying dense probabilities"); + qdmi::throwIfError(QDMI_job_get_results(job_, + QDMI_JOB_RESULT_PROBABILITIES_DENSE, + size, probabilities.data(), nullptr), + "Querying dense probabilities"); return probabilities; } -auto FoMaC::Job::getSparseStateVector() const +auto Session::Job::getSparseStateVector() const -> std::map> { size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, 0, - nullptr, &keysSize), - "Querying sparse state vector keys size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, 0, + nullptr, &keysSize), + "Querying sparse state vector keys size"); if (keysSize == 0) { return {}; // Empty state vector } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, - keysSize, keys.data(), nullptr), - "Querying sparse state vector keys"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_KEYS, + keysSize, keys.data(), nullptr), + "Querying sparse state vector keys"); size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, - 0, nullptr, &valuesSize), - "Querying sparse state vector values size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, 0, + nullptr, &valuesSize), + "Querying sparse state vector values size"); if (valuesSize % sizeof(std::complex) != 0) { throw std::runtime_error( @@ -699,10 +525,10 @@ auto FoMaC::Job::getSparseStateVector() const std::vector> values(valuesSize / sizeof(std::complex)); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, - valuesSize, values.data(), nullptr), - "Querying sparse state vector values"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_STATEVECTOR_SPARSE_VALUES, + valuesSize, values.data(), nullptr), + "Querying sparse state vector values"); // Parse the keys (comma-separated) std::map> stateVector; @@ -723,29 +549,29 @@ auto FoMaC::Job::getSparseStateVector() const return stateVector; } -auto FoMaC::Job::getSparseProbabilities() const +auto Session::Job::getSparseProbabilities() const -> std::map { size_t keysSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, - 0, nullptr, &keysSize), - "Querying sparse probabilities keys size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, 0, + nullptr, &keysSize), + "Querying sparse probabilities keys size"); if (keysSize == 0) { return {}; // Empty probabilities } std::string keys(keysSize - 1, '\0'); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, - keysSize, keys.data(), nullptr), - "Querying sparse probabilities keys"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_KEYS, + keysSize, keys.data(), nullptr), + "Querying sparse probabilities keys"); size_t valuesSize = 0; - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, - 0, nullptr, &valuesSize), - "Querying sparse probabilities values size"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, 0, + nullptr, &valuesSize), + "Querying sparse probabilities values size"); if (valuesSize % sizeof(double) != 0) { throw std::runtime_error( @@ -753,10 +579,10 @@ auto FoMaC::Job::getSparseProbabilities() const } std::vector values(valuesSize / sizeof(double)); - throwIfError(QDMI_job_get_results(job_, - QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, - valuesSize, values.data(), nullptr), - "Querying sparse probabilities values"); + qdmi::throwIfError( + QDMI_job_get_results(job_, QDMI_JOB_RESULT_PROBABILITIES_SPARSE_VALUES, + valuesSize, values.data(), nullptr), + "Querying sparse probabilities values"); // Parse the keys (comma-separated) std::map probabilities; @@ -776,14 +602,125 @@ auto FoMaC::Job::getSparseProbabilities() const return probabilities; } -FoMaC::FoMaC() { - QDMI_session_alloc(&session_); - QDMI_session_init(session_); +Session::Session(const SessionConfig& config) { + const auto result = QDMI_session_alloc(&session_); + qdmi::throwIfError(result, "Allocating QDMI session"); + + // Helper to ensure session is freed if an exception is thrown during setup + const auto cleanup = [this]() -> void { + if (session_ != nullptr) { + QDMI_session_free(session_); + session_ = nullptr; + } + }; + // Helper to set session parameters + const auto setParameter = [this](const std::optional& value, + QDMI_Session_Parameter param) -> void { + if (value) { + const auto status = static_cast(QDMI_session_set_parameter( + session_, param, value->size() + 1, value->c_str())); + if (status == QDMI_ERROR_NOTSUPPORTED) { + // Optional parameter not supported by session - skip it + SPDLOG_INFO("Session parameter {} not supported (skipped)", + qdmi::toString(param)); + return; + } + if (status == QDMI_SUCCESS) { + return; + } + std::ostringstream ss; + ss << "Setting session parameter " << qdmi::toString(param) << ": " + << qdmi::toString(status) << " (status = " << status << ")"; + qdmi::throwIfError(status, ss.str()); + } + }; + + try { + // Validate file existence for authFile + if (config.authFile) { + if (!std::filesystem::exists(*config.authFile)) { + throw std::runtime_error("Authentication file does not exist: " + + *config.authFile); + } + } + // Validate URL format for authUrl + if (config.authUrl) { + // Breakdown of the regex pattern: + // 1. ^https?:// -> Start with http:// or https:// + // 2. (?: -> Start Host Group + // \[[a-fA-F0-9:]+\] -> Branch A: IPv6 (Must be in brackets like + // [::1]) + // -> Note: No \b used here because ']' is a + // non-word char + // | -> OR + // (?: -> Branch B: Alphanumeric Hosts (Group for + // \b check) + // (?:\d{1,3}\.){3}\d{1,3} -> IPv4 (e.g., 127.0.0.1) + // | -> OR + // localhost -> Localhost + // | -> OR + // (?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6} -> + // Domain + // )\b -> End Branch B + Word Boundary (Prevents + // "localhostX") + // ) -> End Host Group + // 3. (?::\d+)? -> Optional Port (e.g., :8080) + // 4. (?:...)*$ -> Optional Path/Query params + End of + // string + static const std::regex URL_PATTERN( + R"(^https?://(?:\[[a-fA-F0-9:]+\]|(?:(?:\d{1,3}\.){3}\d{1,3}|localhost|(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6})\b)(?::\d+)?(?:[-a-zA-Z0-9()@:%_\+.~#?&/=]*)$)", + std::regex::optimize); + if (!std::regex_match(*config.authUrl, URL_PATTERN)) { + throw std::runtime_error("Invalid URL format: " + *config.authUrl); + } + } + + // Set session parameters + setParameter(config.token, QDMI_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_SESSION_PARAMETER_PASSWORD); + setParameter(config.projectId, QDMI_SESSION_PARAMETER_PROJECTID); + setParameter(config.custom1, QDMI_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_SESSION_PARAMETER_CUSTOM5); + + // Initialize the session + qdmi::throwIfError(QDMI_session_init(session_), "Initializing session"); + } catch (...) { + cleanup(); + throw; + } +} + +Session::~Session() { + if (session_ != nullptr) { + QDMI_session_free(session_); + } +} + +Session::Session(Session&& other) noexcept : session_(other.session_) { + other.session_ = nullptr; } -FoMaC::~FoMaC() { QDMI_session_free(session_); } -auto FoMaC::getDevices() -> std::vector { - const auto& qdmiDevices = get().queryProperty>( - QDMI_SESSION_PROPERTY_DEVICES); + +Session& Session::operator=(Session&& other) noexcept { + if (this != &other) { + if (session_ != nullptr) { + QDMI_session_free(session_); + } + session_ = other.session_; + other.session_ = nullptr; + } + return *this; +} + +auto Session::getDevices() -> std::vector { + + const auto& qdmiDevices = + queryProperty>(QDMI_SESSION_PROPERTY_DEVICES); std::vector devices; devices.reserve(qdmiDevices.size()); std::ranges::transform( diff --git a/src/na/CMakeLists.txt b/src/na/CMakeLists.txt index 024e4eb492..b75056ee2b 100644 --- a/src/na/CMakeLists.txt +++ b/src/na/CMakeLists.txt @@ -6,8 +6,6 @@ # # Licensed under the MIT License -# add subdirectories -add_subdirectory(device) add_subdirectory(fomac) if(NOT TARGET ${MQT_CORE_TARGET_NAME}-na) diff --git a/src/na/fomac/Device.cpp b/src/na/fomac/Device.cpp index dd528f1330..a0d025afc0 100644 --- a/src/na/fomac/Device.cpp +++ b/src/na/fomac/Device.cpp @@ -12,7 +12,7 @@ #include "fomac/FoMaC.hpp" #include "ir/Definitions.hpp" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include @@ -38,12 +38,12 @@ namespace na { namespace { /** - * @brief Calculate the rectangular extent covering all given FoMaC sites. - * @param sites is a vector of FoMaC sites + * @brief Calculate the rectangular extent covering all given Session sites. + * @param sites is a vector of Session sites * @return the extent covering all given sites */ auto calculateExtentFromSites( - const std::vector& sites) -> Device::Region { + const std::vector& sites) -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); auto minY = std::numeric_limits::max(); @@ -61,13 +61,14 @@ auto calculateExtentFromSites( .height = static_cast(maxY - minY)}}; } /** - * @brief Calculate the rectangular extent covering all given FoMaC site pairs. - * @param sitePairs is a vector of FoMaC site pairs + * @brief Calculate the rectangular extent covering all given Session site + * pairs. + * @param sitePairs is a vector of Session site pairs * @return the extent covering all sites in the pairs */ auto calculateExtentFromSites( - const std::vector>& sitePairs) + const std::vector>& sitePairs) -> Device::Region { auto minX = std::numeric_limits::max(); auto maxX = std::numeric_limits::min(); @@ -109,8 +110,8 @@ class MinHeap } }; } // namespace -auto FoMaC::Device::initNameFromDevice() -> void { name = getName(); } -auto FoMaC::Device::initMinAtomDistanceFromDevice() -> bool { +auto Session::Device::initNameFromDevice() -> void { name = getName(); } +auto Session::Device::initMinAtomDistanceFromDevice() -> bool { const auto& d = getMinAtomDistance(); if (!d.has_value()) { SPDLOG_INFO("Minimal atom distance not set"); @@ -119,11 +120,11 @@ auto FoMaC::Device::initMinAtomDistanceFromDevice() -> bool { minAtomDistance = *d; return true; } -auto FoMaC::Device::initQubitsNumFromDevice() -> void { +auto Session::Device::initQubitsNumFromDevice() -> void { numQubits = getQubitsNum(); } -auto FoMaC::Device::initLengthUnitFromDevice() -> bool { - const auto& u = fomac::FoMaC::Device::getLengthUnit(); +auto Session::Device::initLengthUnitFromDevice() -> bool { + const auto& u = fomac::Session::Device::getLengthUnit(); if (!u.has_value()) { SPDLOG_INFO("Length unit not set"); return false; @@ -132,8 +133,8 @@ auto FoMaC::Device::initLengthUnitFromDevice() -> bool { lengthUnit.scaleFactor = getLengthScaleFactor().value_or(1.0); return true; } -auto FoMaC::Device::initDurationUnitFromDevice() -> bool { - const auto& u = fomac::FoMaC::Device::getDurationUnit(); +auto Session::Device::initDurationUnitFromDevice() -> bool { + const auto& u = fomac::Session::Device::getDurationUnit(); if (!u.has_value()) { SPDLOG_INFO("Duration unit not set"); return false; @@ -142,7 +143,7 @@ auto FoMaC::Device::initDurationUnitFromDevice() -> bool { durationUnit.scaleFactor = getDurationScaleFactor().value_or(1.0); return true; } -auto FoMaC::Device::initDecoherenceTimesFromDevice() -> bool { +auto Session::Device::initDecoherenceTimesFromDevice() -> bool { const auto regularSites = getRegularSites(); if (regularSites.empty()) { SPDLOG_INFO("Device has no regular sites with decoherence data"); @@ -169,7 +170,7 @@ auto FoMaC::Device::initDecoherenceTimesFromDevice() -> bool { decoherenceTimes.t2 = sumT2 / count; return true; } -auto FoMaC::Device::initTrapsfromDevice() -> bool { +auto Session::Device::initTrapsfromDevice() -> bool { traps.clear(); const auto regularSites = getRegularSites(); if (regularSites.empty()) { @@ -293,13 +294,13 @@ auto FoMaC::Device::initTrapsfromDevice() -> bool { } return true; } -auto FoMaC::Device::initOperationsFromDevice() -> bool { +auto Session::Device::initOperationsFromDevice() -> bool { std::map>> shuttlingUnitsPerId; - for (const fomac::FoMaC::Device::Operation& op : getOperations()) { + for (const fomac::Session::Device::Operation& op : getOperations()) { const auto zoned = op.isZoned(); const auto& nq = op.getQubitsNum(); - const auto& name = op.getName(); + const auto& opName = op.getName(); const auto& sitesOpt = op.getSites(); if (!sitesOpt.has_value() || sitesOpt->empty()) { SPDLOG_INFO("Operation missing sites"); @@ -307,7 +308,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } if (zoned) { if (std::ranges::any_of( - *sitesOpt, [](const fomac::FoMaC::Device::Site& site) -> bool { + *sitesOpt, [](const fomac::Session::Device::Site& site) -> bool { return !site.isZone(); })) { SPDLOG_INFO("Operation marked as zoned but has non-zone sites"); @@ -343,7 +344,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { if (!nq.has_value()) { // shuttling operations std::smatch match; - if (std::regex_match(name, match, std::regex("load<(\\d+)>"))) { + if (std::regex_match(opName, match, std::regex("load<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& d = op.getDuration(); if (!d.has_value()) { @@ -380,7 +381,8 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } unit.loadDuration = *d; unit.loadFidelity = *f; - } else if (std::regex_match(name, match, std::regex("move<(\\d+)>"))) { + } else if (std::regex_match(opName, match, + std::regex("move<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& speed = op.getMeanShuttlingSpeed(); if (!speed.has_value()) { @@ -411,7 +413,8 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } } unit.meanShuttlingSpeed = *speed; - } else if (std::regex_match(name, match, std::regex("store<(\\d+)>"))) { + } else if (std::regex_match(opName, match, + std::regex("store<(\\d+)>"))) { const auto id = std::stoul(match[1]); const auto& d = op.getDuration(); if (!d.has_value()) { @@ -466,7 +469,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { if (*nq == 1) { // zoned single-qubit operations globalSingleQubitOperations.emplace_back(GlobalSingleQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -489,7 +492,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { return false; } globalMultiQubitOperations.emplace_back(GlobalMultiQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -520,7 +523,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { const auto region = calculateExtentFromSites(*sitesOpt); if (*nq == 1) { localSingleQubitOperations.emplace_back(LocalSingleQubitOperation{ - {.name = name, + {.name = opName, .region = region, .duration = *d, .fidelity = *f, @@ -545,7 +548,7 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { return false; } localMultiQubitOperations.emplace_back( - LocalMultiQubitOperation{{.name = name, + LocalMultiQubitOperation{{.name = opName, .region = pairRegion, .duration = *d, .fidelity = *f, @@ -573,11 +576,12 @@ auto FoMaC::Device::initOperationsFromDevice() -> bool { } return true; } -FoMaC::Device::Device(const fomac::FoMaC::Device& device) - : fomac::FoMaC::Device(device) {} -auto FoMaC::getDevices() -> std::vector { +Session::Device::Device(const fomac::Session::Device& device) + : fomac::Session::Device(device) {} +auto Session::getDevices() -> std::vector { std::vector devices; - for (const auto& d : fomac::FoMaC::getDevices()) { + fomac::Session session; + for (const auto& d : session.getDevices()) { if (auto r = Device::tryCreateFromDevice(d); r.has_value()) { devices.emplace_back(r.value()); } diff --git a/src/qdmi/CMakeLists.txt b/src/qdmi/CMakeLists.txt index 2a74069adf..acd14d0bbf 100644 --- a/src/qdmi/CMakeLists.txt +++ b/src/qdmi/CMakeLists.txt @@ -6,8 +6,32 @@ # # Licensed under the MIT License +set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-common) + +if(NOT TARGET ${TARGET_NAME}) + # Add common library + add_mqt_core_library(${TARGET_NAME} ALIAS_NAME QDMICommon) + + # Add sources to target + target_sources(${TARGET_NAME} PRIVATE Common.cpp) + + # Add headers using file sets + target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} + FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/Common.hpp) + + # Add link libraries + target_link_libraries( + ${TARGET_NAME} + PUBLIC qdmi::qdmi + PRIVATE qdmi::project_warnings) + + # add to list of MQT core targets + list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) +endif() + add_subdirectory(dd) add_subdirectory(sc) +add_subdirectory(na) set(TARGET_NAME ${MQT_CORE_TARGET_NAME}-qdmi-driver) @@ -25,12 +49,14 @@ if(NOT TARGET ${TARGET_NAME}) # Add link libraries target_link_libraries( ${TARGET_NAME} - PUBLIC qdmi::qdmi + PUBLIC qdmi::qdmi MQT::CoreQDMICommon PRIVATE MQT::CoreQDMINaDevice MQT::CoreQDMIScDevice MQT::CoreQDMI_DDSIM_Device - qdmi::project_warnings) + qdmi::project_warnings spdlog::spdlog) # add to list of MQT core targets - set(MQT_CORE_TARGETS - ${MQT_CORE_TARGETS} ${TARGET_NAME} - PARENT_SCOPE) + list(APPEND MQT_CORE_TARGETS ${TARGET_NAME}) endif() + +set(MQT_CORE_TARGETS + ${MQT_CORE_TARGETS} + PARENT_SCOPE) diff --git a/src/qdmi/Common.cpp b/src/qdmi/Common.cpp new file mode 100644 index 0000000000..0a8f6b9484 --- /dev/null +++ b/src/qdmi/Common.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023 - 2025 Chair for Design Automation, TUM + * Copyright (c) 2025 Munich Quantum Software Company GmbH + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + * Licensed under the MIT License + */ + +/// @file Common.cpp +/// @brief Common definitions and utilities for working with QDMI in C++. +/// @note This file will be upstreamed to the QDMI core library in the future. + +#include "qdmi/Common.hpp" + +#include +#include +#include +#include +#include + +namespace qdmi { + +auto throwIfError(const int result, const std::string& msg) -> void { + switch (const auto res = static_cast(result)) { + case QDMI_SUCCESS: + break; + case QDMI_WARN_GENERAL: + std::cerr << "Warning: " << msg << '\n'; + break; + default: + std::ostringstream ss; + ss << msg << ": " << toString(res) << "."; + switch (res) { + case QDMI_ERROR_OUTOFMEM: + throw std::bad_alloc(); + case QDMI_ERROR_OUTOFRANGE: + throw std::out_of_range(ss.str()); + case QDMI_ERROR_INVALIDARGUMENT: + throw std::invalid_argument(ss.str()); + case QDMI_ERROR_FATAL: + case QDMI_ERROR_NOTIMPLEMENTED: + case QDMI_ERROR_LIBNOTFOUND: + case QDMI_ERROR_NOTFOUND: + case QDMI_ERROR_PERMISSIONDENIED: + case QDMI_ERROR_NOTSUPPORTED: + case QDMI_ERROR_BADSTATE: + case QDMI_ERROR_TIMEOUT: + throw std::runtime_error(ss.str()); + default: + throw std::runtime_error("Unknown QDMI error code. " + ss.str()); + } + } +} + +} // namespace qdmi diff --git a/src/qdmi/Driver.cpp b/src/qdmi/Driver.cpp index 3438a48f17..c0eadd10c0 100644 --- a/src/qdmi/Driver.cpp +++ b/src/qdmi/Driver.cpp @@ -13,14 +13,18 @@ #include "mqt_ddsim_qdmi/device.h" #include "mqt_na_qdmi/device.h" #include "mqt_sc_qdmi/device.h" +#include "qdmi/Common.hpp" #include #include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -86,12 +90,6 @@ DynamicDeviceLibrary::DynamicDeviceLibrary(const std::string& libName, if (libHandle_ == nullptr) { throw std::runtime_error("Couldn't open the device library: " + libName); } - if (!openLibHandles().emplace(libHandle_).second) { - // dlopen uses reference counting, so we need to decrement the reference - // count that was increased by dlopen. - dlclose(libHandle_); - throw std::runtime_error("Device library already loaded: " + libName); - } //===----------------------------------------------------------------------===// // Macro for loading a symbol from the dynamic library. @@ -154,11 +152,50 @@ DynamicDeviceLibrary::~DynamicDeviceLibrary() { } // namespace qdmi QDMI_Device_impl_d::QDMI_Device_impl_d( - std::unique_ptr&& lib) + std::unique_ptr&& lib, + const qdmi::DeviceSessionConfig& config) : library_(std::move(lib)) { if (library_->device_session_alloc(&deviceSession_) != QDMI_SUCCESS) { throw std::runtime_error("Failed to allocate device session"); } + + // Set device session parameters from config + auto setParameter = [this](const std::optional& value, + QDMI_Device_Session_Parameter param) { + if (value && library_->device_session_set_parameter) { + const auto status = + static_cast(library_->device_session_set_parameter( + deviceSession_, param, value->size() + 1, value->c_str())); + if (status == QDMI_SUCCESS) { + return; + } + + if (status == QDMI_ERROR_NOTSUPPORTED) { + SPDLOG_INFO( + "Device session parameter {} not supported by device (skipped)", + qdmi::toString(param)); + return; + } + library_->device_session_free(deviceSession_); + std::ostringstream ss; + ss << "Failed to set device session parameter " << qdmi::toString(param) + << ": " << qdmi::toString(status); + throw std::runtime_error(ss.str()); + } + }; + + setParameter(config.baseUrl, QDMI_DEVICE_SESSION_PARAMETER_BASEURL); + setParameter(config.token, QDMI_DEVICE_SESSION_PARAMETER_TOKEN); + setParameter(config.authFile, QDMI_DEVICE_SESSION_PARAMETER_AUTHFILE); + setParameter(config.authUrl, QDMI_DEVICE_SESSION_PARAMETER_AUTHURL); + setParameter(config.username, QDMI_DEVICE_SESSION_PARAMETER_USERNAME); + setParameter(config.password, QDMI_DEVICE_SESSION_PARAMETER_PASSWORD); + setParameter(config.custom1, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1); + setParameter(config.custom2, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2); + setParameter(config.custom3, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3); + setParameter(config.custom4, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4); + setParameter(config.custom5, QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5); + if (library_->device_session_init(deviceSession_) != QDMI_SUCCESS) { library_->device_session_free(deviceSession_); throw std::runtime_error("Failed to initialize device session"); @@ -349,20 +386,11 @@ Driver::~Driver() { #ifndef _WIN32 auto Driver::addDynamicDeviceLibrary(const std::string& libName, - const std::string& prefix) -> bool { - try { - devices_.emplace_back(std::make_unique( - std::make_unique(libName, prefix))); - } catch (const std::runtime_error& e) { - if (std::string(e.what()).starts_with("Device library already loaded:")) { - // The library is already loaded, so we can ignore this error but return - // false. - return false; - } - // Re-throw other exception - throw; - } - return true; + const std::string& prefix, + const DeviceSessionConfig& config) + -> void { + devices_.emplace_back(std::make_unique( + std::make_unique(libName, prefix), config)); } #endif diff --git a/src/qdmi/dd/CMakeLists.txt b/src/qdmi/dd/CMakeLists.txt index 6b5c7065ac..849574eb80 100644 --- a/src/qdmi/dd/CMakeLists.txt +++ b/src/qdmi/dd/CMakeLists.txt @@ -41,8 +41,7 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreCircuitOptimizer MQT::ProjectOptions - MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreDD MQT::CoreQASM MQT::CoreCircuitOptimizer MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries diff --git a/src/qdmi/dd/Device.cpp b/src/qdmi/dd/Device.cpp index e5b93eb5d3..09a165d75e 100644 --- a/src/qdmi/dd/Device.cpp +++ b/src/qdmi/dd/Device.cpp @@ -22,6 +22,7 @@ #include "ir/QuantumComputation.hpp" #include "mqt_ddsim_qdmi/device.h" #include "qasm3/Importer.hpp" +#include "qdmi/Common.hpp" #include #include @@ -144,105 +145,17 @@ constexpr std::array SUPPORTED_PROGRAM_FORMATS = {QDMI_PROGRAM_FORMAT_QASM2, } // namespace -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, size); -#else -#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::dd { - -std::atomic Device::instance = nullptr; - Device::Device() : name_("MQT Core DDSIM QDMI Device"), qubitsNum_(std::numeric_limits<::dd::Qubit>::max()) {} - -void Device::initialize() { - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; - } -} - -void Device::finalize() { - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; -} - auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } - auto Device::sessionAlloc(MQT_DDSIM_QDMI_Device_Session* session) -> QDMI_STATUS { if (session == nullptr) { @@ -267,12 +180,7 @@ auto Device::sessionFree(MQT_DDSIM_QDMI_Device_Session session) -> void { auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_PROPERTY_MAX && - prop != QDMI_DEVICE_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -314,7 +222,7 @@ auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, } auto Device::generateUniqueID() -> int { - const std::scoped_lock lock(sessionsMutex_); + const std::scoped_lock lock(rngMutex_); return dis_(rng_); } auto Device::setStatus(const QDMI_Device_Status status) -> void { @@ -344,12 +252,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::setParameter( const QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_SESSION_PARAMETER_MAX && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_SESSION_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -393,11 +296,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::querySiteProperty( return QDMI_ERROR_BADSTATE; } if (site == nullptr || (value != nullptr && size == 0) || - (prop >= QDMI_SITE_PROPERTY_MAX && prop != QDMI_SITE_PROPERTY_CUSTOM1 && - prop != QDMI_SITE_PROPERTY_CUSTOM2 && - prop != QDMI_SITE_PROPERTY_CUSTOM3 && - prop != QDMI_SITE_PROPERTY_CUSTOM4 && - prop != QDMI_SITE_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto id = @@ -417,12 +316,7 @@ auto MQT_DDSIM_QDMI_Device_Session_impl_d::queryOperationProperty( if (operation == nullptr || (sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || (value != nullptr && size == 0) || - (prop >= QDMI_OPERATION_PROPERTY_MAX && - prop != QDMI_OPERATION_PROPERTY_CUSTOM1 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM2 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM3 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM4 && - prop != QDMI_OPERATION_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto& [name_, numSites_, numParams_, isVariadic] = @@ -453,12 +347,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (param >= QDMI_DEVICE_JOB_PARAMETER_MAX && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM1 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM2 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM3 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM4 && - param != QDMI_DEVICE_JOB_PARAMETER_CUSTOM5)) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_.load() != QDMI_JOB_STATUS_CREATED) { @@ -468,12 +357,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::setParameter( case QDMI_DEVICE_JOB_PARAMETER_PROGRAMFORMAT: if (value != nullptr) { const auto format = *static_cast(value); - if (format >= QDMI_PROGRAM_FORMAT_MAX && - format != QDMI_PROGRAM_FORMAT_CUSTOM1 && - format != QDMI_PROGRAM_FORMAT_CUSTOM2 && - format != QDMI_PROGRAM_FORMAT_CUSTOM3 && - format != QDMI_PROGRAM_FORMAT_CUSTOM4 && - format != QDMI_PROGRAM_FORMAT_CUSTOM5) { + if (IS_INVALID_ARGUMENT(format, QDMI_PROGRAM_FORMAT)) { return QDMI_ERROR_INVALIDARGUMENT; } if (format != QDMI_PROGRAM_FORMAT_QASM2 && @@ -502,12 +386,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::queryProperty( const QDMI_Device_Job_Property prop, const size_t size, void* value, size_t* sizeRet) const -> QDMI_STATUS { if ((value != nullptr && size == 0) || - (prop >= QDMI_DEVICE_JOB_PROPERTY_MAX && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM1 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM2 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM3 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM4 && - prop != QDMI_DEVICE_JOB_PROPERTY_CUSTOM5)) { + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } const auto str = std::to_string(id_); @@ -774,10 +653,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getResults(const QDMI_Job_Result result, size_t* sizeRet) -> QDMI_STATUS { if ((data != nullptr && size == 0) || - (result >= QDMI_JOB_RESULT_MAX && result != QDMI_JOB_RESULT_CUSTOM1 && - result != QDMI_JOB_RESULT_CUSTOM2 && result != QDMI_JOB_RESULT_CUSTOM3 && - result != QDMI_JOB_RESULT_CUSTOM4 && - result != QDMI_JOB_RESULT_CUSTOM5)) { + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_.load() != QDMI_JOB_STATUS_DONE) { @@ -816,14 +692,12 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::getResults(const QDMI_Job_Result result, // QDMI uses a different naming convention for its C interface functions // NOLINTBEGIN(readability-identifier-naming) int MQT_DDSIM_QDMI_device_initialize() { - qdmi::dd::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::dd::Device::get(); return QDMI_SUCCESS; } -int MQT_DDSIM_QDMI_device_finalize() { - qdmi::dd::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_DDSIM_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_DDSIM_QDMI_device_session_alloc( MQT_DDSIM_QDMI_Device_Session* session) { diff --git a/src/na/device/App.cpp b/src/qdmi/na/App.cpp similarity index 99% rename from src/na/device/App.cpp rename to src/qdmi/na/App.cpp index 9901218b2a..0d31c80f09 100644 --- a/src/na/device/App.cpp +++ b/src/qdmi/na/App.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/src/na/device/CMakeLists.txt b/src/qdmi/na/CMakeLists.txt similarity index 88% rename from src/na/device/CMakeLists.txt rename to src/qdmi/na/CMakeLists.txt index f6dcda1759..e59de2206c 100644 --- a/src/na/device/CMakeLists.txt +++ b/src/qdmi/na/CMakeLists.txt @@ -24,7 +24,7 @@ if(NOT TARGET ${TARGET_NAME}) # add headers using file sets target_sources(${TARGET_NAME} PUBLIC FILE_SET HEADERS BASE_DIRS ${MQT_CORE_INCLUDE_BUILD_DIR} - FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Generator.hpp) + FILES ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Generator.hpp) # Link nlohmann_json, spdlog target_link_libraries( @@ -91,10 +91,10 @@ if(NOT TARGET ${TARGET_NAME}) # Set paths set(JSON_FILE ${PROJECT_SOURCE_DIR}/json/na/device.json) - set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/na/device/DeviceMemberInitializers.hpp) + set(DEVICE_HDR ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/na/DeviceMemberInitializers.hpp) # Create include directory - file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/na/device) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/qdmi/na) # Generate definitions for device add_custom_command( @@ -125,29 +125,19 @@ if(NOT TARGET ${TARGET_NAME}) ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Device.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp ${QDMI_HDRS}) # add link libraries target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # set c++ standard - target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) - - # set versioning information - set_target_properties( - ${TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMINaDevice) - # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} @@ -185,13 +175,14 @@ if(NOT TARGET ${TARGET_NAME}) ${CMAKE_CURRENT_BINARY_DIR}/include FILES ${DEVICE_HDR} - ${MQT_CORE_INCLUDE_BUILD_DIR}/na/device/Device.hpp + ${MQT_CORE_INCLUDE_BUILD_DIR}/qdmi/na/Device.hpp ${QDMI_HDRS}) # add link libraries target_link_libraries( ${DYN_TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) # set c++ standard target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) # set versioning information diff --git a/src/na/device/Device.cpp b/src/qdmi/na/Device.cpp similarity index 83% rename from src/na/device/Device.cpp rename to src/qdmi/na/Device.cpp index 634f9487b7..758cc31901 100644 --- a/src/na/device/Device.cpp +++ b/src/qdmi/na/Device.cpp @@ -12,13 +12,13 @@ * @brief The MQT QDMI device implementation for neutral atom devices. */ -#include "na/device/Device.hpp" +#include "qdmi/na/Device.hpp" #include "mqt_na_qdmi/device.h" -#include "na/device/DeviceMemberInitializers.hpp" +#include "qdmi/Common.hpp" +#include "qdmi/na/DeviceMemberInitializers.hpp" #include -#include #include #include #include @@ -31,73 +31,7 @@ #include #include -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, size); -#else -#define STRNCPY(dest, src, size) strncpy(static_cast(dest), src, size); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::na { -std::atomic Device::instance = nullptr; - Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) INITIALIZE_NAME(name_); @@ -110,34 +44,13 @@ Device::Device() { INITIALIZE_SITES(sites_); INITIALIZE_OPERATIONS(operations_); } - -void Device::initialize() { - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; - } -} - -void Device::finalize() { - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; -} - auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } - auto Device::sessionAlloc(MQT_NA_QDMI_Device_Session* session) -> int { if (session == nullptr) { return QDMI_ERROR_INVALIDARGUMENT; @@ -158,7 +71,8 @@ auto Device::sessionFree(MQT_NA_QDMI_Device_Session session) -> void { } auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -219,7 +133,7 @@ auto MQT_NA_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_SESSION_PARAMETER_MAX) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -287,7 +201,7 @@ auto MQT_NA_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_JOB_PARAMETER_MAX) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -297,7 +211,8 @@ auto MQT_NA_QDMI_Device_Job_impl_d::queryProperty( // NOLINTNEXTLINE(readability-non-const-parameter) const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_JOB_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -327,7 +242,8 @@ auto MQT_NA_QDMI_Device_Job_impl_d::getResults( QDMI_Job_Result result, // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { - if ((data != nullptr && size == 0) || result >= QDMI_JOB_RESULT_MAX) { + if ((data != nullptr && size == 0) || + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -367,7 +283,8 @@ auto MQT_NA_QDMI_Site_impl_d::makeUniqueZone(const uint64_t id, const int64_t x, auto MQT_NA_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_SITE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -535,7 +452,8 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( size_t* sizeRet) const -> int { if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || - (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX || + (value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY) || (isZoned_ && numSites > 1) || (!isZoned_ && numSites > 0 && numQubits_ != numSites)) { return QDMI_ERROR_INVALIDARGUMENT; @@ -654,14 +572,12 @@ auto MQT_NA_QDMI_Operation_impl_d::queryProperty( } int MQT_NA_QDMI_device_initialize() { - qdmi::na::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::na::Device::get(); return QDMI_SUCCESS; } -int MQT_NA_QDMI_device_finalize() { - qdmi::na::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_NA_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_NA_QDMI_device_session_alloc(MQT_NA_QDMI_Device_Session* session) { return qdmi::na::Device::get().sessionAlloc(session); diff --git a/src/na/device/DynDevice.cpp b/src/qdmi/na/DynDevice.cpp similarity index 100% rename from src/na/device/DynDevice.cpp rename to src/qdmi/na/DynDevice.cpp diff --git a/src/na/device/Generator.cpp b/src/qdmi/na/Generator.cpp similarity index 99% rename from src/na/device/Generator.cpp rename to src/qdmi/na/Generator.cpp index 978b26e21b..90f64f1c21 100644 --- a/src/na/device/Generator.cpp +++ b/src/qdmi/na/Generator.cpp @@ -12,7 +12,7 @@ * @brief The MQT QDMI device generator for neutral atom devices. */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/src/qdmi/sc/CMakeLists.txt b/src/qdmi/sc/CMakeLists.txt index f83c70bbac..e477eedd23 100644 --- a/src/qdmi/sc/CMakeLists.txt +++ b/src/qdmi/sc/CMakeLists.txt @@ -132,22 +132,12 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE MQT::CoreQDMICommon spdlog::spdlog) # Always compile with position independent code such that the library can be used in shared # libraries set_target_properties(${TARGET_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) - # set c++ standard - target_compile_features(${TARGET_NAME} PRIVATE cxx_std_20) - - # set versioning information - set_target_properties( - ${TARGET_NAME} - PROPERTIES VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - EXPORT_NAME CoreQDMIScDevice) - # add to list of MQT core targets set(MQT_CORE_TARGETS ${MQT_CORE_TARGETS} ${TARGET_NAME} @@ -191,7 +181,8 @@ if(NOT TARGET ${TARGET_NAME}) target_link_libraries( ${DYN_TARGET_NAME} PUBLIC qdmi::qdmi - PRIVATE ${TARGET_NAME} MQT::ProjectOptions MQT::ProjectWarnings spdlog::spdlog) + PRIVATE ${TARGET_NAME} MQT::CoreQDMICommon MQT::ProjectOptions MQT::ProjectWarnings + spdlog::spdlog) # set c++ standard target_compile_features(${DYN_TARGET_NAME} PRIVATE cxx_std_20) # set versioning information diff --git a/src/qdmi/sc/Device.cpp b/src/qdmi/sc/Device.cpp index 3b248e2263..436a0b5e17 100644 --- a/src/qdmi/sc/Device.cpp +++ b/src/qdmi/sc/Device.cpp @@ -15,10 +15,10 @@ #include "qdmi/sc/Device.hpp" #include "mqt_sc_qdmi/device.h" +#include "qdmi/Common.hpp" #include "qdmi/sc/DeviceMemberInitializers.hpp" #include -#include #include #include #include @@ -31,73 +31,7 @@ #include #include -// NOLINTBEGIN(bugprone-macro-parentheses) -#define ADD_SINGLE_VALUE_PROPERTY(prop_name, prop_type, prop_value, prop, \ - size, value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - *static_cast(value) = prop_value; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#ifdef _WIN32 -#define STRNCPY(dest, src, size) \ - strncpy_s(static_cast(dest), size, src, (size) - 1); -#else -#define STRNCPY(dest, src, size) \ - strncpy(static_cast(dest), src, (size) - 1); -#endif - -#define ADD_STRING_PROPERTY(prop_name, prop_value, prop, size, value, \ - size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < strlen(prop_value) + 1) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - STRNCPY(value, prop_value, size); \ - /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ \ - static_cast(value)[size - 1] = '\0'; \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = strlen(prop_value) + 1; \ - } \ - return QDMI_SUCCESS; \ - } \ - } - -#define ADD_LIST_PROPERTY(prop_name, prop_type, prop_values, prop, size, \ - value, size_ret) \ - { \ - if ((prop) == (prop_name)) { \ - if ((value) != nullptr) { \ - if ((size) < (prop_values).size() * sizeof(prop_type)) { \ - return QDMI_ERROR_INVALIDARGUMENT; \ - } \ - memcpy(static_cast(value), \ - static_cast((prop_values).data()), \ - (prop_values).size() * sizeof(prop_type)); \ - } \ - if ((size_ret) != nullptr) { \ - *size_ret = (prop_values).size() * sizeof(prop_type); \ - } \ - return QDMI_SUCCESS; \ - } \ - } -// NOLINTEND(bugprone-macro-parentheses) - namespace qdmi::sc { -std::atomic Device::instance = nullptr; Device::Device() { // NOLINTBEGIN(cppcoreguidelines-prefer-member-initializer) @@ -113,29 +47,12 @@ Device::~Device() { // Explicitly clear sessions before destruction to avoid spurious segfaults sessions_.clear(); } -void Device::initialize() { - // NOLINTNEXTLINE(misc-const-correctness) - Device* expected = nullptr; - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* newInstance = new Device(); - if (!instance.compare_exchange_strong(expected, newInstance)) { - // Another thread won the race, so delete the instance we created. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete newInstance; - } -} -void Device::finalize() { - // Atomically swap the instance pointer with nullptr and get the old value. - const Device* oldInstance = instance.exchange(nullptr); - // Delete the old instance if it existed. - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - delete oldInstance; -} auto Device::get() -> Device& { - auto* loadedInstance = instance.load(); - assert(loadedInstance != nullptr && - "Device not initialized. Call `initialize()` first."); - return *loadedInstance; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + static auto* instance = new Device(); + // The instance is intentionally leaked to avoid static deinitialization + // issues (cf. static (de)initialization order fiasco) + return *instance; } auto Device::sessionAlloc(MQT_SC_QDMI_Device_Session* session) -> int { if (session == nullptr) { @@ -157,7 +74,8 @@ auto Device::sessionFree(MQT_SC_QDMI_Device_Session session) -> void { } auto Device::queryProperty(const QDMI_Device_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_STRING_PROPERTY(QDMI_DEVICE_PROPERTY_NAME, name_.c_str(), prop, size, @@ -209,7 +127,7 @@ auto MQT_SC_QDMI_Device_Session_impl_d::setParameter( QDMI_Device_Session_Parameter param, const size_t size, const void* value) const -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_SESSION_PARAMETER_MAX) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_SESSION_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } if (status_ != Status::ALLOCATED) { @@ -277,7 +195,7 @@ auto MQT_SC_QDMI_Device_Job_impl_d::setParameter( const QDMI_Device_Job_Parameter param, const size_t size, const void* value) -> int { if ((value != nullptr && size == 0) || - param >= QDMI_DEVICE_JOB_PARAMETER_MAX) { + IS_INVALID_ARGUMENT(param, QDMI_DEVICE_JOB_PARAMETER)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -287,7 +205,8 @@ auto MQT_SC_QDMI_Device_Job_impl_d::queryProperty( // NOLINTNEXTLINE(readability-non-const-parameter) const QDMI_Device_Job_Property prop, const size_t size, void* value, [[maybe_unused]] size_t* sizeRet) -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_DEVICE_JOB_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_DEVICE_JOB_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -317,7 +236,8 @@ auto MQT_SC_QDMI_Device_Job_impl_d::getResults( QDMI_Job_Result result, // NOLINTNEXTLINE(readability-non-const-parameter) const size_t size, void* data, [[maybe_unused]] size_t* sizeRet) -> int { - if ((data != nullptr && size == 0) || result >= QDMI_JOB_RESULT_MAX) { + if ((data != nullptr && size == 0) || + IS_INVALID_ARGUMENT(result, QDMI_JOB_RESULT)) { return QDMI_ERROR_INVALIDARGUMENT; } return QDMI_ERROR_NOTSUPPORTED; @@ -330,7 +250,8 @@ auto MQT_SC_QDMI_Site_impl_d::makeUniqueSite(const uint64_t id) auto MQT_SC_QDMI_Site_impl_d::queryProperty(const QDMI_Site_Property prop, const size_t size, void* value, size_t* sizeRet) const -> int { - if ((value != nullptr && size == 0) || prop >= QDMI_SITE_PROPERTY_MAX) { + if ((value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_SITE_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } ADD_SINGLE_VALUE_PROPERTY(QDMI_SITE_PROPERTY_INDEX, uint64_t, id_, prop, size, @@ -396,7 +317,8 @@ auto MQT_SC_QDMI_Operation_impl_d::queryProperty( size_t* sizeRet) const -> int { if ((sites != nullptr && numSites == 0) || (params != nullptr && numParams == 0) || - (value != nullptr && size == 0) || prop >= QDMI_OPERATION_PROPERTY_MAX) { + (value != nullptr && size == 0) || + IS_INVALID_ARGUMENT(prop, QDMI_OPERATION_PROPERTY)) { return QDMI_ERROR_INVALIDARGUMENT; } if (sites != nullptr) { @@ -482,14 +404,12 @@ auto MQT_SC_QDMI_Operation_impl_d::queryProperty( } int MQT_SC_QDMI_device_initialize() { - qdmi::sc::Device::initialize(); + // ensure the singleton is initialized + std::ignore = qdmi::sc::Device::get(); return QDMI_SUCCESS; } -int MQT_SC_QDMI_device_finalize() { - qdmi::sc::Device::finalize(); - return QDMI_SUCCESS; -} +int MQT_SC_QDMI_device_finalize() { return QDMI_SUCCESS; } int MQT_SC_QDMI_device_session_alloc(MQT_SC_QDMI_Device_Session* session) { return qdmi::sc::Device::get().sessionAlloc(session); diff --git a/test/fomac/test_fomac.cpp b/test/fomac/test_fomac.cpp index 8ab717e673..cc4ad04b42 100644 --- a/test/fomac/test_fomac.cpp +++ b/test/fomac/test_fomac.cpp @@ -9,9 +9,12 @@ */ #include "fomac/FoMaC.hpp" +#include "qdmi/Common.hpp" #include #include +#include +#include #include #include #include @@ -20,40 +23,42 @@ #include #include #include +#include #include #include namespace fomac { -class DeviceTest : public testing::TestWithParam { +class DeviceTest : public testing::TestWithParam { protected: - FoMaC::Device device; + Session::Device device; DeviceTest() : device(GetParam()) {} }; class SiteTest : public DeviceTest { protected: - std::vector sites; + std::vector sites; void SetUp() override { sites = device.getSites(); } }; class OperationTest : public DeviceTest { protected: - std::vector operations; + std::vector operations; void SetUp() override { operations = device.getOperations(); } }; class DDSimulatorDeviceTest : public testing::Test { protected: - FoMaC::Device device; + Session::Device device; DDSimulatorDeviceTest() : device(getDDSimulatorDevice()) {} private: - static auto getDDSimulatorDevice() -> FoMaC::Device { - for (const auto& dev : FoMaC::getDevices()) { + static auto getDDSimulatorDevice() -> Session::Device { + Session session; + for (const auto& dev : session.getDevices()) { if (dev.getName() == "MQT Core DDSIM QDMI Device") { return dev; } @@ -64,11 +69,11 @@ class DDSimulatorDeviceTest : public testing::Test { class JobTest : public DDSimulatorDeviceTest { protected: - FoMaC::Job job; + Session::Job job; JobTest() : job(createTestJob()) {} - [[nodiscard]] FoMaC::Job createTestJob() const { + [[nodiscard]] Session::Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[1] q; @@ -82,11 +87,11 @@ c[0] = measure q[0]; class SimulatorJobTest : public DDSimulatorDeviceTest { protected: - FoMaC::Job job; + Session::Job job; SimulatorJobTest() : job(createTestJob()) {} - [[nodiscard]] FoMaC::Job createTestJob() const { + [[nodiscard]] Session::Job createTestJob() const { const std::string qasm3Program = R"( OPENQASM 3.0; qubit[2] q; @@ -98,112 +103,129 @@ cx q[0], q[1]; }; TEST(FoMaCTest, StatusToString) { - EXPECT_EQ(toString(QDMI_WARN_GENERAL), "General warning"); - EXPECT_EQ(toString(QDMI_SUCCESS), "Success"); - EXPECT_EQ(toString(QDMI_ERROR_FATAL), "A fatal error"); - EXPECT_EQ(toString(QDMI_ERROR_OUTOFMEM), "Out of memory"); - EXPECT_EQ(toString(QDMI_ERROR_NOTIMPLEMENTED), "Not implemented"); - EXPECT_EQ(toString(QDMI_ERROR_LIBNOTFOUND), "Library not found"); - EXPECT_EQ(toString(QDMI_ERROR_NOTFOUND), "Element not found"); - EXPECT_EQ(toString(QDMI_ERROR_OUTOFRANGE), "Out of range"); - EXPECT_EQ(toString(QDMI_ERROR_INVALIDARGUMENT), "Invalid argument"); - EXPECT_EQ(toString(QDMI_ERROR_PERMISSIONDENIED), "Permission denied"); - EXPECT_EQ(toString(QDMI_ERROR_NOTSUPPORTED), "Not supported"); - EXPECT_EQ(toString(QDMI_ERROR_BADSTATE), "Bad state"); - EXPECT_EQ(toString(QDMI_ERROR_TIMEOUT), "Timeout"); + EXPECT_STREQ(qdmi::toString(QDMI_WARN_GENERAL), "General warning"); + EXPECT_STREQ(qdmi::toString(QDMI_SUCCESS), "Success"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_FATAL), "A fatal error"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_OUTOFMEM), "Out of memory"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTIMPLEMENTED), "Not implemented"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_LIBNOTFOUND), "Library not found"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTFOUND), "Element not found"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_OUTOFRANGE), "Out of range"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_INVALIDARGUMENT), "Invalid argument"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_PERMISSIONDENIED), + "Permission denied"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_NOTSUPPORTED), "Not supported"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_BADSTATE), "Bad state"); + EXPECT_STREQ(qdmi::toString(QDMI_ERROR_TIMEOUT), "Timeout"); } TEST(FoMaCTest, SitePropertyToString) { - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_INDEX), "QDMI_SITE_PROPERTY_INDEX"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_T1), "QDMI_SITE_PROPERTY_T1"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_T2), "QDMI_SITE_PROPERTY_T2"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_NAME), "QDMI_SITE_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_XCOORDINATE), - "QDMI_SITE_PROPERTY_XCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_YCOORDINATE), - "QDMI_SITE_PROPERTY_YCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ZCOORDINATE), - "QDMI_SITE_PROPERTY_ZCOORDINATE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ISZONE), "QDMI_SITE_PROPERTY_ISZONE"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_XEXTENT), "QDMI_SITE_PROPERTY_XEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_YEXTENT), "QDMI_SITE_PROPERTY_YEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_ZEXTENT), "QDMI_SITE_PROPERTY_ZEXTENT"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_MODULEINDEX), - "QDMI_SITE_PROPERTY_MODULEINDEX"); - EXPECT_EQ(toString(QDMI_SITE_PROPERTY_SUBMODULEINDEX), - "QDMI_SITE_PROPERTY_SUBMODULEINDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_INDEX), "INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_T1), "T1"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_T2), "T2"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_XCOORDINATE), "X COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_YCOORDINATE), "Y COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ZCOORDINATE), "Z COORDINATE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ISZONE), "IS ZONE"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_XEXTENT), "X EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_YEXTENT), "Y EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_ZEXTENT), "Z EXTENT"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_MODULEINDEX), "MODULE INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_SUBMODULEINDEX), + "SUBMODULE INDEX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_SITE_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, OperationPropertyToString) { - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_NAME), - "QDMI_OPERATION_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_QUBITSNUM), - "QDMI_OPERATION_PROPERTY_QUBITSNUM"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_PARAMETERSNUM), - "QDMI_OPERATION_PROPERTY_PARAMETERSNUM"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_DURATION), - "QDMI_OPERATION_PROPERTY_DURATION"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_FIDELITY), - "QDMI_OPERATION_PROPERTY_FIDELITY"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS), - "QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS"); - EXPECT_EQ(toString(QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS), - "QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_QUBITSNUM), "QUBITS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_PARAMETERSNUM), + "PARAMETERS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_DURATION), "DURATION"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_FIDELITY), "FIDELITY"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_INTERACTIONRADIUS), + "INTERACTION RADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_BLOCKINGRADIUS), + "BLOCKING RADIUS"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_IDLINGFIDELITY), + "IDLING FIDELITY"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_ISZONED), "IS ZONED"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_MEANSHUTTLINGSPEED), + "MEAN SHUTTLING SPEED"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_OPERATION_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, DevicePropertyToString) { - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_NAME), "QDMI_DEVICE_PROPERTY_NAME"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_VERSION), - "QDMI_DEVICE_PROPERTY_VERSION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_STATUS), - "QDMI_DEVICE_PROPERTY_STATUS"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LIBRARYVERSION), - "QDMI_DEVICE_PROPERTY_LIBRARYVERSION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_QUBITSNUM), - "QDMI_DEVICE_PROPERTY_QUBITSNUM"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_SITES), "QDMI_DEVICE_PROPERTY_SITES"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_OPERATIONS), - "QDMI_DEVICE_PROPERTY_OPERATIONS"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_COUPLINGMAP), - "QDMI_DEVICE_PROPERTY_COUPLINGMAP"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION), - "QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LENGTHUNIT), - "QDMI_DEVICE_PROPERTY_LENGTHUNIT"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR), - "QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_DURATIONUNIT), - "QDMI_DEVICE_PROPERTY_DURATIONUNIT"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR), - "QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR"); - EXPECT_EQ(toString(QDMI_DEVICE_PROPERTY_MINATOMDISTANCE), - "QDMI_DEVICE_PROPERTY_MINATOMDISTANCE"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_NAME), "NAME"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_VERSION), "VERSION"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_STATUS), "STATUS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LIBRARYVERSION), + "LIBRARY VERSION"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_QUBITSNUM), "QUBITS NUM"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_SITES), "SITES"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_OPERATIONS), "OPERATIONS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_COUPLINGMAP), + "COUPLING MAP"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_NEEDSCALIBRATION), + "NEEDS CALIBRATION"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LENGTHUNIT), "LENGTH UNIT"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_LENGTHSCALEFACTOR), + "LENGTH SCALE FACTOR"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_DURATIONUNIT), + "DURATION UNIT"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_DURATIONSCALEFACTOR), + "DURATION SCALE FACTOR"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_MINATOMDISTANCE), + "MIN ATOM DISTANCE"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_SUPPORTEDPROGRAMFORMATS), + "SUPPORTED PROGRAM FORMATS"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_DEVICE_PROPERTY_CUSTOM5), "CUSTOM5"); } TEST(FoMaCTest, SessionPropertyToString) { - EXPECT_EQ(toString(QDMI_SESSION_PROPERTY_DEVICES), - "QDMI_SESSION_PROPERTY_DEVICES"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PROPERTY_DEVICES), "DEVICES"); } TEST(FoMaCTest, ThrowIfError) { - EXPECT_NO_THROW(throwIfError(QDMI_SUCCESS, "Test")); - EXPECT_NO_THROW(throwIfError(QDMI_WARN_GENERAL, "Test")); - EXPECT_THROW(throwIfError(QDMI_ERROR_FATAL, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_OUTOFMEM, "Test"), std::bad_alloc); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTIMPLEMENTED, "Test"), + EXPECT_NO_THROW(qdmi::throwIfError(QDMI_SUCCESS, "Test")); + EXPECT_NO_THROW(qdmi::throwIfError(QDMI_WARN_GENERAL, "Test")); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_FATAL, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_LIBNOTFOUND, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_OUTOFMEM, "Test"), std::bad_alloc); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTIMPLEMENTED, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTFOUND, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_OUTOFRANGE, "Test"), std::out_of_range); - EXPECT_THROW(throwIfError(QDMI_ERROR_INVALIDARGUMENT, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_LIBNOTFOUND, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTFOUND, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_OUTOFRANGE, "Test"), + std::out_of_range); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_INVALIDARGUMENT, "Test"), std::invalid_argument); - EXPECT_THROW(throwIfError(QDMI_ERROR_PERMISSIONDENIED, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_PERMISSIONDENIED, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_NOTSUPPORTED, "Test"), + std::runtime_error); + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_BADSTATE, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_NOTSUPPORTED, "Test"), + EXPECT_THROW(qdmi::throwIfError(QDMI_ERROR_TIMEOUT, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_BADSTATE, "Test"), std::runtime_error); - EXPECT_THROW(throwIfError(QDMI_ERROR_TIMEOUT, "Test"), std::runtime_error); } TEST_P(DeviceTest, Name) { @@ -720,14 +742,265 @@ TEST_F(SimulatorJobTest, getSparseProbabilitiesReturnsValidProbabilities) { EXPECT_NEAR(it11->second, 0.5, 1e-10); } +TEST(AuthenticationTest, SessionParameterToString) { + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_TOKEN), "TOKEN"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHFILE), "AUTH FILE"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_AUTHURL), "AUTH URL"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_USERNAME), "USERNAME"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_PASSWORD), "PASSWORD"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_PROJECTID), "PROJECT ID"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_MAX), "MAX"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM1), "CUSTOM1"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM2), "CUSTOM2"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM3), "CUSTOM3"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM4), "CUSTOM4"); + EXPECT_STREQ(qdmi::toString(QDMI_SESSION_PARAMETER_CUSTOM5), "CUSTOM5"); +} + +TEST(AuthenticationTest, SessionConstructionWithToken) { + // Empty token should be accepted + SessionConfig config1; + config1.token = ""; + EXPECT_NO_THROW({ const Session session(config1); }); + + // Non-empty token should be accepted + SessionConfig config2; + config2.token = "test_token_123"; + EXPECT_NO_THROW({ const Session session(config2); }); + + // Token with special characters should be accepted + SessionConfig config3; + config3.token = "very_long_token_with_special_characters_!@#$%^&*()"; + EXPECT_NO_THROW({ const Session session(config3); }); +} + +TEST(AuthenticationTest, SessionConstructionWithAuthUrl) { + // Valid HTTPS URL + SessionConfig config1; + config1.authUrl = "https://example.com"; + EXPECT_NO_THROW({ const Session session(config1); }); + + // Valid HTTP URL with port and path + SessionConfig config2; + config2.authUrl = "http://auth.server.com:8080/api"; + EXPECT_NO_THROW({ const Session session(config2); }); + + // Valid HTTPS URL with query parameters + SessionConfig config3; + config3.authUrl = "https://auth.example.com/token?param=value"; + EXPECT_NO_THROW({ const Session session(config3); }); + + // Valid localhost URL + SessionConfig configLocalhost; + configLocalhost.authUrl = "http://localhost"; + EXPECT_NO_THROW({ const Session session(configLocalhost); }); + + // Valid localhost URL with port + SessionConfig configLocalhostPort; + configLocalhostPort.authUrl = "http://localhost:8080"; + EXPECT_NO_THROW({ const Session session(configLocalhostPort); }); + + // Valid localhost URL with port and path + SessionConfig configLocalhostPath; + configLocalhostPath.authUrl = "https://localhost:3000/auth/api"; + EXPECT_NO_THROW({ const Session session(configLocalhostPath); }); + + // Valid IPv4 address URL + SessionConfig configIPv4; + configIPv4.authUrl = "http://127.0.0.1:5000/auth"; + EXPECT_NO_THROW({ const Session session(configIPv4); }); + + // Valid IPv6 address URL + SessionConfig configIPv6; + configIPv6.authUrl = "https://[::1]:8080/auth"; + EXPECT_NO_THROW({ const Session session(configIPv6); }); + + // Invalid URL - not a URL at all (validation fails before setting parameter) + SessionConfig config4; + config4.authUrl = "not-a-url"; + EXPECT_THROW({ const Session session(config4); }, std::runtime_error); + + // Invalid URL - unsupported protocol + SessionConfig config5; + config5.authUrl = "ftp://invalid.com"; + EXPECT_THROW({ const Session session(config5); }, std::runtime_error); + + // Invalid URL - missing protocol + SessionConfig config6; + config6.authUrl = "example.com"; + EXPECT_THROW({ const Session session(config6); }, std::runtime_error); + + // Invalid URL - empty + SessionConfig config7; + config7.authUrl = ""; + EXPECT_THROW({ const Session session(config7); }, std::runtime_error); +} + +TEST(AuthenticationTest, SessionConstructionWithAuthFile) { + // Non-existent file (validation fails before setting parameter) + SessionConfig config1; + config1.authFile = "/nonexistent/path/to/file.txt"; + EXPECT_THROW({ const Session session(config1); }, std::runtime_error); + + // Existing file (should succeed even if parameter is unsupported) + const auto tempDir = std::filesystem::temp_directory_path(); + auto tmpPath = tempDir / ("fomac_test_auth_" + + std::to_string(std::hash{}( + std::this_thread::get_id())) + + ".txt"); + { + std::ofstream tmpFile(tmpPath); + ASSERT_TRUE(tmpFile.is_open()) << "Failed to create temporary file"; + tmpFile << "test_token_content"; + } + + SessionConfig config2; + config2.authFile = tmpPath.string(); + EXPECT_NO_THROW({ const Session session(config2); }); + + // Clean up + std::filesystem::remove(tmpPath); +} + +TEST(AuthenticationTest, SessionConstructionWithUsernamePassword) { + // Username only + SessionConfig config1; + config1.username = "user123"; + EXPECT_NO_THROW({ const Session session(config1); }); + + // Password only + SessionConfig config2; + config2.password = "secure_password"; + EXPECT_NO_THROW({ const Session session(config2); }); + + // Both username and password + SessionConfig config3; + config3.username = "user123"; + config3.password = "secure_password"; + EXPECT_NO_THROW({ const Session session(config3); }); +} + +TEST(AuthenticationTest, SessionConstructionWithProjectId) { + SessionConfig config; + config.projectId = "project-123-abc"; + EXPECT_NO_THROW({ const Session session(config); }); +} + +TEST(AuthenticationTest, SessionConstructionWithMultipleParameters) { + SessionConfig config; + config.token = "test_token"; + config.username = "test_user"; + config.password = "test_pass"; + config.projectId = "test_project"; + EXPECT_NO_THROW({ const Session session(config); }); +} + +TEST(AuthenticationTest, SessionConstructionWithCustomParameters) { + // Custom parameters may not be supported by all devices, or may have specific + // validation requirements. This test verifies they can be passed to the + // Session constructor. Currently a smoke test. + + // Test custom1 - may succeed or fail with validation/unsupported errors + SessionConfig config1; + config1.custom1 = "custom_value_1"; + try { + Session session(config1); + EXPECT_NO_THROW(std::ignore = session.getDevices()); + } catch (const std::invalid_argument&) { + // Validation error - parameter recognized but value invalid + SUCCEED(); + } catch (const std::runtime_error&) { + // Not supported or other error + GTEST_SKIP() << "Custom parameter not supported by backend"; + } + + // Test custom2 + SessionConfig config2; + config2.custom2 = "custom_value_2"; + try { + Session session(config2); + EXPECT_NO_THROW(std::ignore = session.getDevices()); + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { + GTEST_SKIP() << "Custom parameter not supported by backend"; + } + + // Test all custom parameters together + SessionConfig config3; + config3.custom1 = "value1"; + config3.custom2 = "value2"; + config3.custom3 = "value3"; + config3.custom4 = "value4"; + config3.custom5 = "value5"; + try { + Session session(config3); + EXPECT_NO_THROW(std::ignore = session.getDevices()); + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { + GTEST_SKIP() << "Custom parameter not supported by backend"; + } + + // Test mixing custom parameters with standard authentication + SessionConfig config4; + config4.token = "test_token"; + config4.custom1 = "custom_value"; + config4.projectId = "project_id"; + try { + Session session(config4); + EXPECT_NO_THROW(std::ignore = session.getDevices()); + } catch (const std::invalid_argument&) { + SUCCEED(); + } catch (const std::runtime_error&) { + GTEST_SKIP() << "Custom parameter not supported by backend"; + } +} + +TEST(AuthenticationTest, SessionGetDevicesReturnsList) { + Session session; + auto devices = session.getDevices(); + + EXPECT_FALSE(devices.empty()); + + // All elements should be Device instances + for (const auto& device : devices) { + // Device should have a name + EXPECT_FALSE(device.getName().empty()); + } +} + +TEST(AuthenticationTest, SessionMultipleInstances) { + Session session1; + Session session2; + + auto devices1 = session1.getDevices(); + auto devices2 = session2.getDevices(); + + // Both should return devices + EXPECT_FALSE(devices1.empty()); + EXPECT_FALSE(devices2.empty()); + + // Should return the same number of devices + EXPECT_EQ(devices1.size(), devices2.size()); +} + +namespace { +// Helper function to get all devices for parameterized tests +auto getDevices() -> std::vector { + Session session; + return session.getDevices(); +} +} // namespace + INSTANTIATE_TEST_SUITE_P( // Custom instantiation name DeviceTest, // Test suite name DeviceTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -740,8 +1013,8 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name SiteTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); @@ -754,8 +1027,8 @@ INSTANTIATE_TEST_SUITE_P( // Test suite name OperationTest, // Parameters to test with - testing::ValuesIn(FoMaC::getDevices()), - [](const testing::TestParamInfo& paramInfo) { + testing::ValuesIn(getDevices()), + [](const testing::TestParamInfo& paramInfo) { auto name = paramInfo.param.getName(); // Replace spaces with underscores for valid test names std::ranges::replace(name, ' ', '_'); diff --git a/test/na/device/test_device.cpp b/test/na/device/test_device.cpp index abc07fa292..d155f44982 100644 --- a/test/na/device/test_device.cpp +++ b/test/na/device/test_device.cpp @@ -9,7 +9,7 @@ */ #include "mqt_na_qdmi/device.h" -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/test/na/device/test_generator.cpp b/test/na/device/test_generator.cpp index f66b5e9193..aa048c75c6 100644 --- a/test/na/device/test_generator.cpp +++ b/test/na/device/test_generator.cpp @@ -8,7 +8,7 @@ * Licensed under the MIT License */ -#include "na/device/Generator.hpp" +#include "qdmi/na/Generator.hpp" #include #include diff --git a/test/na/fomac/test_fomac.cpp b/test/na/fomac/test_fomac.cpp index 8d1e022d27..246c2ca80c 100644 --- a/test/na/fomac/test_fomac.cpp +++ b/test/na/fomac/test_fomac.cpp @@ -42,7 +42,7 @@ TEST(TestNAFoMaC, TrapsJSONRoundTrip) { } catch (const nlohmann::json::parse_error& e) { GTEST_FAIL() << "JSON parsing error: " << e.what(); } - nlohmann::json qdmiDevice = FoMaC::getDevices().front(); + nlohmann::json qdmiDevice = Session::getDevices().front(); canonicallyOrderLatticeVectors(fomacDevice); canonicallyOrderLatticeVectors(qdmiDevice); EXPECT_EQ(fomacDevice["traps"], qdmiDevice["traps"]); @@ -58,7 +58,7 @@ TEST(TestNAFoMaC, FullJSONRoundTrip) { } catch (const nlohmann::json::parse_error& e) { GTEST_FAIL() << "JSON parsing error: " << e.what(); } - nlohmann::json fomacDevice = FoMaC::getDevices().front(); + nlohmann::json fomacDevice = Session::getDevices().front(); canonicallyOrderLatticeVectors(jsonDevice); canonicallyOrderLatticeVectors(fomacDevice); EXPECT_EQ(jsonDevice, fomacDevice); diff --git a/test/python/fomac/test_fomac.py b/test/python/fomac/test_fomac.py index 0e449efd84..5d92ac84a7 100644 --- a/test/python/fomac/test_fomac.py +++ b/test/python/fomac/test_fomac.py @@ -10,14 +10,27 @@ from __future__ import annotations +import sys +import tempfile +from pathlib import Path from typing import cast import pytest -from mqt.core.fomac import Device, Job, ProgramFormat, devices +from mqt.core.fomac import Device, Job, ProgramFormat, Session -@pytest.fixture(params=devices()) +def _get_devices() -> list[Device]: + """Get all available devices from a Session. + + Returns: + List of all available QDMI devices. + """ + session = Session() + return session.get_devices() + + +@pytest.fixture(params=_get_devices()) def device(request: pytest.FixtureRequest) -> Device: """Fixture to provide a device for testing. @@ -27,7 +40,7 @@ def device(request: pytest.FixtureRequest) -> Device: return cast("Device", request.param) -@pytest.fixture(params=devices()) +@pytest.fixture(params=_get_devices()) def device_and_site(request: pytest.FixtureRequest) -> tuple[Device, Device.Site]: """Fixture to provide a device for testing. @@ -39,7 +52,7 @@ def device_and_site(request: pytest.FixtureRequest) -> tuple[Device, Device.Site return dev, site -@pytest.fixture(params=devices()) +@pytest.fixture(params=_get_devices()) def device_and_operation(request: pytest.FixtureRequest) -> tuple[Device, Device.Operation]: """Fixture to provide a device for testing. @@ -64,7 +77,7 @@ def ddsim_device() -> Device: Returns: The MQT Core DDSIM QDMI Device if it can be found. """ - for dev in devices(): + for dev in _get_devices(): if dev.name() == "MQT Core DDSIM QDMI Device": return dev pytest.skip("DDSIM device not found - job submission tests require DDSIM device") @@ -583,3 +596,222 @@ def test_simulator_job_get_sparse_probabilities_returns_valid_probabilities(simu assert "11" in sparse_probabilities assert sparse_probabilities["11"] == pytest.approx(0.5) + + +def test_session_construction_with_token() -> None: + """Test Session construction with a token parameter. + + Unsupported parameters are skipped during Session initialization, + so Session construction succeeds unless there's a critical error. + """ + # Empty token should be accepted + session = Session(token="") + assert session is not None + + # Non-empty token should be accepted + session = Session(token="test_token_123") # noqa: S106 + assert session is not None + + # Token with special characters should be accepted + session = Session(token="very_long_token_with_special_characters_!@#$%^&*()") # noqa: S106 + assert session is not None + + +def test_session_construction_with_auth_url() -> None: + """Test Session construction with auth URL parameter. + + Valid URLs should pass validation and Session construction should succeed + (even if the parameter is unsupported and skipped). Invalid URLs should + fail validation before attempting to set the parameter. + """ + # Valid HTTPS URL + session = Session(auth_url="https://example.com") + assert session is not None + + # Valid HTTP URL with port and path + session = Session(auth_url="http://auth.server.com:8080/api") + assert session is not None + + # Valid HTTPS URL with query parameters + session = Session(auth_url="https://auth.example.com/token?param=value") + assert session is not None + + # Valid localhost URL + session = Session(auth_url="http://localhost") + assert session is not None + + # Valid localhost URL with port + session = Session(auth_url="http://localhost:8080") + assert session is not None + + # Valid localhost URL with port and path + session = Session(auth_url="https://localhost:3000/auth/api") + assert session is not None + + # Invalid URL - not a URL at all + with pytest.raises(RuntimeError): + Session(auth_url="not-a-url") + + # Invalid URL - unsupported protocol + with pytest.raises(RuntimeError): + Session(auth_url="ftp://invalid.com") + + # Invalid URL - missing protocol + with pytest.raises(RuntimeError): + Session(auth_url="example.com") + + +def test_session_construction_with_auth_file() -> None: + """Test Session construction with auth file parameter. + + Existing files should pass validation and Session construction should succeed. + Non-existent files should fail validation before attempting to set the parameter. + """ + # Test with non-existent file + with pytest.raises(RuntimeError): + Session(auth_file="/nonexistent/path/to/file.txt") + + # Test with existing file + with tempfile.NamedTemporaryFile(encoding="utf-8", mode="w", delete=False, suffix=".txt") as tmp_file: + tmp_file.write("test_token_content") + tmp_path = tmp_file.name + + try: + # Existing file should be accepted (validation passes, parameter may be skipped) + session = Session(auth_file=tmp_path) + assert session is not None + finally: + # Clean up + Path(tmp_path).unlink(missing_ok=True) + + +def test_session_construction_with_username_password() -> None: + """Test Session construction with username and password parameters. + + Unsupported parameters are skipped, so construction should succeed. + """ + # Username only + session = Session(username="user123") + assert session is not None + + # Password only + session = Session(password="secure_password") # noqa: S106 + assert session is not None + + # Both username and password + session = Session(username="user123", password="secure_password") # noqa: S106 + assert session is not None + + +def test_session_construction_with_project_id() -> None: + """Test Session construction with project ID parameter. + + Unsupported parameters are skipped, so construction should succeed. + """ + session = Session(project_id="project-123-abc") + assert session is not None + + +def test_session_construction_with_multiple_parameters() -> None: + """Test Session construction with multiple authentication parameters. + + Unsupported parameters are skipped, so construction should succeed. + """ + session = Session( + token="test_token", # noqa: S106 + username="test_user", + password="test_pass", # noqa: S106 + project_id="test_project", + ) + assert session is not None + + +def test_session_construction_with_custom_parameters() -> None: + """Test Session construction with custom configuration parameters. + + Custom parameters may not be supported by all devices, or may have specific + validation requirements. This test verifies they can be passed to the Session + constructor. Currently a smoke test.. + """ + # Test custom1 - may succeed or fail with validation/unsupported errors + try: + session = Session(custom1="custom_value_1") + assert session is not None + except (RuntimeError, ValueError): + pass + + # Test custom2 + try: + session = Session(custom2="custom_value_2") + assert session is not None + except (RuntimeError, ValueError): + pass + + # Test all custom parameters together + try: + session = Session( + custom1="value1", + custom2="value2", + custom3="value3", + custom4="value4", + custom5="value5", + ) + assert session is not None + except (RuntimeError, ValueError): + pass + + # Test mixing custom parameters with standard authentication + try: + session = Session( + token="test_token", # noqa: S106 + custom1="custom_value", + project_id="project_id", + ) + assert session is not None + except (RuntimeError, ValueError): + pass + + +def test_session_get_devices_returns_list() -> None: + """Test that get_devices() returns a list of Device objects.""" + session = Session() + devices = session.get_devices() + + assert isinstance(devices, list) + assert len(devices) > 0 + + # All elements should be Device instances + for device in devices: + assert isinstance(device, Device) + # Device should have a name + assert len(device.name()) > 0 + + +def test_session_multiple_instances() -> None: + """Test that multiple Session instances can be created independently.""" + session1 = Session() + session2 = Session() + + devices1 = session1.get_devices() + devices2 = session2.get_devices() + + # Both should return devices + assert len(devices1) > 0 + assert len(devices2) > 0 + + # Should return the same number of devices + assert len(devices1) == len(devices2) + + +if sys.platform != "win32": + from mqt.core import fomac + + def test_add_dynamic_device_library_exists() -> None: + """Test that add_dynamic_device_library function exists on non-Windows platforms.""" + assert hasattr(fomac, "add_dynamic_device_library") + assert callable(fomac.add_dynamic_device_library) + + def test_add_dynamic_device_library_nonexistent_library() -> None: + """Test that loading a non-existent library raises an error.""" + with pytest.raises(RuntimeError): + fomac.add_dynamic_device_library("/nonexistent/lib.so", "PREFIX") diff --git a/test/python/plugins/qiskit/conftest.py b/test/python/plugins/qiskit/conftest.py index 6c979b9c7a..ae745923e3 100644 --- a/test/python/plugins/qiskit/conftest.py +++ b/test/python/plugins/qiskit/conftest.py @@ -451,7 +451,8 @@ def real_qdmi_devices() -> list[fomac.Device]: Returns: List of all QDMI devices from FoMaC. """ - return fomac.devices() + session = fomac.Session() + return session.get_devices() @pytest.fixture(scope="module") diff --git a/test/python/plugins/qiskit/test_backend.py b/test/python/plugins/qiskit/test_backend.py index 18eb21a0ae..078299ec89 100644 --- a/test/python/plugins/qiskit/test_backend.py +++ b/test/python/plugins/qiskit/test_backend.py @@ -265,11 +265,11 @@ def test_backend_warns_on_unmappable_operation( operations=["cz", "custom_unmappable_gate", "measure"], ) - # Monkeypatch fomac.devices to return mock device - def mock_devices() -> list[MockQDMIDevice]: + # Monkeypatch Session.get_devices to return mock device + def mock_get_devices(_self: object) -> list[MockQDMIDevice]: return [mock_device] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) # Creating backend should trigger warning about unmappable operation with warnings.catch_warnings(record=True) as w: @@ -297,11 +297,11 @@ def test_backend_warns_on_missing_measurement_operation( operations=["cz"], # No measure operation ) - # Monkeypatch fomac.devices to return mock device - def mock_devices() -> list[MockQDMIDevice]: + # Monkeypatch Session.get_devices to return mock device + def mock_get_devices(_self: object) -> list[MockQDMIDevice]: return [mock_device] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) # Creating backend should trigger warning about missing measurement operation with warnings.catch_warnings(record=True) as w: diff --git a/test/python/plugins/qiskit/test_provider.py b/test/python/plugins/qiskit/test_provider.py index 6ae4442a0b..7865fb26a5 100644 --- a/test/python/plugins/qiskit/test_provider.py +++ b/test/python/plugins/qiskit/test_provider.py @@ -10,6 +10,9 @@ from __future__ import annotations +import tempfile +from pathlib import Path + import pytest from mqt.core import fomac @@ -80,10 +83,10 @@ def test_provider_get_backend_no_devices(monkeypatch: pytest.MonkeyPatch) -> Non """Provider raises ValueError when no devices available.""" # Monkeypatch to return empty device list - def mock_devices() -> list[object]: + def mock_get_devices(_self: object) -> list[object]: return [] - monkeypatch.setattr(fomac, "devices", mock_devices) + monkeypatch.setattr(fomac.Session, "get_devices", mock_get_devices) provider = QDMIProvider() with pytest.raises(ValueError, match="No backend found with name"): @@ -104,3 +107,143 @@ def test_backend_has_provider_reference() -> None: backend = provider.get_backend("MQT Core DDSIM QDMI Device") assert backend.provider is provider + + +def test_provider_with_token_parameter() -> None: + """Provider accepts token parameter.""" + # Should not raise an error when creating provider with token + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(token="test_token") # noqa: S106 + # If device supports token, verify provider was created + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_auth_file_parameter() -> None: + """Provider accepts auth_file parameter.""" + # Create a temporary file for testing + with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8") as tmp_file: + tmp_file.write("test_auth_content") + tmp_path = tmp_file.name + + try: + # Should not raise an error when creating provider with auth_file + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(auth_file=tmp_path) + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + finally: + # Clean up + Path(tmp_path).unlink(missing_ok=True) + + +def test_provider_with_auth_url_parameter() -> None: + """Provider accepts auth_url parameter.""" + # Should not raise an error when creating provider with auth_url + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(auth_url="https://auth.example.com") + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_username_password_parameters() -> None: + """Provider accepts username and password parameters.""" + # Should not raise an error when creating provider with username and password + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(username="test_user", password="test_pass") # noqa: S106 + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_project_id_parameter() -> None: + """Provider accepts project_id parameter.""" + # Should not raise an error when creating provider with project_id + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider(project_id="test_project") + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_with_multiple_auth_parameters() -> None: + """Provider accepts multiple authentication parameters.""" + # Should not raise an error when creating provider with multiple auth parameters + # Note: The currently available QDMI devices don't support authentication. + try: + provider = QDMIProvider( + token="test_token", # noqa: S106 + username="test_user", + password="test_pass", # noqa: S106 + project_id="test_project", + ) + assert provider is not None + except RuntimeError: + # If not supported, that's okay for now + pass + + +def test_provider_default_constructor_unchanged() -> None: + """Provider default constructor behavior is unchanged (backward compatibility).""" + # Create provider without any parameters + provider = QDMIProvider() + + # Should have at least one backend (the DDSIM device) + backends = provider.backends() + assert len(backends) > 0 + + # Should be able to get backends + all_backends = provider.backends() + assert isinstance(all_backends, list) + + # Should be able to get a specific backend + backend = provider.get_backend("MQT Core DDSIM QDMI Device") + assert backend.name == "MQT Core DDSIM QDMI Device" + + +def test_provider_with_custom_parameters() -> None: + """Provider accepts custom configuration parameters.""" + # Test custom1 + try: + provider = QDMIProvider(custom1="custom_value_1") + assert provider is not None + except (RuntimeError, ValueError): + # If not supported, that's okay for now + pass + + # Test all custom parameters together + try: + provider = QDMIProvider( + custom1="value1", + custom2="value2", + custom3="value3", + custom4="value4", + custom5="value5", + ) + assert provider is not None + except (RuntimeError, ValueError): + pass + + # Test mixing custom with standard authentication + try: + provider = QDMIProvider( + token="test_token", # noqa: S106 + custom1="custom_value", + project_id="project_id", + ) + assert provider is not None + except (RuntimeError, ValueError): + pass diff --git a/test/qdmi/dd/results_sampling_test.cpp b/test/qdmi/dd/results_sampling_test.cpp index e6707be467..b60abff206 100644 --- a/test/qdmi/dd/results_sampling_test.cpp +++ b/test/qdmi/dd/results_sampling_test.cpp @@ -32,7 +32,7 @@ TEST(ResultsSampling, HistogramKeysAndValuesSumToShots) { auto [keys, vals] = qdmi_test::getHistogram(j.job); ASSERT_EQ(keys.size(), vals.size()); - auto sum = 0U; + size_t sum = 0U; for (const auto& v : vals) { sum += v; } diff --git a/test/qdmi/test_driver.cpp b/test/qdmi/test_driver.cpp index 3473c05edd..1728f3b465 100644 --- a/test/qdmi/test_driver.cpp +++ b/test/qdmi/test_driver.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -49,9 +50,11 @@ class DriverTest : public testing::TestWithParam { QDMI_Device device = nullptr; #ifndef _WIN32 static void SetUpTestSuite() { + // Load dynamic libraries with default device session configuration + const qdmi::DeviceSessionConfig config; ASSERT_NO_THROW({ for (const auto& [lib, prefix] : DYN_DEV_LIBS) { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix); + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); } }); } @@ -125,19 +128,6 @@ class DriverJobTest : public DriverTest { } }; -#ifndef _WIN32 -TEST(DriverTest, LoadLibraryTwice) { - // Verify that attempting to load already-loaded libraries returns false. - // Note: SetUpTestSuite may have already loaded these libraries, so the first - // call here might also return false. This test validates that duplicate loads - // are safely handled and consistently return false (idempotent behavior). - EXPECT_NO_THROW(for (const auto& [lib, prefix] : DYN_DEV_LIBS) { - qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix); - EXPECT_FALSE(qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix)); - }); -} -#endif // _WIN32 - TEST_P(DriverTest, SessionSetParameter) { const std::string authFile = "authfile.txt"; QDMI_Session uninitializedSession = nullptr; @@ -539,6 +529,184 @@ INSTANTIATE_TEST_SUITE_P( std::erase(name, ')'); return name; }); + +#ifndef _WIN32 +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithBaseUrl) { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithCustomParameters) { + qdmi::DeviceSessionConfig config; + config.custom1 = "RESONANCE_COCOS_V1"; + config.custom2 = "test_value"; + config.baseUrl = "http://localhost:9090"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + // Custom parameters may fail with validation errors or succeed/return false + try { + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + SUCCEED() << "Library loaded or already loaded"; + } catch (const std::runtime_error& e) { + // Custom parameters may be rejected with INVALIDARGUMENT + const std::string msg = e.what(); + if (msg.find("CUSTOM") != std::string::npos && + msg.find("Invalid argument") != std::string::npos) { + SUCCEED() << "Custom parameter validation error (expected): " << msg; + } else { + throw; // Re-throw unexpected errors + } + } + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthToken) { + qdmi::DeviceSessionConfig config; + config.token = "test_token_123"; + config.baseUrl = "https://api.example.com"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAuthFile) { + qdmi::DeviceSessionConfig config; + config.authFile = "/nonexistent/auth.json"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + // This should not throw even with non-existent file because + // if the auth file parameter is not supported, it's skipped + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithUsernamePassword) { + qdmi::DeviceSessionConfig config; + config.authUrl = "https://auth.example.com"; + config.username = "quantum_user"; + config.password = "secret_password"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + EXPECT_NO_THROW( + { qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); }); + } +} + +TEST(DeviceSessionConfigTest, AddDynamicDeviceLibraryWithAllParameters) { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + config.token = "test_token"; + config.authUrl = "https://auth.example.com"; + config.username = "user"; + config.password = "pass"; + config.custom1 = "value1"; + config.custom2 = "value2"; + config.custom3 = "value3"; + config.custom4 = "value4"; + config.custom5 = "value5"; + + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + try { + qdmi::Driver::get().addDynamicDeviceLibrary(lib, prefix, config); + SUCCEED() << "Library loaded or already loaded"; + } catch (const std::runtime_error& e) { + // Custom parameters may be rejected with INVALIDARGUMENT + const std::string msg = e.what(); + if (msg.find("CUSTOM") != std::string::npos && + msg.find("Invalid argument") != std::string::npos) { + SUCCEED() << "Custom parameter validation error (expected): " << msg; + } else { + throw; // Re-throw unexpected errors + } + } + } +} + +TEST(DeviceSessionConfigTest, VerifyDynamicDevicesInSession) { + QDMI_Session session = nullptr; + ASSERT_EQ(QDMI_session_alloc(&session), QDMI_SUCCESS); + ASSERT_EQ(QDMI_session_init(session), QDMI_SUCCESS); + + size_t devicesSize = 0; + ASSERT_EQ(QDMI_session_query_session_property(session, + QDMI_SESSION_PROPERTY_DEVICES, + 0, nullptr, &devicesSize), + QDMI_SUCCESS); + + const size_t numDevices = devicesSize / sizeof(QDMI_Device); + + // Should have at least the static devices (NA, DDSIM, SC) + const size_t expectedMinDevices = 3; + EXPECT_GE(numDevices, expectedMinDevices) + << "Should have at least " << expectedMinDevices << " static devices"; + + // Verify we can query device names + std::vector devices(numDevices); + ASSERT_EQ(QDMI_session_query_session_property( + session, QDMI_SESSION_PROPERTY_DEVICES, devicesSize, + static_cast(devices.data()), nullptr), + QDMI_SUCCESS); + + for (auto* device : devices) { + size_t nameSize = 0; + ASSERT_EQ(QDMI_device_query_device_property( + device, QDMI_DEVICE_PROPERTY_NAME, 0, nullptr, &nameSize), + QDMI_SUCCESS); + + std::string name(nameSize - 1, '\0'); + ASSERT_EQ(QDMI_device_query_device_property(device, + QDMI_DEVICE_PROPERTY_NAME, + nameSize, name.data(), nullptr), + QDMI_SUCCESS); + + EXPECT_FALSE(name.empty()) << "Device should have a non-empty name"; + } + + QDMI_session_free(session); +} + +TEST(DeviceSessionConfigTest, IdempotentLoadingWithDifferentConfigs) { + // This test is explicitly not part of the fixture because this would + // automatically load the default config and the respective libraries. + if constexpr (DYN_DEV_LIBS.empty()) { + GTEST_SKIP() << "No dynamic device libraries to test"; + } + auto& driver = qdmi::Driver::get(); + for (const auto& [lib, prefix] : DYN_DEV_LIBS) { + // Config 1: baseUrl + { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:8080"; + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + } + + // Config 2: different baseUrl and custom parameters + { + qdmi::DeviceSessionConfig config; + config.baseUrl = "http://localhost:9090"; + config.custom1 = "API_V2"; + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + } + + // Config 3: authentication parameters + { + qdmi::DeviceSessionConfig config; + config.token = "new_token"; + config.authUrl = "https://auth.example.com"; + EXPECT_NO_THROW(driver.addDynamicDeviceLibrary(lib, prefix, config);); + } + } +} +#endif // _WIN32 + INSTANTIATE_TEST_SUITE_P( // Custom instantiation name DefaultDevices,