QDMI device configuration

MQT Core discovers QDMI device definitions from versioned JSON configuration. Discovery only parses definitions. When the QDMI driver initializes a client session, it opens the configured native libraries. The stable-ID API opens only the requested device.

Warning

QDMI configuration is a native-code loading trust boundary. Use configuration files and device libraries only from trusted sources. Project discovery starts at the current working directory and searches its parent directories. Before you process an untrusted checkout, set MQT_CORE_QDMI_CONFIG_FILE to an administrator-controlled file or use a working directory outside that checkout. The explicit file replaces system, user, and project discovery but retains packaged device definitions. Treat MQT_CORE_QDMI_CONFIG_JSON as trusted input too.

Device definitions

The following qdmi.json registers one device:

{
  "schema-version": 1,
  "qdmi": {
    "devices": [
      {
        "id": "example.device",
        "library": "libexample-device.so",
        "prefix": "EXAMPLE",
        "enabled": true,
        "session": {
          "base-url": "https://device.example",
          "auth-file": "credentials.json",
          "device-config": {
            "file": "device.json"
          }
        }
      }
    ]
  }
}

Every enabled definition requires a stable, unique id, a library, and a QDMI symbol prefix. The session object supports base-url, token, auth-file, auth-url, username, password, device-config, and custom1 through custom5.

device-config selects exactly one provider configuration source:

{"device-config": {"inline": {"schema-version": 1}}}

or:

{"device-config": {"file": "device.json"}}

The inline value must be a JSON object. A relative file path is resolved against the registry file that declares it. The complete source is one merge field: changing from inline to file at a higher-precedence layer replaces the inherited inline JSON. The Driver adapts inline JSON to QDMI v1 CUSTOM1 and a file path to CUSTOM2 when opening the native session. Consequently, device-config cannot be combined with raw custom1 or custom2; CUSTOM3 through CUSTOM5 remain available to providers.

Relative library and authentication-file paths are resolved against the file that declared them. For MQT_CORE_QDMI_CONFIG_JSON, they resolve against the current working directory.

Unknown keys, invalid types, duplicate IDs within one source, unsupported schema versions, and incomplete enabled definitions are hard errors. Diagnostics name the source and configuration path. Credentials and session values are not included in Driver warnings.

Discovery and precedence

Definitions are merged field by field by ID, from lowest to highest precedence:

  1. generated *.qdmi.json fragments packaged beside the MQT Core Driver;

  2. the system qdmi.json;

  3. the user or XDG qdmi.json;

  4. the nearest project qdmi.json;

  5. MQT_CORE_QDMI_CONFIG_JSON.

On Unix, file configuration uses /etc/mqt-core/qdmi.json and then ${XDG_CONFIG_HOME}/mqt-core/qdmi.json, falling back to ${HOME}/.config/mqt-core/qdmi.json. On Windows, it uses the corresponding mqt-core/qdmi.json files below PROGRAMDATA and APPDATA.

An entry containing only its ID and "enabled": false masks an inherited definition. Since definitions are merged field by field, a later definition with the same ID must explicitly set "enabled": true to enable it again. The final disabled ID remains reserved, so fallback registration cannot silently re-enable a device that an administrator disabled.

MQT_CORE_QDMI_CONFIG_FILE replaces the system, user, and project levels while retaining packaged built-ins.

Using configured devices

When the QDMI driver initializes a client session, it opens the configured definitions. A failure to load one definition does not hide the remaining devices. Stable-ID registration does not initialize device libraries.

from mqt.core.qdmi.driver import open_device, registered_device_ids

for device_id in registered_device_ids():
    print(open_device(device_id).name())

Set MQT_CORE_QDMI_CONFIG_FILE or MQT_CORE_QDMI_CONFIG_JSON before the first driver call. Applications can also register a definition without loading its library and open it later by stable ID:

from mqt.core.qdmi.driver import DeviceDefinition, open_device, register_device

register_device(
    DeviceDefinition(
        "example.device",
        "/path/to/libexample-device.so",
        "EXAMPLE",
        base_url="https://device.example",
        device_config_file="/path/to/device.json",
    )
)
device = open_device("example.device")

DeviceDefinition and open_device() also accept device_config="<json>" for inline configuration. device_config and device_config_file are mutually exclusive.

Every open_device() call creates a fresh device session while preserving the registered defaults and stable ID. The returned Device and any Site, Operation, or Job wrapper derived from it keeps that fresh device session alive. The session is released after the last such wrapper is destroyed.

Code paths that may be imported more than once can use register_device_if_absent(). It returns whether the definition was inserted and ignores an existing or explicitly disabled stable ID; malformed definitions still raise an error.

Use registered_device_ids() to inspect the enabled stable IDs in deterministic registration order. This includes runtime registrations without loading native device libraries or exposing their paths, prefixes, or session configuration.

The equivalent C++ registration operation is qdmi::Driver::registerDevice. Duplicate IDs are rejected unless replace is true, and an opened definition cannot be replaced. qdmi::Driver::registeredDeviceIds provides the same load-free enumeration, and qdmi::Driver::open returns the cached device. qdmi::Session::openDevice returns a fresh device session and does not add it to the QDMI client catalog. Runtime registrations and explicit opens are not added to that catalog.

Multiple definitions may refer to the same library and prefix. MQT Core reuses the initialized library while creating a fresh QDMI device session, with its own session parameters, for every definition.

Selecting a device from a Slurm license environment

MQT Core provides a mechanism-specific adapter for jobs that use local Slurm licenses for cluster-wide admission. The license name must equal one registered QDMI device ID. Each job must request one license. For example:

sbatch --licenses=mqt.ddsim.default:1 simulation.sh

The job can then open the named device:

from mqt.core.qdmi import slurm

device = slurm.open_device_from_license()

The equivalent C++ function is qdmi::slurm::openDeviceFromLicense() from qdmi/Slurm.hpp. Both functions read SLURM_JOB_LICENSES. They accept only <registered-device-id> or <registered-device-id>:1. They reject remote, compound, and non-unit license values.

The adapter opens a fresh device session from the persistent definition. It does not replace configuration or inject credentials. Each provider defines its own credential sources. The adapter accepts QDMI device status IDLE and BUSY. It rejects all other device states.

SLURM_JOB_LICENSES is process-mutable. The adapter uses this value only for device selection. It does not verify that Slurm allocated the license. It does not authenticate the caller or authorize access to the device. Provider credentials must authorize remote devices. The operating system must isolate a local device when access requires enforcement. A caller can also bypass this adapter and call open_device() with a stable device ID. A different Slurm lookup would therefore not make MQT Core an access control boundary.

A cluster can configure more than one license for a device. For example, mqt.ddsim.default:2 permits two independent jobs to request one license each. The count is a Slurm admission limit. It is not an access permission, a provider availability check, or a provider queue length.

Relocatable packages and static consumers

Built-in targets generate manifests beside their runtime libraries in both build and install trees. Library paths in those fragments contain only the target filename, so moving an installed tree or Python wheel preserves discovery. Automatic discovery searches relative to the MQT Core Driver, not every library loaded by the process. An application using a separately installed device implementation therefore copies its manifest beside the Driver or registers its definition by stable ID.

A fully static executable has no portable shared-module location. Place the fragments beside the executable, point MQT_CORE_QDMI_CONFIG_FILE at a complete configuration, or use qdmi::Driver::registerDevice and qdmi::Driver::open. No install prefix is compiled into the manifests.

An installed MQT Core CMake package provides a helper that colocates selected device libraries and manifests with an executable:

find_package(mqt-core CONFIG REQUIRED)
add_executable(my-application main.cpp)
target_link_libraries(my-application PRIVATE MQT::CoreQDMI)
mqt_copy_qdmi_runtime(
  my-application
  MQT::CoreQDMINaDevice
  MQT::CoreQDMIScDevice
  MQT::CoreQDMI_DDSIM_Device)

Inside an MQT Core build, omitting the device list copies every device registered through mqt_configure_qdmi_device. Installed consumers select the exported device targets they need, as shown above.

An external device implementation does not need MQT Core as a build dependency. It can export its stable ID and prefix as target metadata:

set_target_properties(
  example-device
  PROPERTIES QDMI_DEVICE_ID "example.device"
             QDMI_DEVICE_PREFIX "EXAMPLE")
set_property(
  TARGET example-device
  APPEND
  PROPERTY EXPORT_PROPERTIES QDMI_DEVICE_ID QDMI_DEVICE_PREFIX)

When mqt_copy_qdmi_runtime receives that built or imported target, it generates the relocatable manifest while copying the device. Device targets may also declare RUNTIME_FILES through mqt_configure_qdmi_device; their exported QDMI_RUNTIME_FILES basenames are copied beside the provider as part of the same operation.