Skip to content
Merged
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
074c7ff
:sparkles: QDMI authentication via FoMaC
marcelwa Dec 3, 2025
f1d4360
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 3, 2025
c184cc3
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 4, 2025
9d17fe3
:recycle: Don't re-expose session parameter enum
marcelwa Dec 4, 2025
97a863c
:recycle: Rename `setSessionParameter` to `setParameter`
marcelwa Dec 4, 2025
5705a51
:fire: Remove module-level convenience functions for parameter settin…
marcelwa Dec 4, 2025
b2f8833
:label: Propagate changes to `.pyi`
marcelwa Dec 4, 2025
4369473
:recycle: Session authentication via constructor parameters
marcelwa Dec 4, 2025
84e6f35
:recycle: Rename the `FoMaC` class to `Session`
marcelwa Dec 4, 2025
6a4dc71
:recycle: Adjust Qiskit-QDMI Python interface to adhere to new API
marcelwa Dec 4, 2025
8380a22
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 4, 2025
594b3b3
:white_check_mark: Add placeholder tests for authentication
marcelwa Dec 4, 2025
578b5f2
:bug: Guard session allocation
marcelwa Dec 4, 2025
b6ffb8a
:recycle: Improve URL validation regex
marcelwa Dec 4, 2025
1db21eb
:art: Address CodeRabbit's suggestions
marcelwa Dec 4, 2025
ed6d6b4
:memo: Update documentation to reflect code changes
marcelwa Dec 4, 2025
321ee04
:sparkles: Expose session authentication to QDMIProvider initializati…
marcelwa Dec 4, 2025
7f5cbe9
:bug: Fixed regex format
marcelwa Dec 4, 2025
36aaeae
:white_check_mark: Add authentication FoMaC C++ tests
marcelwa Dec 4, 2025
b4f495c
:rotating_light: Remove unused header
marcelwa Dec 4, 2025
25b13ab
:memo: Documentation on session authentication
marcelwa Dec 4, 2025
4dae4f1
:memo: CHANGELOG
marcelwa Dec 4, 2025
5655d59
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 4, 2025
bed169b
:art: Light code cleanup
marcelwa Dec 4, 2025
9b76cd3
Merge branch 'main' into qdmi-auth
marcelwa Dec 4, 2025
3206c7d
:sparkles: Expose CUSTOM `QDMI_Program_Format` parameters
marcelwa Dec 5, 2025
77ed885
:art: Cleanup Session constructor
marcelwa Dec 5, 2025
5ffabe0
Merge branch 'main' into qdmi-auth
marcelwa Dec 5, 2025
fe6b067
:art: Cleanup Session constructor in bindings
marcelwa Dec 5, 2025
8f1b299
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 5, 2025
7c8047a
:safety_vest: Ensure partially instantiated sessions get cleaned up p…
marcelwa Dec 5, 2025
5680e80
:art: Implement CodeRabbit's suggestions
marcelwa Dec 5, 2025
3d8d402
:recycle: Refactor URL validation to allow local URLs and IPs
marcelwa Dec 5, 2025
9843d31
:art: Refactor QDMI Provider args to kwargs
marcelwa Dec 5, 2025
f95d547
:recycle: Expose `QDMI_DEVICE_SESSION_PARAMETER`s via the `Driver`
marcelwa Dec 5, 2025
b0fed9a
:rotating_light: Address `clang-tidy` warnings
marcelwa Dec 6, 2025
69467b9
:recycle: Refactor `QDMI_Device_impl_d` and `Session` initialization …
marcelwa Dec 6, 2025
fbb7873
:white_check_mark: Stricter Session tests to properly test updated pa…
marcelwa Dec 6, 2025
74cfd1d
:white_check_mark: Test `Driver::addDynamicDeviceLibrary`
marcelwa Dec 6, 2025
7882499
:green_heart: Fix RTD build
marcelwa Dec 6, 2025
106452c
:art: Prefer SPDLOG macros over function calls
marcelwa Dec 6, 2025
d0d132c
:sparkles: Expose device library loading to Python
marcelwa Dec 6, 2025
2e10639
Merge branch 'main' into qdmi-auth
marcelwa Dec 6, 2025
f0b3a32
:art: Implement CodeRabbit's suggestions
marcelwa Dec 6, 2025
3fb8b1c
:rotating_light: Fix `clang-tidy` warnings
marcelwa Dec 6, 2025
ffb8ea5
Merge branch 'main' into qdmi-auth
marcelwa Dec 6, 2025
685c609
Merge branch 'main' into qdmi-auth
burgholzer Dec 6, 2025
5cd5f00
🩹 ensure session stays alive in provider
burgholzer Dec 6, 2025
0a4f68f
♻️ refactor provider initialization to use session kwargs
burgholzer Dec 6, 2025
8e7938c
🎨 remove redundant inline
burgholzer Dec 6, 2025
5ea53a6
✅♻️ refine smoke tests
burgholzer Dec 6, 2025
7b6fb8f
🐛 Fix custom QDMI property and parameter handling in SC and NA devices
burgholzer Dec 7, 2025
f8e719c
🐛 Lock the right mutex in the DD QDMI device
burgholzer Dec 7, 2025
6be3b68
🎨 remove redundant initialization
burgholzer Dec 7, 2025
5b7bd74
♻️ Enable thread-safe reference counting for QDMI devices singletons
burgholzer Dec 7, 2025
f9f0b6b
♻️ Allow repeated loading of QDMI device library with potentially dif…
burgholzer Dec 7, 2025
23b8846
🎨 slightly adjust session parameter setter
burgholzer Dec 7, 2025
951035b
🎨 reduce redundancy via new macro
burgholzer Dec 7, 2025
8e84370
♻️ slightly refactor reference counting
burgholzer Dec 7, 2025
d617428
🔥 remove openLibHandles
burgholzer Dec 7, 2025
d6a5467
✅ add one more test case
burgholzer Dec 7, 2025
e432e56
🚚 move `NA` QDMI device in its right place
burgholzer Dec 7, 2025
2e20fab
🩹 fix include dir
burgholzer Dec 7, 2025
80e88dc
📝 better singleton management docstrings
burgholzer Dec 7, 2025
086f7b2
🩹 fix invalid argument check
burgholzer Dec 7, 2025
0b74445
🚨 additional clang-tidy naming convention rule
burgholzer Dec 7, 2025
6d3dc52
♻️ rework thread-safe singleton logic
burgholzer Dec 7, 2025
7af7e00
Merge branch 'main' into qdmi-auth
burgholzer Dec 7, 2025
cb6f1c2
Revert "🚨 additional clang-tidy naming convention rule"
burgholzer Dec 7, 2025
676a8a1
♻️ simplify handling of static deinitialization order fiasco
burgholzer Dec 7, 2025
090ee48
🚨 clang-tidy
burgholzer Dec 7, 2025
724d2b8
🚨 fix compiler and linter warnings
burgholzer Dec 7, 2025
a3023d5
✨ add common definitions and utilities for QDMI
burgholzer Dec 7, 2025
59b6db4
:memo: Add missing PR reference
marcelwa Dec 7, 2025
1a49aea
:green_heart: Fix Windows tests by using `EXPECT_STREQ` instead of `E…
marcelwa Dec 7, 2025
df67ab4
:recycle: Refactor URL validation regex to allow IPv4 and IPv6 addresses
marcelwa Dec 7, 2025
2ea83b7
:pencil2: Fix typo
marcelwa Dec 7, 2025
931e0e4
:memo: Add comments to highlight the subtle difference between `strnc…
marcelwa Dec 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**])
Expand All @@ -21,6 +22,10 @@ This project adheres to [Semantic Versioning], with the exception that minor rel

### Changed

- 🚚 Move `NA` QDMI device in its right place next to other QDMI devices ([#1355]) ([**@burgholzer**])
- ♻️ Allow repeated loading of QDMI device library with potentially different session configurations ([#1355]) ([**@burgholzer**])
- ♻️ Enable thread-safe reference counting for QDMI devices singletons ([#1355]) ([**@burgholzer**])
- ♻️ Refactor `FoMaC` singleton to instantiable `Session` class with configurable authentication parameters ([#1355]) ([**@marcelwa**])
- 👷 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**])
Expand All @@ -33,6 +38,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**])

Expand Down Expand Up @@ -273,6 +279,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
Expand Down
224 changes: 151 additions & 73 deletions bindings/fomac/fomac.cpp

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions bindings/na/fomac/fomac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pybind11/cast.h>
#include <pybind11/operators.h>
Expand Down Expand Up @@ -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_<na::FoMaC::Device, fomac::FoMaC::Device>(m, "Device");
py::class_<na::Session::Device, fomac::Session::Device>(m, "Device");

auto lattice = py::class_<na::Device::Lattice>(device, "Lattice");

Expand Down Expand Up @@ -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 name=\"" + dev.getName() + "\">";
});
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
17 changes: 10 additions & 7 deletions docs/qdmi/driver.md
Original file line number Diff line number Diff line change
Expand Up @@ -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())
```
106 changes: 106 additions & 0 deletions docs/qdmi/qdmi_backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
91 changes: 62 additions & 29 deletions include/mqt-core/fomac/FoMaC.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <iostream>
#include <iterator>
#include <map>
#include <mutex>
#include <optional>
#include <qdmi/client.h>
#include <ranges>
Expand Down Expand Up @@ -160,33 +161,56 @@ constexpr auto toString(QDMI_Session_Property prop) -> std::string {
return "QDMI_SESSION_PROPERTY_UNKNOWN";
}

/// @returns the string representation of the given QDMI_SESSION_PARAMETER_T.
auto toString(QDMI_SESSION_PARAMETER_T param) -> std::string;

/// Throws an exception corresponding to the given QDMI_STATUS code.
[[noreturn]] auto throwError(int result, const std::string& msg) -> void;

/// Throws an exception if the result indicates an error.
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);
}
}
auto throwIfError(int result, const std::string& msg) -> void;

/**
* @brief Configuration structure for session authentication parameters.
* @details All parameters are optional. Only set the parameters needed for
* your authentication method. Parameters are validated when the session is
* constructed.
*/
struct SessionConfig {
/// Authentication token
std::optional<std::string> token;
/// Path to file containing authentication information
std::optional<std::string> authFile;
/// URL to authentication server
std::optional<std::string> authUrl;
/// Username for authentication
std::optional<std::string> username;
/// Password for authentication
std::optional<std::string> password;
/// Project ID for session
std::optional<std::string> projectId;
/// Custom configuration parameter 1
std::optional<std::string> custom1;
/// Custom configuration parameter 2
std::optional<std::string> custom2;
/// Custom configuration parameter 3
std::optional<std::string> custom3;
/// Custom configuration parameter 4
std::optional<std::string> custom4;
/// Custom configuration parameter 5
std::optional<std::string> 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:
Expand Down Expand Up @@ -608,7 +632,7 @@ class FoMaC {
* @brief Constructs a Device object from a QDMI_Device handle.
* @param device The QDMI_Device handle to wrap.
*/
Device(FoMaC::Token /* unused */, QDMI_Device device) : device_(device) {}
Device(Session::Token /* unused */, QDMI_Device device) : device_(device) {}
/// @returns the underlying QDMI_Device object.
[[nodiscard]] auto getQDMIDevice() const -> QDMI_Device { return device_; }
// NOLINTNEXTLINE(google-explicit-constructor)
Expand Down Expand Up @@ -675,11 +699,6 @@ class FoMaC {
private:
QDMI_Session session_ = nullptr;

FoMaC();
static auto get() -> FoMaC& {
static FoMaC instance;
return instance;
}
template <size_constructible_contiguous_range T>
[[nodiscard]] auto queryProperty(const QDMI_Session_Property prop) const
-> T {
Expand All @@ -696,14 +715,28 @@ class FoMaC {
}

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<Device>;
[[nodiscard]] auto getDevices() -> std::vector<Device>;
};
} // namespace fomac
Loading
Loading